gpsd.php.in 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  1. <?php
  2. # @MASTER@
  3. # @GENERATED@
  4. # Copyright 2006 Chris Kuethe <chris.kuethe@gmail.com>
  5. #
  6. # This file is Copyright 2009 The GPSD project
  7. # SPDX-License-Identifier: BSD-2-clause
  8. # This code validates with https://validator.w3.org/
  9. # Keep it valid.
  10. global $head, $blurb, $title, $showmap, $autorefresh, $footer, $gmap_key;
  11. global $server, $advertise, $port, $open, $swap_ew, $testmode;
  12. global $colors;
  13. $testmode = 1; # leave this set to 1
  14. # Public script parameters:
  15. # host: host name or address where GPSd runs. Default: from config file
  16. # port: port of GPSd. Default: from config file
  17. # op=view: show just the skyview image instead of the whole HTML page
  18. # sz=small: used with op=view, display a small (240x240px) skyview
  19. # op=json: respond with the GPSd POLL JSON structure
  20. # jsonp=prefix: used with op=json, wrap the POLL JSON in parentheses
  21. # and prepend prefix
  22. # If you're running PHP with the Suhosin patch (like the Debian PHP5 package),
  23. # it may be necessary to increase the value of the
  24. # suhosin.get.max_value_length parameter to 2048. The imgdata parameter used
  25. # for displaying the skyview is longer than the default 512 allowed by Suhosin.
  26. # Debian has the config file at /etc/php5/conf.d/suhosin.ini.
  27. # If you use the OPenLayers code you will likely want to server
  28. # their JS locally.
  29. # this script shouldn't take more than a few seconds to run
  30. set_time_limit(3);
  31. ini_set('max_execution_time', 3);
  32. if (!file_exists("gpsd_config.inc"))
  33. write_config();
  34. require_once("gpsd_config.inc");
  35. # sample data
  36. $resp = <<<EOF
  37. {"class":"POLL","time":"2010-04-05T21:27:54.84Z","active":1,
  38. "tpv":[{"class":"TPV","tag":"MID41","device":"/dev/ttyUSB0",
  39. "time":1270517264.240,"ept":0.005,"lat":40.035093060,
  40. "lon":-75.519748733,"alt":31.1,"track":99.4319,
  41. "speed":0.123,"mode":3}],
  42. "sky":[{"class":"SKY","tag":"MID41","device":"/dev/ttyUSB0",
  43. "time":"2010-04-05T21:27:44.84Z","hdop":9.20,"vdop":12.1,
  44. "satellites":[{"PRN":16,"el":55,"az":42,"ss":36,"used":true},
  45. {"PRN":19,"el":25,"az":177,"ss":0,"used":false},
  46. {"PRN":7,"el":13,"az":295,"ss":0,"used":false},
  47. {"PRN":6,"el":56,"az":135,"ss":32,"used":true},
  48. {"PRN":13,"el":47,"az":304,"ss":0,"used":false},
  49. {"PRN":23,"el":66,"az":259,"ss":40,"used":true},
  50. {"PRN":20,"el":7,"az":226,"ss":0,"used":false},
  51. {"PRN":3,"el":52,"az":163,"ss":32,"used":true},
  52. {"PRN":31,"el":16,"az":102,"ss":0,"used":false}
  53. ]
  54. }
  55. ]
  56. }
  57. EOF;
  58. # if we're passing in a query, let's unpack and use it
  59. $op = isset($_GET['op']) ? $_GET['op'] : '';
  60. if (isset($_GET['imgdata']) && $op == 'view'){
  61. $resp = base64_decode($_GET['imgdata']);
  62. if ($resp){
  63. gen_image($resp);
  64. exit(0);
  65. }
  66. } else {
  67. if (isset($_GET['host']))
  68. if (!preg_match('/[^a-zA-Z0-9\.-]/', $_GET['host']))
  69. $server = $_GET['host'];
  70. if (isset($_GET['port']))
  71. if (!preg_match('/\D/', $_GET['port']) &&
  72. ($port > 0) && ($port < 65536))
  73. $port = $_GET['port'];
  74. if ($testmode){
  75. $sock = @fsockopen($server, $port, $errno, $errstr, 2);
  76. @fwrite($sock, "?WATCH={\"enable\":true}\n");
  77. usleep(1000);
  78. @fwrite($sock, "?POLL;\n");
  79. usleep(1000);
  80. for($tries = 0; $tries < 10; $tries++){
  81. $resp = @fread($sock, 10000); # SKY can be pretty big
  82. if (preg_match('/{"class":"POLL".+}/i', $resp, $m)){
  83. $resp = $m[0];
  84. break;
  85. }
  86. }
  87. @fclose($sock);
  88. if (!$resp)
  89. $resp = '{"class":"ERROR","message":"no response from GPS daemon"}';
  90. }
  91. }
  92. # ensure all satellites keys exist, for clean logs.
  93. function sat_clean($sat) {
  94. $skeys = array('az', 'el', 'gnssid', 'health', 'PRN', 'ss', 'svid', 'used');
  95. foreach($skeys as $key) {
  96. if (!array_key_exists($key, $sat)) {
  97. $sat[$key] = 'n/a';
  98. }
  99. }
  100. return $sat;
  101. }
  102. # format $arr[$field], adding $unit, optionally add $errkey/$errunit
  103. # otherwise return 'n/a' or 'n/a</td><td>'
  104. function field_str($arr, $key, $unit, $errkey = '', $errunit = '') {
  105. if (!array_key_exists($key, $arr)) {
  106. if ($errkey) {
  107. return 'n/a</td><td>&nbsp;';
  108. } else {
  109. return 'n/a';
  110. }
  111. }
  112. if ('' == $errunit) {
  113. $errunit = $unit;
  114. }
  115. $ret = strval($arr[$key]);
  116. if ('' != $unit) {
  117. $ret .= " " . $unit;
  118. if ($errkey) {
  119. $ret .= "</td><td>&nbsp;" ;
  120. if (array_key_exists($errkey, $arr)) {
  121. $ret .= "&plusmn;" . strval($arr[$errkey]) .
  122. "&nbsp;" . $errunit;
  123. }
  124. }
  125. }
  126. return $ret;
  127. }
  128. if ($op == 'view')
  129. gen_image($resp);
  130. else if ($op == 'json')
  131. write_json($resp);
  132. else
  133. write_html($resp);
  134. exit(0);
  135. function colorsetup($im){
  136. global $colors;
  137. $C['white'] = imageColorAllocate($im, 255, 255, 255);
  138. $C['ltgray'] = imageColorAllocate($im, 191, 191, 191);
  139. $C['mdgray'] = imageColorAllocate($im, 127, 127, 127);
  140. $C['dkgray'] = imageColorAllocate($im, 63, 63, 63);
  141. $C['black'] = imageColorAllocate($im, 0, 0, 0);
  142. $C['red'] = imageColorAllocate($im, 236, 50, 31);
  143. $C['brightgreen'] = imageColorAllocate($im, 0, 255, 0);
  144. $C['darkgreen'] = imageColorAllocate($im, 0, 192, 0);
  145. $C['blue'] = imageColorAllocate($im, 0, 0, 255);
  146. $C['cyan'] = imageColorAllocate($im, 0, 255, 255);
  147. $C['magenta'] = imageColorAllocate($im, 255, 0, 255);
  148. $C['yellow'] = imageColorAllocate($im, 255, 255, 0);
  149. $C['burntyellow'] = imageColorAllocate($im, 199, 163, 23);
  150. $C['orange'] = imageColorAllocate($im, 255, 128, 0);
  151. $colors = $C;
  152. return $C;
  153. }
  154. function legend($im, $sz, $C){
  155. $r = 30;
  156. $fn = 5;
  157. $x = $sz - (4*$r+7) - 2;
  158. $y = $sz - $r - 3;
  159. imageFilledRectangle($im, $x, $y, $x + 4*$r + 7, $y + $r +1,
  160. $C['dkgray']);
  161. imageRectangle($im, $x+0*$r+1, $y+1, $x + 1*$r + 0, $y + $r,
  162. $C['red']);
  163. imageRectangle($im, $x+1*$r+2, $y+1, $x + 2*$r + 2, $y + $r,
  164. $C['burntyellow']);
  165. imageRectangle($im, $x+2*$r+4, $y+1, $x + 3*$r + 4, $y + $r,
  166. $C['darkgreen']);
  167. imageRectangle($im, $x+4*$r+6, $y+1, $x + 3*$r + 6, $y + $r,
  168. $C['brightgreen']);
  169. imageString($im, $fn, $x+3+0*$r, $y+$r/3, "<30", $C['red']);
  170. imageString($im, $fn, $x+5+1*$r, $y+$r/3, "30+", $C['burntyellow']);
  171. imageString($im, $fn, $x+7+2*$r, $y+$r/3, "35+", $C['darkgreen']);
  172. imageString($im, $fn, $x+9+3*$r, $y+$r/3, "40+", $C['brightgreen']);
  173. }
  174. function radial($angle, $sz){
  175. #turn into radians
  176. $angle = deg2rad($angle);
  177. # determine length of radius
  178. $r = $sz * 0.5 * 0.95;
  179. # and convert length/azimuth to cartesian
  180. $x0 = sprintf("%d", (($sz * 0.5) - ($r * cos($angle))));
  181. $y0 = sprintf("%d", (($sz * 0.5) - ($r * sin($angle))));
  182. $x1 = sprintf("%d", (($sz * 0.5) + ($r * cos($angle))));
  183. $y1 = sprintf("%d", (($sz * 0.5) + ($r * sin($angle))));
  184. return array($x0, $y0, $x1, $y1);
  185. }
  186. function azel2xy($az, $el, $sz){
  187. global $swap_ew;
  188. #rotate coords... 90deg E = 180deg trig
  189. $az += 270;
  190. #turn into radians
  191. $az = deg2rad($az);
  192. # determine length of radius
  193. $r = $sz * 0.5 * 0.95;
  194. $r -= ($r * ($el/90));
  195. # and convert length/azimuth to cartesian
  196. $x = sprintf("%d", (($sz * 0.5) + ($r * cos($az))));
  197. $y = sprintf("%d", (($sz * 0.5) + ($r * sin($az))));
  198. if ($swap_ew != 0)
  199. $x = $sz - $x;
  200. return array($x, $y);
  201. }
  202. function imageCircle($im, $x, $y, $r, $color, $filled){
  203. $t = $r / 2;
  204. if ($filled) {
  205. imageFilledArc($im, $x, $y, $r, $r, 0, 360, $color, 0);
  206. } else {
  207. imageArc($im, $x, $y, $r, $r, 0, 360, $color);
  208. }
  209. }
  210. function imageDiamond($im, $x, $y, $r, $color, $filled){
  211. $t = $r / 2;
  212. $vx = array($x + $t, $y, $x, $y + $t, $x - $t, $y, $x, $y - $t,
  213. $x + $t, $y);
  214. if ($filled) {
  215. imageFilledPolygon($im, $vx, 5, $color);
  216. } else {
  217. imagepolygon($im, $vx, 5, $color);
  218. }
  219. }
  220. function imageSquare($im, $x, $y, $r, $color, $filled){
  221. global $colors;
  222. $t = $r / 2;
  223. $vx = array($x + $t, $y + $t,
  224. $x + $t, $y - $t,
  225. $x - $t, $y - $t,
  226. $x - $t, $y + $t,
  227. $x + $t, $y + $t);
  228. if ($filled) {
  229. imageFilledPolygon($im, $vx, 5, $color);
  230. } else {
  231. imagepolygon($im, $vx, 5, $color);
  232. }
  233. }
  234. # Triangle pointing down
  235. function imageTriangleD($im, $x, $y, $r, $color, $filled){
  236. $t = $r / 2;
  237. $vx = array($x, $y + $t,
  238. $x + $t, $y - $t,
  239. $x - $t, $y - $t,
  240. $x, $y + $t);
  241. if ($filled) {
  242. imageFilledPolygon($im, $vx, 4, $color);
  243. } else {
  244. imagepolygon($im, $vx, 4, $color);
  245. }
  246. }
  247. # Triangle pointing up
  248. function imageTriangleU($im, $x, $y, $r, $color, $filled){
  249. $t = $r / 2;
  250. $vx = array($x, $y - $t,
  251. $x - $t, $y + $t,
  252. $x + $t, $y + $t,
  253. $x, $y - $t);
  254. if ($filled) {
  255. imageFilledPolygon($im, $vx, 4, $color);
  256. } else {
  257. imagepolygon($im, $vx, 4, $color);
  258. }
  259. }
  260. function splot($im, $sz, $C, $e){
  261. # ensure all $e keys exist, for clean logs.
  262. $keys = array('PRN', 'az', 'el', 'gnssid', 'ss');
  263. foreach($keys as $key) {
  264. if (!array_key_exists($key, $e)) {
  265. return;
  266. }
  267. if (!is_numeric($e[$key])) {
  268. return;
  269. }
  270. }
  271. if (!array_key_exists('used', $e)) {
  272. return;
  273. }
  274. if (!array_key_exists('health', $e) ||
  275. !is_numeric($e['health'])) {
  276. $e['health'] = 0;
  277. }
  278. # validate ranges
  279. if ((0 >= $e['PRN']) || (0 > $e['az']) || (0 > $e['el']) ||
  280. (0 > $e['ss'])) {
  281. return;
  282. }
  283. $color = $C['brightgreen'];
  284. if ($e['ss'] < 40)
  285. $color = $C['darkgreen'];
  286. if ($e['ss'] < 35)
  287. $color = $C['burntyellow'];
  288. if ($e['ss'] < 30)
  289. $color = $C['red'];
  290. if ($e['el']<10)
  291. $color = $C['blue'];
  292. if ($e['ss'] < 10)
  293. $color = $C['black'];
  294. list($x, $y) = azel2xy($e['az'], $e['el'], $sz);
  295. $r = 12;
  296. if (isset($_GET['sz']) && ($_GET['sz'] == 'small'))
  297. $r = 8;
  298. imageString($im, 3, $x+4, $y+4, $e['PRN'], $C['black']);
  299. imagesetthickness($im, 2);
  300. switch ($e['gnssid']) {
  301. case 0:
  302. # GPS
  303. # FALLTHROUGH
  304. case 5:
  305. # QZSS
  306. imageCircle($im, $x, $y, $r, $color, $e['used']);
  307. break;
  308. case 1:
  309. # SBAS
  310. # FALLTHROUGH
  311. case 4:
  312. # IMES
  313. # FALLTHROUGH
  314. default:
  315. imageDiamond($im, $x, $y, $r, $color, $e['used']);
  316. break;
  317. case 2:
  318. # Galileo
  319. imageTriangleU($im, $x, $y, $r, $color, $e['used']);
  320. break;
  321. case 3:
  322. # BeiDou
  323. imageTriangleD($im, $x, $y, $r, $color, $e['used']);
  324. break;
  325. case 6:
  326. # GLONASS
  327. imageSquare($im, $x, $y, $r, $color, $e['used']);
  328. break;
  329. }
  330. }
  331. function elevation($im, $sz, $C, $a){
  332. $b = 90 - $a;
  333. $a = $sz * 0.95 * ($a/180);
  334. imageArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['ltgray']);
  335. $x = $sz/2 - 16;
  336. $y = $sz/2 - $a;
  337. imageString($im, 2, $x, $y, $b, $C['ltgray']);
  338. }
  339. function skyview($im, $sz, $C){
  340. global $swap_ew;
  341. $a = 90; $a = $sz * 0.95 * ($a/180);
  342. imageFilledArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['mdgray'], 0);
  343. imageArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['black']);
  344. $x = $sz/2 - 16; $y = $sz/2 - $a;
  345. imageString($im, 2, $x, $y, "0", $C['ltgray']);
  346. $a = 85; $a = $sz * 0.95 * ($a/180);
  347. imageFilledArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['white'], 0);
  348. imageArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['ltgray']);
  349. imageString($im, 1, $sz/2 - 6, $sz+$a, '5', $C['black']);
  350. $x = $sz/2 - 16; $y = $sz/2 - $a;
  351. imageString($im, 2, $x, $y, "5", $C['ltgray']);
  352. for($i = 0; $i < 180; $i += 15){
  353. list($x0, $y0, $x1, $y1) = radial($i, $sz);
  354. imageLine($im, $x0, $y0, $x1, $y1, $C['ltgray']);
  355. }
  356. for($i = 15; $i < 90; $i += 15)
  357. elevation($im, $sz, $C, $i);
  358. $x = $sz/2 - 16; $y = $sz/2 - 8;
  359. /* imageString($im, 2, $x, $y, "90", $C['ltgray']); */
  360. imageString($im, 4, $sz/2 + 4, 2 , 'N', $C['black']);
  361. imageString($im, 4, $sz/2 + 4, $sz - 16 , 'S', $C['black']);
  362. if ($swap_ew != 0){
  363. imageString($im, 4, 4 , $sz/2 + 4, 'E', $C['black']);
  364. imageString($im, 4, $sz - 10 , $sz/2 + 4, 'W', $C['black']);
  365. } else {
  366. imageString($im, 4, 4 , $sz/2 + 4, 'W', $C['black']);
  367. imageString($im, 4, $sz - 10 , $sz/2 + 4, 'E', $C['black']);
  368. }
  369. }
  370. function gen_image($resp){
  371. $sz = 600;
  372. if (isset($_GET['sz']) && ($_GET['sz'] == 'small'))
  373. $sz = 240;
  374. $GPS = json_decode($resp, true);
  375. if ($GPS['class'] != "POLL"){
  376. die("json_decode error: $resp");
  377. }
  378. $im = imageCreate($sz, $sz);
  379. $C = colorsetup($im);
  380. skyview($im, $sz, $C);
  381. if (240 < $sz)
  382. legend($im, $sz, $C);
  383. if (array_key_exists('sky', $GPS) &&
  384. array_key_exists(0, $GPS['sky']) &&
  385. array_key_exists('satellites', $GPS['sky'][0])) {
  386. for($i = 0; $i < count($GPS['sky'][0]['satellites']); $i++){
  387. $sat = sat_clean($GPS['sky'][0]['satellites'][$i]);
  388. splot($im, $sz, $C, $sat);
  389. }
  390. }
  391. header("Content-type: image/png");
  392. imagePNG($im);
  393. imageDestroy($im);
  394. }
  395. function dfix($x, $y, $z){
  396. if ($x < 0){
  397. $x = sprintf("%f %s", -1 * $x, $z);
  398. } else {
  399. $x = sprintf("%f %s", $x, $y);
  400. }
  401. return $x;
  402. }
  403. # compare sats for sort. used at top.
  404. function sat_cmp($a, $b) {
  405. if ($b['used'] != $a['used']) {
  406. # used Y before used N
  407. return $b['used'] - $a['used'];
  408. }
  409. return $a['PRN'] - $b['PRN'];
  410. }
  411. function write_html($resp) {
  412. global $sock, $errstr, $errno, $server, $port, $head, $body, $open;
  413. global $blurb, $title, $autorefresh, $showmap, $gmap_key, $footer;
  414. global $testmode, $advertise;
  415. $GPS = json_decode($resp, true);
  416. if ($GPS['class'] != 'POLL'){
  417. die("json_decode error: $resp");
  418. }
  419. header("Content-type: text/html; charset=UTF-8");
  420. global $lat, $lon;
  421. # make sure some things exist
  422. if (!array_key_exists('sky', $GPS)) {
  423. $GPS['sky'] = array();
  424. }
  425. if (!array_key_exists(0, $GPS['sky'])) {
  426. $GPS['sky'][0] = array();
  427. }
  428. if (!array_key_exists('satellites', $GPS['sky'][0])) {
  429. $GPS['sky'][0]['satellites'] = array();
  430. }
  431. if (!array_key_exists('tpv', $GPS)) {
  432. $GPS['tpv'] = array();
  433. }
  434. if (!array_key_exists(0, $GPS['tpv'])) {
  435. $GPS['tpv'][0] = array();
  436. }
  437. if (!array_key_exists('lat', $GPS['tpv'][0]) ||
  438. !array_key_exists('lon', $GPS['tpv'][0])) {
  439. $GPS['tpv'][0]['lat'] = 0.0;
  440. $GPS['tpv'][0]['lon'] = 0.0;
  441. }
  442. if (!array_key_exists('mode', $GPS['tpv'][0])) {
  443. $GPS['tpv'][0]['mode'] = 0;
  444. }
  445. if (!array_key_exists('time', $GPS['tpv'][0])) {
  446. $GPS['tpv'][0]['time'] = 0;
  447. }
  448. $lat = (float)$GPS['tpv'][0]['lat'];
  449. $lon = (float)$GPS['tpv'][0]['lon'];
  450. $x = $server; $y = $port;
  451. $imgdata = base64_encode($resp);
  452. $server = $x; $port = $y;
  453. if ($autorefresh > 0)
  454. $autorefresh = "<meta http-equiv='Refresh' content='$autorefresh'/>";
  455. else
  456. $autorefresh = '';
  457. $map_head = $map_body = $map_code = '';
  458. if ($showmap == 1) {
  459. $map_head = gen_gmap_head();
  460. $map_body = 'onload="Load()" onunload="GUnload()"';
  461. $map_code = gen_map_code();
  462. } else if ($showmap == 2) {
  463. $map_head = gen_osm_head();
  464. $map_body = '';
  465. $map_code = gen_osmmap_code();
  466. }
  467. $part_header = <<<EOF
  468. <!DOCTYPE html>
  469. <html lang="en">
  470. <head>
  471. {$head}
  472. {$map_head}
  473. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  474. <title>{$title} - GPSD Test Station {$lat}, {$lon}</title>
  475. {$autorefresh}
  476. <style>
  477. blink,
  478. .warning {
  479. color: #dc322f;
  480. }
  481. .fixed {
  482. font-family: mono-space;
  483. }
  484. .caption {
  485. text-align: left;
  486. margin: 1ex 1em 1ex 1em; /* top right bottom left */
  487. }
  488. .administrivia {
  489. font-size: small;
  490. font-family: verdana, sans-serif;
  491. }
  492. body.light {
  493. background-color: #fdf6e3;
  494. color: #657b83;
  495. }
  496. body.dark {
  497. background-color: #002b36;
  498. color: #839496;
  499. }
  500. td:nth-child(1) {
  501. text-align: right;
  502. font-weight: bold;
  503. }
  504. </style>
  505. </head>
  506. <body {$body} {$map_body}>
  507. <div id="main" style="clear:both">
  508. <div>
  509. {$blurb}
  510. <br>
  511. </div>
  512. EOF;
  513. if (!strlen($advertise))
  514. $advertise = $server;
  515. if ($testmode && !$sock)
  516. $part_sky = "";
  517. else
  518. $part_sky = <<<EOF
  519. <!-- -->
  520. <div style="width:600px;float:right;margin:0 0 1ex 1em;">
  521. <img src="?op=view&amp;imgdata={$imgdata}"
  522. width="600" height="600" alt="Skyview"/>
  523. <br style="clear:both">
  524. <p class="caption">A filled shape means the satellite was used in
  525. the last fix.<br>
  526. Green-yellow-red colors indicate signal to noise ratio in dBHz<br>
  527. <span style="color:green;">green=best</span>,
  528. <span style="color:#c7a317;">yellow=fair</span>,
  529. and <span style="color:#dc322f">red=worst</span>.<br>
  530. Circles are GPS and QZSS satellites.<br>
  531. Diamonds indicate augmentation (SBAS, WAAS, etc.) satellites.<br>
  532. Triangles pointing up are Galileo satellites.<br>
  533. Triangles pointing down are BeiDou satellites.<br>
  534. Squares are GLONASS satellites.<br>
  535. </p>
  536. {$map_code}
  537. </div>
  538. EOF;
  539. if ($open)
  540. $part3 = <<<EOF
  541. <!-- -->
  542. <div>To get real-time information, connect to
  543. <span class="fixed">telnet://{$advertise}:{$port}/</span> and type "?POLL;"
  544. or "?WATCH={"enable":true,"raw":true}".<br/>
  545. Use a different server:<br/>
  546. <form method=GET action="${_SERVER['SCRIPT_NAME']}">
  547. <input name="host" value="{$advertise}">:
  548. <input name="port" value="{$port}" size="5" maxlength="5">
  549. <input type=submit value="Get Position"><input type=reset></form>
  550. <br/>
  551. </div>
  552. EOF;
  553. else
  554. $part3 = '';
  555. if ($testmode && !$sock)
  556. $part_tpv_sky = <<<EOF
  557. <div style="clear:both">The gpsd instance that this page monitors is
  558. not running.</div>
  559. EOF;
  560. else {
  561. $fix = $GPS['tpv'][0];
  562. $sky = $GPS['sky'][0];
  563. $sats = $sky['satellites'];
  564. $fixtype = array('Unknown' => 0, 'No Fix' => 1, '2D Fix' => 2,
  565. '3D Fix' => 3);
  566. $type = array_search($fix['mode'], $fixtype);
  567. $nsv = count($sats);
  568. $ts = $fix['time'];
  569. $sat = '';
  570. # gnssid to gnss abbreviation
  571. $gnss = array(0 => 'GP', 1 => 'SB', 2 => 'GA', 3 => 'BD',
  572. 4 => 'IM', 5 => 'QZ', 6 => 'GL');
  573. # sort sats
  574. usort($sats, "sat_cmp");
  575. $sats_used = 0;
  576. foreach($sats as $s) {
  577. $s = sat_clean($s);
  578. if (array_key_exists($s['gnssid'], $gnss)) {
  579. $s['gnssid'] = $gnss[$s['gnssid']];
  580. } else {
  581. $s['gnssid'] = ' ';
  582. }
  583. if ($s['used']) {
  584. $sats_used += 1;
  585. }
  586. if (2 == (int)$s['health']) {
  587. $used = $s['used'] ? 'uY&nbsp;' : 'uN&nbsp; ';
  588. } else {
  589. $used = $s['used'] ? '&nbsp;Y&nbsp;' : '&nbsp;N&nbsp; ';
  590. }
  591. $sat .= sprintf(
  592. "\t<tr style='text-align:right'><td>%s</td>" .
  593. "<td>%d</td><td>%d&nbsp;</td><td>%d&nbsp;</td>" .
  594. "<td>%d</td><td>%s</td></tr>\n",
  595. $s['gnssid'] . $s['svid'],
  596. $s['PRN'], $s['el'], $s['az'], $s['ss'],
  597. $used
  598. );
  599. };
  600. # ensure all $fix keys exist, for clean logs.
  601. $fixkeys = array('lat', 'leapseconds', 'lon');
  602. foreach($fixkeys as $key) {
  603. if (!array_key_exists($key, $fix)) {
  604. $fix[$key] = 'n/a';
  605. }
  606. }
  607. # ensure all $sky keys exist, for clean logs.
  608. $skykeys = array('gdop', 'hdop', 'pdop','tdop', 'vdop',
  609. 'xdop', 'ydop');
  610. foreach($skykeys as $key) {
  611. if (!array_key_exists($key, $sky)) {
  612. $sky[$key] = 'n/a';
  613. }
  614. }
  615. $lat = field_str($fix, 'lat', '&deg;', 'epx', 'm');
  616. $lon = field_str($fix, 'lon', '&deg;', 'epy', 'm');
  617. $altHAE = field_str($fix, 'altHAE', 'm', 'epv');
  618. $altMSL = field_str($fix, 'altMSL', 'm');
  619. $geoidSep = field_str($fix, 'geoidSep', 'm');
  620. $speed = field_str($fix, 'speed', 'm/s', 'eps');
  621. $climb = field_str($fix, 'climb', 'm/s', 'epc');
  622. $velN = field_str($fix, 'velN', 'm/s');
  623. $velE = field_str($fix, 'velE', 'm/s');
  624. $velD = field_str($fix, 'velD', 'm/s');
  625. $track = field_str($fix, 'track', '&deg;', 'epd');
  626. $magtrack = field_str($fix, 'magtrack', '&deg;');
  627. $magvar = field_str($fix, 'magvar', '&deg;');
  628. $ecefx = field_str($fix, 'ecefx', 'm');
  629. $ecefy = field_str($fix, 'ecefy', 'm');
  630. $ecefz = field_str($fix, 'ecefz', 'm', 'ecefpAcc');
  631. $ecefvx = field_str($fix, 'ecefvx', 'm/s');
  632. $ecefvy = field_str($fix, 'ecefvy', 'm/s');
  633. $ecefvz = field_str($fix, 'ecefvz', 'm/s', 'ecefvAcc');
  634. $epc = field_str($fix, 'epc', 'm/s');
  635. $eph = field_str($fix, 'eph', 'm');
  636. $eps = field_str($fix, 'eps', 'm/s');
  637. $ept = field_str($fix, 'ept', 's');
  638. $epx = field_str($fix, 'epx', 'm');
  639. $epy = field_str($fix, 'epy', 'm');
  640. $epv = field_str($fix, 'epv', 'm');
  641. $sep = field_str($fix, 'sep', 'm');
  642. $ecefpAcc = field_str($fix, 'ecefpAcc', 'm');
  643. $ecefvAcc = field_str($fix, 'ecefvAcc', 'm');
  644. $sep = field_str($fix, 'sep', 'm');
  645. if (!array_key_exists('status', $fix)) {
  646. $fix['status'] = 1;
  647. }
  648. switch($fix['status']) {
  649. case 0:
  650. $status = 'No Fix';
  651. break;
  652. case 1:
  653. $status = 'Normal Fix';
  654. break;
  655. case 2:
  656. $status = 'DGPS Fix';
  657. break;
  658. case 3:
  659. $status = 'RTK Fixed Fix';
  660. break;
  661. case 4:
  662. $status = 'RTK Float Fix';
  663. break;
  664. case 5:
  665. $status = 'Dead Reckoning';
  666. break;
  667. case 6:
  668. $status = 'GNSS+DR Fix';
  669. break;
  670. case 7:
  671. $status = 'Surveyed-In Fix';
  672. break;
  673. case 8:
  674. $status = 'Simulated Fix';
  675. break;
  676. default:
  677. $status = 'Unknown Fix Type';
  678. break;
  679. }
  680. $part_tpv_sky = <<<EOF
  681. <!-- -->
  682. <div style="float:left;margin:0 0 1ex 1em;">
  683. <table style="border-width:1px;border-style:solid;text-align:center;">
  684. <tr><th colspan=3 style="text-align:center">Fix Data</th></tr>
  685. <tr><td>Fix Type</td><td>{$type}</td></tr>
  686. <tr><td>Fix Status</td><td>{$status}</td></tr>
  687. <tr><th colspan=3 style="text-align:center">Time</th></tr>
  688. <tr><td>UTC</td><td colspan=2>{$ts}&nbsp;</td></tr>
  689. <tr><td>Leap Seconds</td><td>{$fix['leapseconds']}</td></tr>
  690. <tr><th colspan=3 style="text-align:center">Position</th></tr>
  691. <tr><td>Latitude</td><td>{$lat}</td></tr>
  692. <tr><td>Longitude</td><td>{$lon}</td></tr>
  693. <tr title="Height Above Ellipsoid. Typically WGS84">
  694. <td>Altitude HAE</td><td>{$altHAE}</td></tr>
  695. <tr title="Height Above Mean Sea Level">
  696. <td>Altitude MSL</td><td>{$altMSL}</td></tr>
  697. <tr title="HAE - MSL"><td>Geoid Separation</td><td>{$geoidSep}</td></tr>
  698. <tr><th colspan=3 style="text-align:center">Velocity</th></tr>
  699. <tr title="Horizontal Velocity"><td>Speed</td><td>{$speed}</td></tr>
  700. <tr title="Vertical Velocity"><td>Climb</td><td>{$climb}</td></tr>
  701. <tr title="Velocity North"><td>velN</td><td>{$velN}</td></tr>
  702. <tr title="Velocity East"><td>velE</td><td>{$velE}</td></tr>
  703. <tr title="Velocity Down"><td>velD</td><td>{$velD}</td></tr>
  704. <tr><td>Track True</td><td>{$track}</td></tr>
  705. <tr><td>Track Magnetic</td><td>{$magtrack}</td></tr>
  706. <tr><td>Magnetic Variation</td><td>{$magvar}</td></tr>
  707. </table>
  708. </div>
  709. <div style="float:left;margin:0 0 1ex 1em;">
  710. <table style="border-width:1px;border-style:solid;text-align:center;">
  711. <tr title="Dimentionless Ratios">
  712. <th colspan=2 style="text-align:center">Dilution of Precision
  713. </th></tr>
  714. <tr title="Geometric Dilution Of Precision"><td>GDOP
  715. </td><td>{$sky['gdop']}</td></tr>
  716. <tr title="Horizontal (2D) Dilution Of Precision"><td>HDOP</td>
  717. <td>{$sky['hdop']}</td></tr>
  718. <tr title="Position (3D) Dilution Of Precision"><td>PDOP
  719. </td><td>{$sky['pdop']}</td></tr>
  720. <tr title="Time Dilution Of Precision"><td>TDOP</td>
  721. <td>{$sky['tdop']}</td></tr>
  722. <tr title="X (Longitude) Dilution Of Precision"><td>XDOP
  723. </td><td>{$sky['xdop']}</td></tr>
  724. <tr title="Y (Latitude) Dilution Of Precision"><td>YDOP
  725. </td><td>{$sky['ydop']}</td></tr>
  726. <tr title="Velocity Dilution Of Precision"><td>VDOP</td>
  727. <td>{$sky['vdop']}</td></tr>
  728. </table>
  729. </div>
  730. <div style="float:left;margin:0 0 1ex 1em;">
  731. <table style="border-width:1px;border-style:solid;text-align:center;">
  732. <tr title="Unknown/undefined uncertainty">
  733. <th colspan=2 style="text-align:center">Error Estimates</th></tr>
  734. <tr title="Estimated Precision of Climb"><td>epc</td>
  735. <td>{$epc}</td></tr>
  736. <tr title="Estimated Precision 2D"><td>eph</td>
  737. <td>{$eph}</td></tr>
  738. <tr title="Estimated Precision Speed"><td>eps</td>
  739. <td>{$eps}</td></tr>
  740. <tr title="Estimated Precision Time"><td>ept</td><td>{$ept}</td></tr>
  741. <tr title="Estimated Precision X (Longitude)"><td>epx</td>
  742. <td>{$epx}</td></tr>
  743. <tr title="Estimated Precision Y (Latitude)"><td>epy</td>
  744. <td>{$epy}</td></tr>
  745. <tr title="Estimated Precision Vertical"><td>epv</td>
  746. <td>{$epv}</td></tr>
  747. <tr title="Spherical Error Probability (epe)"><td>sep</td>
  748. <td>{$sep}</td></tr>
  749. <tr title="Estimated ECEF Position Accuracy"><td>ecef pAcc</td>
  750. <td>{$ecefpAcc}</td></tr>
  751. <tr title="Estimated ECEF Velocity Accuracy"><td>ecef vAcc</td>
  752. <td>{$ecefvAcc}</td></tr>
  753. </table>
  754. </div>
  755. <div style="float:right;margin:0 0 1ex 1em;">
  756. <table style="border-width:1px;border-style:solid">
  757. <tr><th colspan=6 style="text-align:center">Satellites</th></tr>
  758. <tr><td colspan=6 style="text-align:center"
  759. >Seen {$nsv}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Used {$sats_used}
  760. </td></tr>
  761. <tr><th></th><th>PRN</th>
  762. <th>Elv</th><th>Azm</th>
  763. <th>SNR</th><th>Used</th>
  764. </tr>
  765. $sat </table>
  766. </div>
  767. <div style="float:left;margin:0 0 1ex 1em;">
  768. <table style="border-width:1px;border-style:solid;text-align:center;">
  769. <tr><th colspan=5 style="text-align:center"
  770. >Earth Centered Earth Fixed (ECEF)</th></tr>
  771. <tr><th></th><th>X</th><th>Y</th><th>Z</th><th>Acc</th></td>
  772. <tr><td>Position</td>
  773. <td>{$ecefx}</td><td>{$ecefy}</td> <td>{$ecefz}</td></tr>
  774. <tr><td>Velocity</td>
  775. <td>{$ecefvx}</td><td>{$ecefvy}</td> <td>{$ecefvz}</td></tr>
  776. </table>
  777. </div>
  778. <!-- raw response:
  779. {$resp}
  780. -->
  781. EOF;
  782. }
  783. $part_footer = <<<EOF
  784. </div> <!-- end div main -->
  785. <div style="clear:both">
  786. <br>
  787. <hr>
  788. {$footer}
  789. <p class="administrivia">This script is distributed by the
  790. <a href="@WEBSITE@">GPSD project</a>.</p>
  791. </div>
  792. </body>
  793. </html>
  794. EOF;
  795. print $part_header . $part_sky . $part3 . $part_tpv_sky . $part_footer;
  796. }
  797. function write_json($resp){
  798. header('Content-Type: text/javascript');
  799. if (isset($_GET['jsonp']))
  800. print "{$_GET['jsonp']}({$resp})";
  801. else
  802. print $resp;
  803. }
  804. function write_config(){
  805. $f = fopen("gpsd_config.inc", "a");
  806. if (!$f)
  807. die("can't generate prototype config file. try running this script as root in DOCUMENT_ROOT");
  808. $buf = <<<EOB
  809. <?PHP
  810. \$title = 'My GPS Server';
  811. \$server = 'localhost';
  812. #\$advertise = 'localhost';
  813. \$port = 2947;
  814. \$autorefresh = 0; # number of seconds after which to refresh
  815. # set to 1 if you want to have a google map,
  816. # set it to 2 if you want a map based on opemstreetmap/openlayers (osm)
  817. \$showmap = 0;
  818. \$gmap_key = 'GetYourOwnGoogleKey'; # your google API key goes here
  819. \$swap_ew = 0; # set to 1 for upward facing view (nonstandard)
  820. \$open = 0; # set to 1 to show the form to change the GPSd server
  821. ## You can read the header, footer and blurb from a file...
  822. # \$head = file_get_contents('/path/to/header.inc');
  823. # \$body = file_get_contents('/path/to/body.inc');
  824. # \$footer = file_get_contents('/path/to/footer.hinc');
  825. # \$blurb = file_get_contents('/path/to/blurb.inc');
  826. ## ... or you can just define them here
  827. \$head = '';
  828. \$body = '';
  829. \$footer = '';
  830. \$blurb = <<<EOT
  831. This is a
  832. <a href="@WEBSITE@">gpsd</a>
  833. server <blink>located someplace</blink>.
  834. The hardware is a
  835. <blink>hardware description and link</blink>.
  836. This machine is maintained by
  837. <a href="mailto:you@example.com">Your Name Goes Here</a>.<br/>
  838. EOT;
  839. ?>
  840. EOB;
  841. fwrite($f, $buf);
  842. fclose($f);
  843. }
  844. function gen_gmap_head() {
  845. global $gmap_key;
  846. return <<<EOT
  847. <script src="//maps.googleapis.com/maps/api/js?sensor=false"/>
  848. <script>
  849. <!-- note that the google map API is commented out, it requires
  850. an API KEY, and costs money to use! As of March 2020 more info
  851. here: https://developers.google.com/maps/gmp-get-started -->
  852. <!--
  853. function Load() {
  854. var map = new google.maps.Map(
  855. document.getElementById('map'), {
  856. center: new google.maps.LatLng({$GLOBALS['lat']}, {$GLOBALS['lon']}),
  857. zoom: 13,
  858. mapTypeId: google.maps.MapTypeId.ROADMAP
  859. });
  860. var marker = new google.maps.Marker({
  861. position: new google.maps.LatLng({$GLOBALS['lat']}, {$GLOBALS['lon']}),
  862. map: map
  863. });
  864. }
  865. google.maps.event.addDomListener(window, 'load', initialize);
  866. -->
  867. </script>
  868. EOT;
  869. }
  870. # example code from: https://openlayers.org/en/latest/doc/quickstart.html
  871. function gen_osm_head() {
  872. global $GPS;
  873. return <<< EOT
  874. <link rel="stylesheet"
  875. href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/css/ol.css"
  876. type="text/css">
  877. <script
  878. src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/build/ol.js"></script>
  879. EOT;
  880. }
  881. function gen_osmmap_code() {
  882. return <<<EOT
  883. <br>
  884. <div id="map" style="height:400px;"></div>
  885. <noscript>
  886. <span class='warning'>Sorry: you must enable javascript to view our
  887. maps.</span><br/>
  888. </noscript>
  889. <script>
  890. var LonLat = ol.proj.fromLonLat([{$GLOBALS['lon']}, {$GLOBALS['lat']}])
  891. var stroke = new ol.style.Stroke({color: 'red', width: 2});
  892. var feature = new ol.Feature(new ol.geom.Point(LonLat))
  893. var x = new ol.style.Style({
  894. image: new ol.style.RegularShape({
  895. stroke: stroke,
  896. points: 4,
  897. radius: 10,
  898. radius2: 0,
  899. angle: 0.785397 // Pi / 4
  900. })
  901. })
  902. feature.setStyle(x)
  903. var source = new ol.source.Vector({
  904. features: [feature]
  905. });
  906. var vectorLayer = new ol.layer.Vector({
  907. source: source
  908. });
  909. var map = new ol.Map({
  910. target: 'map',
  911. layers: [
  912. new ol.layer.Tile({
  913. source: new ol.source.OSM()
  914. }),
  915. vectorLayer
  916. ],
  917. view: new ol.View({
  918. center: LonLat,
  919. zoom: 6
  920. })
  921. });
  922. </script>
  923. EOT;
  924. }
  925. function gen_map_code() {
  926. return <<<EOT
  927. <br>
  928. <div id="map"
  929. style="width:550px;height:400px;border:1px;border-style:solid;float:left;">
  930. Loading...
  931. <noscript>
  932. <span class='warning'>Sorry: you must enable javascript to view our
  933. maps.</span><br/>
  934. </noscript>
  935. </div>
  936. EOT;
  937. }
  938. ?>