<?php

# Copyright (c) 2006,2007 Chris Kuethe <chris.kuethe@gmail.com>
# Updated 2015 by Sanjeev Gupta <ghane0@gmail.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# This file is Copyright (c) 2010-2018 by the GPSD project
# SPDX-License-Identifier: BSD-2-clause

// This program originally read a logfile of filtered gpsd messages,
// type Y.  The gpsd protocal changed in 2.90, since when this became
// non-functional.
//
// The program has been updated (the first while loop) to read messages
// over tcp; of type SKY.  These are unpacked from JSON.  No attempt has
// been made to touch the actual calculation or plotting routines.
//
// Because it now reads a live stream, the program must be run with an
// option, "count", to specify the number of SKY messages it reads.  SKY
// messages are usually emitted every 5 secs, so a number close to 700
// will cover an hour's worth.
//
// Tested to work with php5.6 , although earlier versions may work.


$cellmode = 0;
if ($argc != 3){
	if (($argc != 4) || strcmp("cells", $argv[3])){
		die("usage: ${argv[0]} count imagefile.png [cells]\n");
	} else {
		$cellmode = 1;
	}
}

// How many samples to read of SKY messages.
$count = $argv[1] ;

$sz = 640;
$cellsize = 5; # degrees
$radius = 8; # pixels
$filled = 0;

$im = imageCreate($sz, $sz);
$C = colorsetup($im);
skyview($im, $sz, $C);
legend($im, $sz, $C);

$sky = array();

error_reporting(E_ALL);

// Get the port for the GPSD service.
$service_port = 2947 ;
// Get the IP address for the target host. 
$address = "127.0.0.1" ;
// Create a TCP/IP socket.
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
}

$result = socket_connect($socket, $address, $service_port);
if ($result === false) {
    echo "socket_connect() failed.\nReason: ($result) " . socket_strerror(socket_last_error($socket)) . "\n";
}

// Send a WATCH command.
$cmd = "?WATCH={\"enable\":true,\"json\":true};" ;

socket_write($socket, $cmd, strlen($in));

// Start the loop to start reading from gpsd.
$out = '';
$j = 0 ;
while (($out = socket_read($socket, 2048)) && ( $j < $count) ) {

	if (strpos($out, "SKY")) {
		$j = $j + 1;
		$PRN = json_decode($out,true);
// var_dump($PRN) ;
// object(stdClass)#12 (5)
//       ["PRN"]=>
//       int(137)
//       ["el"]=>
//       int(42)
//       ["az"]=>
//       int(91)
//       ["ss"]=>
//       int(32)
//       ["used"]=>
//       bool(false)

		$n =  count($PRN["satellites"]) ;
		for($i = 0; $i < $n; $i++) {
        		$sv = $PRN["satellites"][$i]["PRN"] ;
        		$el = $PRN["satellites"][$i]["el"] ;
        		$az = $PRN["satellites"][$i]["az"] ;
        		$snr = $PRN["satellites"][$i]["ss"] ;
        		$u = $PRN["satellites"][$i]["used"] ;

// Below this, Chris' original code, more or less. -- Sanjeev 20150326

			if ($cellmode){
				$az = $cellsize * (int)($az/$cellsize);
				$el = $cellsize * (int)($el/$cellsize);
			}
			if (isset($sky[$az][$el]['avg'])){
				$sky[$az][$el]['snr'] += $snr;
				$sky[$az][$el]['num']++;
			} else {
				$sky[$az][$el]['snr'] = $snr;
				$sky[$az][$el]['num'] = 1;
			}
			$sky[$az][$el]['avg'] = $sky[$az][$el]['snr'] / $sky[$az][$el]['num'];
		}
	}

}


foreach($sky as $az => $x){
	foreach ($sky[$az] as $el => $y){
		$e = array(-1, $el, $az, $sky[$az][$el]['avg'], -1);
		if ($cellmode)
			cellplot($im, $sz, $C, $cellsize, $e);
		else
			splot($im, $sz, $C, $radius, $filled, $e);
	}
}


skygrid($im, $sz, $C);	# redraw grid over satellites
imagePNG($im, $argv[2]);
imageDestroy($im);

exit(0);

###########################################################################
function colorsetup($im){
	$C['white']	= imageColorAllocate($im, 255, 255, 255);
	$C['ltgray']	= imageColorAllocate($im, 191, 191, 191);
	$C['mdgray']	= imageColorAllocate($im, 127, 127, 127);
	$C['dkgray']	= imageColorAllocate($im, 63, 63, 63);
	$C['black']	= imageColorAllocate($im, 0, 0, 0);
	$C['red']	= imageColorAllocate($im, 255, 0, 0);
	$C['brightgreen'] = imageColorAllocate($im, 0, 255, 0);
	$C['darkgreen']	= imageColorAllocate($im, 0, 192, 0);
	$C['blue']	= imageColorAllocate($im, 0, 0, 255);
	$C['cyan']	= imageColorAllocate($im, 0, 255, 255);
	$C['magenta']	= imageColorAllocate($im, 255, 0, 255);
	$C['yellow']	= imageColorAllocate($im, 255, 255, 0);
	$C['orange']	= imageColorAllocate($im, 255, 128, 0);

	return $C;
}

function legend($im, $sz, $C){
	$r = 30;
	$fn = 5;
	$x = $sz - (4*$r+7) - 2;
	$y = $sz - $r - 3;

	imageFilledRectangle($im, $x, $y, $x + 4*$r + 7, $y + $r +1, $C['dkgray']);
	imageRectangle($im, $x+0*$r+1, $y+1, $x + 1*$r + 0, $y + $r, $C['red']);
	imageRectangle($im, $x+1*$r+2, $y+1, $x + 2*$r + 2, $y + $r, $C['yellow']);
	imageRectangle($im, $x+2*$r+4, $y+1, $x + 3*$r + 4, $y + $r, $C['darkgreen']);
	imageRectangle($im, $x+4*$r+6, $y+1, $x + 3*$r + 6, $y + $r, $C['brightgreen']);
	imageString($im, $fn, $x+3+0*$r, $y+$r/3, "<30", $C['red']);
	imageString($im, $fn, $x+5+1*$r, $y+$r/3, "30+", $C['yellow']);
	imageString($im, $fn, $x+7+2*$r, $y+$r/3, "35+", $C['darkgreen']);
	imageString($im, $fn, $x+9+3*$r, $y+$r/3, "40+", $C['brightgreen']);
}

function radial($angle, $sz){
	#turn into radians
	$angle = deg2rad($angle);

	# determine length of radius
	$r = $sz * 0.5 * 0.95;

	# and convert length/azimuth to cartesian
	$x0 = sprintf("%d", (($sz * 0.5) - ($r * cos($angle))));
	$y0 = sprintf("%d", (($sz * 0.5) - ($r * sin($angle))));
	$x1 = sprintf("%d", (($sz * 0.5) + ($r * cos($angle))));
	$y1 = sprintf("%d", (($sz * 0.5) + ($r * sin($angle))));

	return array($x0, $y0, $x1, $y1);
}

function azel2xy($az, $el, $sz){
	#rotate coords... 90deg W = 180deg trig
	$az += 270;

	#turn into radians
	$az = deg2rad($az);

	# determine length of radius
	$r = $sz * 0.5 * 0.95;
	$r -= ($r * ($el/90));

	# and convert length/azimuth to cartesian
	$x = sprintf("%d", (($sz * 0.5) + ($r * cos($az))));
	$y = sprintf("%d", (($sz * 0.5) + ($r * sin($az))));
	$x = $sz - $x;

	return array($x, $y);
}

function cellplot($im, $sz, $C, $cellsize, $e){
	list($sv, $el, $az, $snr, $u) = $e;

	if ((0 == $sv) || (0 == $az + $el + $snr) ||
	    ($az < 0) || ($el < 0))
		return;

	$color = $C['brightgreen'];
	if ($snr < 40)
		$color = $C['darkgreen'];
	if ($snr < 35)
		$color = $C['yellow'];
	if ($snr < 30)
		$color = $C['red'];
	if ($snr < 15)
		$color = $C['dkgray'];

	#consider an N-degree cell plotted at (0,0). its top left corner
	#will be (0,0) and its bottom right corner will be at (N,N). The
	#sides are straight lines from (0,0)-(0,N) and (N,0)-(N,N). The
	#top and bottom edges will be approximated by segments from
	#(0,0):(0,1)..(0,N-1):(0,N) and (N,0):(N,1)...(N,N-1):(N,N).
	#Plotting that unholy mess is the job of
	# imagefilledpolygon ( $image, array $points, $num_points, $color )

	$np = 0;
	$points = array();
	for($x = $az; $x <= $az+$cellsize; $x++){
		list($px,$py) = azel2xy($x, $el, $sz);
		array_push($points, $px, $py);
		$np++;
	}
	for($x = $az+$cellsize; $x >= $az; $x--){
		list($px,$py) = azel2xy($x, $el+$cellsize, $sz);
		array_push($points, $px, $py);
		$np++;
	}
	list($px,$py) = azel2xy($az, $el, $sz);
	array_push($points, $px, $py);
	$np++;

	if ($snr > 0)
		imageFilledPolygon($im, $points, $np, $color);
}

function splot($im, $sz, $C, $r, $filled, $e){
	list($sv, $az, $el, $snr, $u) = $e;

	if ((0 == $sv) || (0 == $az + $el + $snr))
		return;

	$color = $C['brightgreen'];
	if ($snr < 40)
		$color = $C['darkgreen'];
	if ($snr < 35)
		$color = $C['yellow'];
	if ($snr < 30)
		$color = $C['red'];
	if ($snr == 0)
		$color = $C['black'];

	list($x, $y) = azel2xy($el, $az, $sz);

	if ($snr > 0){
		if ($filled)
			imageFilledArc($im, $x, $y, $r, $r, 0, 360, $color, 0);
		else
			imageArc($im, $x, $y, $r, $r, 0, 360, $color);
	}
}

function elevation($im, $sz, $C, $a){
	$b = 90 - $a;
	$a = $sz * 0.95 * ($a/180);
	imageArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['ltgray']);
	$x = $sz/2 - 16;
	$y = $sz/2 - $a;
	imageString($im, 2, $x, $y, $b, $C['ltgray']);
}

function skyview($im, $sz, $C){
	$a = 90; $a = $sz * 0.95 * ($a/180);
	imageFilledArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['mdgray'], 0);
	imageArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['black']);
	$x = $sz/2 - 16; $y = $sz/2 - $a;
	imageString($im, 2, $x, $y, "0", $C['ltgray']);

	$a = 85; $a = $sz * 0.95 * ($a/180);
	imageFilledArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['white'], 0);
	imageArc($im, $sz/2, $sz/2, $a*2, $a*2, 0, 360, $C['ltgray']);
	imageString($im, 1, $sz/2 - 6, $sz+$a, '5', $C['black']);
	$x = $sz/2 - 16; $y = $sz/2 - $a;
	imageString($im, 2, $x, $y, "5", $C['ltgray']);

	skygrid($im, $sz, $C);
	$x = $sz/2 - 16; $y = $sz/2 - 8;
	/* imageString($im, 2, $x, $y, "90", $C['ltgray']); */

	imageString($im, 4, $sz/2 + 4, 2        , 'N', $C['black']);
	imageString($im, 4, $sz/2 + 4, $sz - 16 , 'S', $C['black']);
	imageString($im, 4, 4        , $sz/2 + 4, 'E', $C['black']);
	imageString($im, 4, $sz - 10 , $sz/2 + 4, 'W', $C['black']);

}

function skygrid($im, $sz, $C){
	for($i = 0; $i < 180; $i += 15){
		list($x0, $y0, $x1, $y1) = radial($i, $sz);
		imageLine($im, $x0, $y0, $x1, $y1, $C['ltgray']);
	}

	for($i = 15; $i < 90; $i += 15)
		elevation($im, $sz, $C, $i);
}

?>
