1<?php
2// PHP Weathermap 0.97a
3// Copyright Howard Jones, 2005-2010 howie@thingy.com
4// http://www.network-weathermap.com/
5// Released under the GNU Public License
6
7// Utility functions
8// Check for GD & PNG support This is just in here so that both the editor and CLI can use it without the need for another file
9function module_checks()
10{
11	if (!extension_loaded('gd'))
12	{
13		warn ("\n\nNo image (gd) extension is loaded. This is required by weathermap. [WMWARN20]\n\n");
14		warn ("\nrun check.php to check PHP requirements.\n\n");
15
16		return (FALSE);
17	}
18
19	if (!function_exists('imagecreatefrompng'))
20	{
21		warn ("Your GD php module doesn't support PNG format. [WMWARN21]\n");
22		warn ("\nrun check.php to check PHP requirements.\n\n");
23		return (FALSE);
24	}
25
26	if (!function_exists('imagecreatetruecolor'))
27	{
28		warn ("Your GD php module doesn't support truecolor. [WMWARN22]\n");
29		warn ("\nrun check.php to check PHP requirements.\n\n");
30		return (FALSE);
31	}
32
33	if (!function_exists('imagecopyresampled'))
34	{
35		warn ("Your GD php module doesn't support thumbnail creation (imagecopyresampled). [WMWARN23]\n");
36	}
37	return (TRUE);
38}
39
40function debug($string)
41{
42	global $weathermap_debugging;
43	global $weathermap_map;
44	global $weathermap_debug_suppress;
45
46	if ($weathermap_debugging)
47	{
48		$calling_fn = "";
49		if(function_exists("debug_backtrace"))
50		{
51			$bt = debug_backtrace();
52			$index = 1;
53		# 	$class = (isset($bt[$index]['class']) ? $bt[$index]['class'] : '');
54        		$function = (isset($bt[$index]['function']) ? $bt[$index]['function'] : '');
55			$index = 0;
56			$file = (isset($bt[$index]['file']) ? basename($bt[$index]['file']) : '');
57        		$line = (isset($bt[$index]['line']) ? $bt[$index]['line'] : '');
58
59			$calling_fn = " [$function@$file:$line]";
60
61			if(is_array($weathermap_debug_suppress) && in_array(strtolower($function),$weathermap_debug_suppress)) return;
62		}
63
64		// use Cacti's debug log, if we are running from the poller
65		if (function_exists('debug_log_insert') && (!function_exists('show_editor_startpage')))
66		{ cacti_log("DEBUG:$calling_fn " . ($weathermap_map==''?'':$weathermap_map.": ") . rtrim($string), true, "WEATHERMAP"); }
67		else
68		{
69			$stderr=fopen('php://stderr', 'w');
70			fwrite($stderr, "DEBUG:$calling_fn " . ($weathermap_map==''?'':$weathermap_map.": ") . $string);
71			fclose ($stderr);
72
73			// mostly this is overkill, but it's sometimes useful (mainly in the editor)
74			if(1==0)
75			{
76				$log=fopen('debug.log', 'a');
77				fwrite($log, "DEBUG:$calling_fn " . ($weathermap_map==''?'':$weathermap_map.": ") . $string);
78				fclose ($log);
79			}
80		}
81	}
82}
83
84function warn($string,$notice_only=FALSE)
85{
86	global $weathermap_map;
87	global $weathermap_warncount;
88
89	$message = "";
90
91	if(!$notice_only)
92	{
93		$weathermap_warncount++;
94		$message .= "WARNING: ";
95	}
96
97	$message .= ($weathermap_map==''?'':$weathermap_map.": ") . rtrim($string);
98
99	// use Cacti's debug log, if we are running from the poller
100	if (function_exists('cacti_log') && (!function_exists('show_editor_startpage')))
101	{ cacti_log($message, true, "WEATHERMAP"); }
102	else
103	{
104		$stderr=fopen('php://stderr', 'w');
105		fwrite($stderr, $message."\n");
106		fclose ($stderr);
107	}
108}
109
110function js_escape($str, $wrap=TRUE)
111{
112	$str=str_replace('\\', '\\\\', $str);
113	$str=str_replace('"', '\\"', $str);
114
115	if($wrap) $str='"' . $str . '"';
116
117	return ($str);
118}
119
120function mysprintf($format,$value,$kilo=1000)
121{
122	$output = "";
123
124	debug("mysprintf: $format $value\n");
125	if(preg_match("/%(\d*\.?\d*)k/",$format,$matches))
126	{
127		$spec = $matches[1];
128		$places = 2;
129		if($spec !='')
130		{
131			preg_match("/(\d*)\.?(\d*)/",$spec,$matches);
132			if($matches[2] != '') $places=$matches[2];
133			// we don't really need the justification (pre-.) part...
134		}
135		debug("KMGT formatting $value with $spec.\n");
136		$result = nice_scalar($value, $kilo, $places);
137		$output = preg_replace("/%".$spec."k/",$format,$result);
138	}
139	else
140	{
141		debug("Falling through to standard sprintf\n");
142		$output = sprintf($format,$value);
143	}
144	return $output;
145}
146
147// ParseString is based on code from:
148// http://www.webscriptexpert.com/Php/Space-Separated%20Tag%20Parser/
149
150function ParseString($input)
151{
152    $output = array();            // Array of Output
153    $cPhraseQuote = null;   // Record of the quote that opened the current phrase
154    $sPhrase = null;                // Temp storage for the current phrase we are building
155
156    // Define some constants
157    $sTokens = " \t";    // Space, Tab
158    $sQuotes = "'\"";                // Single and Double Quotes
159
160    // Start the State Machine
161    do
162    {
163        // Get the next token, which may be the first
164        $sToken = isset($sToken)? strtok($sTokens) : strtok($input, $sTokens);
165
166        // Are there more tokens?
167        if ($sToken === false)
168        {
169                // Ensure that the last phrase is marked as ended
170                $cPhraseQuote = null;
171        }
172        else
173        {
174                // Are we within a phrase or not?
175                if ($cPhraseQuote !== null)
176                {
177                        // Will the current token end the phrase?
178                        if (substr($sToken, -1, 1) === $cPhraseQuote)
179                        {
180                                // Trim the last character and add to the current phrase, with a single leading space if necessary
181                                if (strlen($sToken) > 1) $sPhrase .= ((strlen($sPhrase) > 0)? ' ' : null) . substr($sToken, 0, -1);
182                                $cPhraseQuote = null;
183                        }
184                        else
185                        {
186                                // If not, add the token to the phrase, with a single leading space if necessary
187                                $sPhrase .= ((strlen($sPhrase) > 0)? ' ' : null) . $sToken;
188                        }
189                }
190                else
191                {
192                        // Will the current token start a phrase?
193                        if (strpos($sQuotes, $sToken[0]) !== false)
194                        {
195                                // Will the current token end the phrase?
196                                if ((strlen($sToken) > 1) && ($sToken[0] === substr($sToken, -1, 1)))
197                                {
198                                        // The current token begins AND ends the phrase, trim the quotes
199                                        $sPhrase = substr($sToken, 1, -1);
200                                }
201                                else
202                                {
203                                        // Remove the leading quote
204                                        $sPhrase = substr($sToken, 1);
205                                        $cPhraseQuote = $sToken[0];
206                                }
207                        }
208                        else
209                                $sPhrase = $sToken;
210                }
211        }
212
213        // If, at this point, we are not within a phrase, the prepared phrase is complete and can be added to the array
214        if (($cPhraseQuote === null) && ($sPhrase != null))
215        {
216            $output[] = $sPhrase;
217            $sPhrase = null;
218        }
219    }
220    while ($sToken !== false);      // Stop when we receive FALSE from strtok()
221
222    return $output;
223}
224
225// wrapper around imagecolorallocate to try and re-use palette slots where possible
226function myimagecolorallocate($image, $red, $green, $blue)
227{
228	// it's possible that we're being called early - just return straight away, in that case
229	if(!isset($image)) return(-1);
230
231	$existing=imagecolorexact($image, $red, $green, $blue);
232
233	if ($existing > -1)
234		return $existing;
235
236	return (imagecolorallocate($image, $red, $green, $blue));
237}
238
239function screenshotify($input)
240{
241	$tmp = $input;
242
243	$tmp = preg_replace("/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/","127.0.0.1",$tmp);
244	$tmp = preg_replace("/([A-Za-z]{3,})/e","str_repeat('x',strlen('\\1'))",$tmp);
245
246	return($tmp);
247}
248
249function render_colour($col)
250{
251	if (($col[0] == -1) && ($col[1] == -1) && ($col[1] == -1)) { return 'none'; }
252	else if (($col[0] == -2) && ($col[1] == -2) && ($col[1] == -2)) { return 'copy'; }
253	else if (($col[0] == -3) && ($col[1] == -3) && ($col[1] == -3)) { return 'contrast'; }
254	else { return sprintf("%d %d %d", $col[0], $col[1], $col[2]); }
255}
256
257// take the same set of points that imagepolygon does, but don't close the shape
258function imagepolyline($image, $points, $npoints, $color)
259{
260	for ($i=0; $i < ($npoints - 1); $i++)
261	{
262		imageline($image, $points[$i * 2], $points[$i * 2 + 1], $points[$i * 2 + 2], $points[$i * 2 + 3],
263		$color);
264	}
265}
266
267// draw a filled round-cornered rectangle
268function imagefilledroundedrectangle($image  , $x1  , $y1  , $x2  , $y2  , $radius, $color)
269{
270	imagefilledrectangle($image, $x1,$y1+$radius, $x2,$y2-$radius, $color);
271	imagefilledrectangle($image, $x1+$radius,$y1, $x2-$radius,$y2, $color);
272
273	imagefilledarc($image, $x1+$radius, $y1+$radius, $radius*2, $radius*2, 0, 360, $color, IMG_ARC_PIE);
274	imagefilledarc($image, $x2-$radius, $y1+$radius, $radius*2, $radius*2, 0, 360, $color, IMG_ARC_PIE);
275
276	imagefilledarc($image, $x1+$radius, $y2-$radius, $radius*2, $radius*2, 0, 360, $color, IMG_ARC_PIE);
277	imagefilledarc($image, $x2-$radius, $y2-$radius, $radius*2, $radius*2, 0, 360, $color, IMG_ARC_PIE);
278
279	# bool imagefilledarc  ( resource $image  , int $cx  , int $cy  , int $width  , int $height  , int $start  , int $end  , int $color  , int $style  )
280}
281
282// draw a round-cornered rectangle
283function imageroundedrectangle( $image  , $x1  , $y1  , $x2  , $y2  , $radius, $color )
284{
285
286	imageline($image, $x1+$radius, $y1, $x2-$radius, $y1, $color);
287	imageline($image, $x1+$radius, $y2, $x2-$radius, $y2, $color);
288	imageline($image, $x1, $y1+$radius, $x1, $y2-$radius, $color);
289	imageline($image, $x2, $y1+$radius, $x2, $y2-$radius, $color);
290
291	imagearc($image, $x1+$radius, $y1+$radius, $radius*2, $radius*2, 180, 270, $color);
292	imagearc($image, $x2-$radius, $y1+$radius, $radius*2, $radius*2, 270, 360, $color);
293	imagearc($image, $x1+$radius, $y2-$radius, $radius*2, $radius*2, 90, 180, $color);
294	imagearc($image, $x2-$radius, $y2-$radius, $radius*2, $radius*2, 0, 90, $color);
295}
296
297function imagecreatefromfile($filename)
298{
299	$bgimage=NULL;
300	$formats = imagetypes();
301	if (is_readable($filename))
302	{
303		list($width, $height, $type, $attr) = getimagesize($filename);
304		switch($type)
305		{
306		case IMAGETYPE_GIF:
307			if(imagetypes() & IMG_GIF)
308			{
309				$bgimage=imagecreatefromgif($filename);
310			}
311			else
312			{
313				warn("Image file $filename is GIF, but GIF is not supported by your GD library. [WMIMG01]\n");
314			}
315			break;
316
317		case IMAGETYPE_JPEG:
318			if(imagetypes() & IMG_JPEG)
319			{
320				$bgimage=imagecreatefromjpeg($filename);
321			}
322			else
323			{
324				warn("Image file $filename is JPEG, but JPEG is not supported by your GD library. [WMIMG02]\n");
325			}
326			break;
327
328		case IMAGETYPE_PNG:
329			if(imagetypes() & IMG_PNG)
330			{
331				$bgimage=imagecreatefrompng($filename);
332			}
333			else
334			{
335				warn("Image file $filename is PNG, but PNG is not supported by your GD library. [WMIMG03]\n");
336			}
337			break;
338
339		default:
340			warn("Image file $filename wasn't recognised (type=$type). Check format is supported by your GD library. [WMIMG04]\n");
341			break;
342		}
343	}
344	else
345	{
346		warn("Image file $filename is unreadable. Check permissions. [WMIMG05]\n");
347	}
348	return $bgimage;
349}
350
351// taken from here:
352// http://www.php.net/manual/en/function.imagefilter.php#62395
353// ( with some bugfixes and changes)
354//
355// Much nicer colorization than imagefilter does, AND no special requirements.
356// Preserves white, black and transparency.
357//
358function imagecolorize($im, $r, $g, $b)
359{
360    //We will create a monochromatic palette based on
361    //the input color
362    //which will go from black to white
363    //Input color luminosity: this is equivalent to the
364    //position of the input color in the monochromatic
365    //palette
366    $lum_inp = round(255 * ($r + $g + $b) / 765); //765=255*3
367
368    //We fill the palette entry with the input color at its
369    //corresponding position
370
371    $pal[$lum_inp]['r'] = $r;
372    $pal[$lum_inp]['g'] = $g;
373    $pal[$lum_inp]['b'] = $b;
374
375    //Now we complete the palette, first we'll do it to
376    //the black,and then to the white.
377
378    //FROM input to black
379    //===================
380    //how many colors between black and input
381    $steps_to_black = $lum_inp;
382
383    //The step size for each component
384    if ($steps_to_black)
385    {
386        $step_size_red = $r / $steps_to_black;
387        $step_size_green = $g / $steps_to_black;
388        $step_size_blue = $b / $steps_to_black;
389    }
390
391    for ($i = $steps_to_black; $i >= 0; $i--)
392    {
393        $pal[$steps_to_black - $i]['r'] = $r - round($step_size_red * $i);
394        $pal[$steps_to_black - $i]['g'] = $g - round($step_size_green * $i);
395        $pal[$steps_to_black - $i]['b'] = $b - round($step_size_blue * $i);
396    }
397
398    //From input to white:
399    //===================
400    //how many colors between input and white
401    $steps_to_white = 255 - $lum_inp;
402
403    if ($steps_to_white)
404    {
405        $step_size_red = (255 - $r) / $steps_to_white;
406        $step_size_green = (255 - $g) / $steps_to_white;
407        $step_size_blue = (255 - $b) / $steps_to_white;
408    }
409    else
410        $step_size_red = $step_size_green = $step_size_blue = 0;
411
412    //The step size for each component
413    for ($i = ($lum_inp + 1); $i <= 255; $i++)
414    {
415        $pal[$i]['r'] = $r + round($step_size_red * ($i - $lum_inp));
416        $pal[$i]['g'] = $g + round($step_size_green * ($i - $lum_inp));
417        $pal[$i]['b'] = $b + round($step_size_blue * ($i - $lum_inp));
418    }
419
420    //--- End of palette creation
421
422    //Now,let's change the original palette into the one we
423    //created
424    for ($c = 0; $c < imagecolorstotal($im); $c++)
425    {
426        $col = imagecolorsforindex($im, $c);
427        $lum_src = round(255 * ($col['red'] + $col['green'] + $col['blue']) / 765);
428        $col_out = $pal[$lum_src];
429
430   #     printf("%d (%d,%d,%d) -> %d -> (%d,%d,%d)\n", $c,
431   #                $col['red'], $col['green'], $col['blue'],
432   #                $lum_src,
433   #                $col_out['r'], $col_out['g'], $col_out['b']
434   #             );
435
436        imagecolorset($im, $c, $col_out['r'], $col_out['g'], $col_out['b']);
437    }
438
439    return($im);
440}
441
442// find the point where a line from x1,y1 through x2,y2 crosses another line through x3,y3 and x4,y4
443// (the point might not be between those points, but beyond them)
444// - doesn't handle parallel lines. In our case we will never get them.
445// - make sure we remove colinear points, or this will not be true!
446function line_crossing($x1,$y1,$x2,$y2, $x3,$y3,$x4,$y4)
447{
448
449    // First, check that the slope isn't infinite.
450    // if it is, tweak it to be merely huge
451    if($x1 != $x2) { $slope1 = ($y2-$y1)/($x2-$x1); }
452    else { $slope1 = 1e10; debug("Slope1 is infinite.\n");}
453
454    if($x3 != $x4) { $slope2 = ($y4-$y3)/($x4-$x3); }
455    else { $slope2 = 1e10; debug("Slope2 is infinite.\n");}
456
457    $a1 = $slope1;
458    $a2 = $slope2;
459    $b1 = -1;
460    $b2 = -1;
461    $c1 = ($y1 - $slope1 * $x1 );
462    $c2 = ($y3 - $slope2 * $x3 );
463
464    $det_inv = 1/($a1*$b2 - $a2*$b1);
465
466    $xi = (($b1*$c2 - $b2*$c1)*$det_inv);
467    $yi = (($a2*$c1 - $a1*$c2)*$det_inv);
468
469    return(array($xi,$yi));
470}
471
472// rotate a list of points around cx,cy by an angle in radians, IN PLACE
473function RotateAboutPoint(&$points, $cx,$cy, $angle=0)
474{
475	$npoints = count($points)/2;
476
477	for($i=0;$i<$npoints;$i++)
478	{
479		$ox = $points[$i*2] - $cx;
480		$oy = $points[$i*2+1] - $cy;
481		$rx = $ox * cos($angle) - $oy*sin($angle);
482		$ry = $oy * cos($angle) + $ox*sin($angle);
483
484		$points[$i*2] = $rx + $cx;
485		$points[$i*2+1] = $ry + $cy;
486	}
487}
488
489// calculate the points for a span of the curve. We pass in the distance so far, and the array index, so that
490// the chunk of array generated by this function can be array_merged with existing points from before.
491// Considering how many array functions there are, PHP has horrible list support
492// Each point is a 3-tuple - x,y,distance - which is used later to figure out where the 25%, 50% marks are on the curve
493function calculate_catmull_rom_span($startn, $startdistance, $numsteps, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
494{
495	$Ap_x=-$x0 + 3 * $x1 - 3 * $x2 + $x3;
496	$Bp_x=2 * $x0 - 5 * $x1 + 4 * $x2 - $x3;
497	$Cp_x=-$x0 + $x2;
498	$Dp_x=2 * $x1;
499
500	$Ap_y=-$y0 + 3 * $y1 - 3 * $y2 + $y3;
501	$Bp_y=2 * $y0 - 5 * $y1 + 4 * $y2 - $y3;
502	$Cp_y=-$y0 + $y2;
503	$Dp_y=2 * $y1;
504
505	$d=2;
506	$n=$startn;
507	$distance=$startdistance;
508
509	$lx=$x0;
510	$ly=$y0;
511
512	$allpoints[]=array
513		(
514			$x0,
515			$y0,
516			$distance
517		);
518
519	for ($i=0; $i <= $numsteps; $i++)
520	{
521		$t=$i / $numsteps;
522		$t2=$t * $t;
523		$t3=$t2 * $t;
524		$x=(($Ap_x * $t3) + ($Bp_x * $t2) + ($Cp_x * $t) + $Dp_x) / $d;
525		$y=(($Ap_y * $t3) + ($Bp_y * $t2) + ($Cp_y * $t) + $Dp_y) / $d;
526
527		if ($i > 0)
528		{
529			$step=sqrt((($x - $lx) * ($x - $lx)) + (($y - $ly) * ($y - $ly)));
530			$distance=$distance + $step;
531			$allpoints[$n]=array
532				(
533					$x,
534					$y,
535					$distance
536				);
537
538			$n++;
539		}
540
541		$lx=$x;
542		$ly=$y;
543	}
544
545	return array($allpoints, $distance, $n);
546}
547
548function find_distance_coords(&$pointarray,$distance)
549{
550	// We find the nearest lower point for each distance,
551	// then linearly interpolate to get a more accurate point
552	// this saves having quite so many points-per-curve
553
554	$index=find_distance($pointarray, $distance);
555
556	$ratio=($distance - $pointarray[$index][2]) / ($pointarray[$index + 1][2] - $pointarray[$index][2]);
557	$x = $pointarray[$index][0] + $ratio * ($pointarray[$index + 1][0] - $pointarray[$index][0]);
558	$y = $pointarray[$index][1] + $ratio * ($pointarray[$index + 1][1] - $pointarray[$index][1]);
559
560	return(array($x,$y,$index));
561}
562
563function find_distance_coords_angle(&$pointarray,$distance)
564{
565	// This is the point we need
566	list($x,$y,$index) = find_distance_coords($pointarray,$distance);
567
568	// now to find one either side of it, to get a line to find the angle of
569	$left = $index;
570	$right = $left+1;
571	$max = count($pointarray)-1;
572	// if we're right up against the last point, then step backwards one
573	if($right>=$max)
574	{
575		$left--;
576		$right--;
577	}
578	# if($left<=0) { $left = 0; }
579
580	$x1 = $pointarray[$left][0];
581	$y1 = $pointarray[$left][1];
582
583	$x2 = $pointarray[$right][0];
584	$y2 = $pointarray[$right][1];
585
586	$dx = $x2 - $x1;
587	$dy = $y2 - $y1;
588
589	$angle = rad2deg(atan2(-$dy,$dx));
590
591	return(array($x,$y,$index,$angle));
592}
593
594// return the index of the point either at (unlikely) or just before the target distance
595// we will linearly interpolate afterwards to get a true point - pointarray is an array of 3-tuples produced by the function above
596function find_distance(&$pointarray, $distance)
597{
598	$left=0;
599	$right=count($pointarray) - 1;
600
601	if ($left == $right)
602		return ($left);
603
604	// if the distance is zero, there's no need to search (and it doesn't work anyway)
605	 if($distance==0) return($left);
606
607	// if it's a point past the end of the line, then just return the end of the line
608	// Weathermap should *never* ask for this, anyway
609	if ($pointarray[$right][2] < $distance) { return ($right); }
610
611	// if somehow we have a 0-length curve, then don't try and search, just give up
612	// in a somewhat predictable manner
613	if ($pointarray[$left][2] == $pointarray[$right][2]) { return ($left); }
614
615	while ($left <= $right)
616	{
617		$mid=floor(($left + $right) / 2);
618
619		if (($pointarray[$mid][2] < $distance) && ($pointarray[$mid + 1][2] >= $distance)) { return $mid; }
620
621		if ($distance <= $pointarray[$mid][2]) { $right=$mid - 1; }
622		else { $left=$mid + 1; }
623	}
624
625	print "FELL THROUGH\n";
626	die ("Howie's crappy binary search is wrong after all.\n");
627}
628
629// Give a list of key points, calculate a curve through them
630// return value is an array of triples (x,y,distance)
631function calc_curve(&$in_xarray, &$in_yarray,$pointsperspan = 32)
632{
633	// search through the point list, for consecutive duplicate points
634	// (most common case will be a straight link with both NODEs at the same place, I think)
635	// strip those out, because they'll break the binary search/centre-point stuff
636
637	$last_x=NULL;
638	$last_y=NULL;
639
640	for ($i=0; $i < count($in_xarray); $i++)
641	{
642		if (($in_xarray[$i] == $last_x) && ($in_yarray[$i] == $last_y)) { debug
643			("Dumping useless duplicate point on curve\n"); }
644		else
645		{
646			$xarray[]=$in_xarray[$i];
647			$yarray[]=$in_yarray[$i];
648		}
649
650		$last_x=$in_xarray[$i];
651		$last_y=$in_yarray[$i];
652	}
653
654	// only proceed if we still have at least two points!
655	if(count($xarray) <= 1)
656	{
657		warn ("Arrow not drawn, as it's 1-dimensional.\n");
658		return (array(NULL, NULL, NULL, NULL));
659	}
660
661	// duplicate the first and last points, so that all points are drawn
662	// (C-R normally would draw from x[1] to x[n-1]
663	array_unshift($xarray, $xarray[0]);
664	array_unshift($yarray, $yarray[0]);
665
666	$x=array_pop($xarray);
667	$y=array_pop($yarray);
668	array_push($xarray, $x);
669	array_push($xarray, $x);
670	array_push($yarray, $y);
671	array_push($yarray, $y);
672
673	$npoints=count($xarray);
674
675	$curvepoints=array
676		(
677			);
678
679	// add in the very first point manually (the calc function skips this one to avoid duplicates, which mess up the distance stuff)
680	$curvepoints[]=array
681		(
682			$xarray[0],
683			$yarray[0],
684			0
685		);
686
687	$np=0;
688	$distance=0;
689
690	for ($i=0; $i < ($npoints - 3); $i++)
691	{
692		list($newpoints,
693			$distance,
694			$np)=calculate_catmull_rom_span($np,     $distance,  $pointsperspan,  $xarray[$i],
695			$yarray[$i],     $xarray[$i + 1], $yarray[$i + 1], $xarray[$i + 2],
696			$yarray[$i + 2], $xarray[$i + 3], $yarray[$i + 3]);
697		$curvepoints=$curvepoints + $newpoints;
698	}
699
700	return ($curvepoints);
701}
702
703// Give a list of key points, calculate a "curve" through them
704// return value is an array of triples (x,y,distance)
705// this is here to mirror the real 'curve' version when we're using angled VIAs
706// it means that all the stuff that expects an array of points with distances won't be upset.
707function calc_straight(&$in_xarray, &$in_yarray,$pointsperspan = 12)
708{
709	// search through the point list, for consecutive duplicate points
710	// (most common case will be a straight link with both NODEs at the same place, I think)
711	// strip those out, because they'll break the binary search/centre-point stuff
712	$last_x=NULL;
713	$last_y=NULL;
714
715	for ($i=0; $i < count($in_xarray); $i++)
716	{
717		if (($in_xarray[$i] == $last_x) && ($in_yarray[$i] == $last_y)) { debug
718			("Dumping useless duplicate point on curve\n"); }
719		else
720		{
721			$xarray[]=$in_xarray[$i];
722			$yarray[]=$in_yarray[$i];
723		}
724
725		$last_x=$in_xarray[$i];
726		$last_y=$in_yarray[$i];
727	}
728
729	// only proceed if we still have at least two points!
730	if(count($xarray) <= 1)
731	{
732		warn ("Arrow not drawn, as it's 1-dimensional.\n");
733		return (array(NULL, NULL, NULL, NULL));
734	}
735
736	$npoints=count($xarray);
737
738	$curvepoints=array();
739
740	$np=0;
741	$distance=0;
742
743	for ($i=0; $i < ($npoints -1); $i++)
744	{
745		// still subdivide the straight line, becuase other stuff makes assumptions about
746		// how often there is a point - at least find_distance_coords_angle breaks
747		$newdistance = sqrt( pow($xarray[$i+1] - $xarray[$i],2) + pow($yarray[$i+1] - $yarray[$i],2) );
748		$dx = ($xarray[$i+1] - $xarray[$i])/$pointsperspan;
749		$dy = ($yarray[$i+1] - $yarray[$i])/$pointsperspan;
750		$dd = $newdistance/$pointsperspan;
751
752		for($j=0; $j< $pointsperspan; $j++)
753		{
754			$x = $xarray[$i]+$j*$dx;
755			$y = $yarray[$i]+$j*$dy;
756			$d = $distance + $j*$dd;
757
758			$curvepoints[] = array($x,$y,$d);
759			$np++;
760		}
761		$distance += $newdistance;
762	}
763	$curvepoints[] = array($xarray[$npoints-1],$yarray[$npoints-1],$distance);
764
765#	print_r($curvepoints);
766
767	return ($curvepoints);
768}
769
770function calc_arrowsize($width,&$map,$linkname)
771{
772	$arrowlengthfactor=4;
773	$arrowwidthfactor=2;
774
775	// this is so I can use it in some test code - sorry!
776	if($map !== NULL)
777	{
778		if ($map->links[$linkname]->arrowstyle == 'compact')
779		{
780			$arrowlengthfactor=1;
781			$arrowwidthfactor=1;
782		}
783
784		if (preg_match('/(\d+) (\d+)/', $map->links[$linkname]->arrowstyle, $matches))
785		{
786			$arrowlengthfactor=$matches[1];
787			$arrowwidthfactor=$matches[2];
788		}
789	}
790
791	$arrowsize = $width * $arrowlengthfactor;
792	$arrowwidth = $width * $arrowwidthfactor;
793
794	return( array($arrowsize,$arrowwidth) );
795}
796
797function draw_straight($image, &$curvepoints, $widths, $outlinecolour, $fillcolours, $linkname, &$map,
798	$q2_percent=50, $unidirectional=FALSE)
799{
800	$totaldistance = $curvepoints[count($curvepoints)-1][DISTANCE];
801
802	if($unidirectional)
803	{
804		$halfway = $totaldistance;
805		$dirs = array(OUT);
806		$q2_percent = 100;
807		$halfway = $totaldistance * ($q2_percent/100);
808		list($halfway_x,$halfway_y,$halfwayindex) = find_distance_coords($curvepoints,$halfway);
809
810		$spine[OUT] = $curvepoints;
811	}
812	else
813	{
814	    // we'll split the spine in half here.
815	  #  $q2_percent = 50;
816	    $halfway = $totaldistance * ($q2_percent/100);
817
818	    $dirs = array(OUT,IN);
819		# $dirs = array(IN);
820
821	    list($halfway_x,$halfway_y,$halfwayindex) = find_distance_coords($curvepoints,$halfway);
822	#    print "Midpoint is: $totaldistance  $halfway  $halfwayindex   $halfway_x,$halfway_y\n";
823
824	    $spine[OUT] = array();
825	    $spine[IN] = array();
826	    $npoints = count($curvepoints)-1;
827
828	    for($i=0; $i<=$halfwayindex; $i++)
829	    {
830			$spine[OUT] []= $curvepoints[$i];
831	    }
832	    // finally, add the actual midpoint
833	    $spine[OUT] []= array($halfway_x,$halfway_y, $halfway);
834
835	    // and then from the end to the middle for the other arrow
836	    for($i=$npoints; $i>$halfwayindex; $i--)
837	    {
838			// copy the original spine, but reversing the distance calculation
839			$spine[IN] []= array($curvepoints[$i][X], $curvepoints[$i][Y], $totaldistance - $curvepoints[$i][DISTANCE]);
840	    }
841	    // finally, add the actual midpoint
842	    $spine[IN] []= array($halfway_x,$halfway_y, $totaldistance - $halfway);
843	}
844
845	# wm_draw_marker_box($image,$map->selected, $halfway_x, $halfway_y );
846
847	// now we have two seperate spines, with distances, so that the arrowhead is the end of each.
848	// (or one, if it's unidir)
849
850	// so we can loop along the spine for each one as a seperate entity
851
852	// we calculate the arrow size up here, so that we can decide on the
853	// minimum length for a link. The arrowheads are the limiting factor.
854	list( $arrowsize[IN], $arrowwidth[IN] ) = calc_arrowsize( $widths[IN], $map, $linkname );
855	list( $arrowsize[OUT], $arrowwidth[OUT] ) = calc_arrowsize( $widths[OUT], $map, $linkname );
856
857	// the 1.2 here is empirical. It ought to be 1 in theory.
858	// in practice, a link this short is useless anyway, especially with bwlabels.
859	$minimumlength = 1.2*($arrowsize[IN]+$arrowsize[OUT]);
860
861	foreach ($dirs as $dir)
862	{
863		# draw_spine($image, $spine[$dir],$map->selected);
864		#draw_spine_chain($image, $spine[$dir],$map->selected,3);
865		#print "=================\n$linkname/$dir\n";
866		#dump_spine($spine[$dir]);
867	    $n = count($spine[$dir]) - 1;
868	    $l = $spine[$dir][$n][DISTANCE];
869
870		#print "L=$l N=$n\n";
871
872	    // loop increment, start point, width, labelpos, fillcolour, outlinecolour, commentpos
873	    $arrowsettings = array(+1, 0, $widths[$dir], 0, $fillcolours[$dir], $outlinecolour, 5);
874
875	    # print "Line is $n points to a distance of $l\n";
876	    if($l < $minimumlength)
877	    {
878			warn("Skipping too-short line.\n");
879	    }
880	    else
881		{
882			$arrow_d = $l - $arrowsize[$dir];
883			# print "LENGTHS $l $arrow_d ".$arrowsize[$dir]."\n";
884			list($pre_mid_x,$pre_mid_y,$pre_midindex) = find_distance_coords($spine[$dir], $arrow_d);
885			# print "POS $pre_mid_x,$pre_mid_y  $pre_midindex\n";
886			$out = array_slice($spine[$dir], 0, $pre_midindex);
887			$out []= array($pre_mid_x, $pre_mid_y, $arrow_d);
888
889			# wm_draw_marker_diamond($image, $map->selected, $pre_mid_x, $pre_mid_y, 5);
890			# imagearc($image,$pre_mid_x, $pre_mid_y ,15,15,0,360,$map->selected);
891
892			# imagearc($image,$spine[$dir][$pre_midindex+1][X],$spine[$dir][$pre_midindex+1][Y],20,20,0,360,$map->selected);
893			# imagearc($image,$spine[$dir][$pre_midindex][X],$spine[$dir][$pre_midindex][Y],20,20,0,360,$map->selected);
894			#imagearc($image,$pre_mid_x,$pre_mid_y,20,20,0,360,$map->selected);
895			#imagearc($image,$spine[$dir][$pre_midindex][X],$spine[$dir][$pre_midindex][Y],12,12,0,360,$map->selected);
896
897			$spine[$dir] = $out;
898
899			$adx=($halfway_x - $pre_mid_x);
900			$ady=($halfway_y - $pre_mid_y);
901			$ll=sqrt(($adx * $adx) + ($ady * $ady));
902
903			$anx = $ady / $ll;
904			$any = -$adx / $ll;
905
906			$ax1 = $pre_mid_x + $widths[$dir] * $anx;
907			$ay1 = $pre_mid_y + $widths[$dir] * $any;
908
909			$ax2 = $pre_mid_x + $arrowwidth[$dir] * $anx;
910			$ay2 = $pre_mid_y + $arrowwidth[$dir] * $any;
911
912			$ax3 = $halfway_x;
913			$ay3 = $halfway_y;
914
915			$ax5 = $pre_mid_x - $widths[$dir] * $anx;
916			$ay5 = $pre_mid_y - $widths[$dir] * $any;
917
918			$ax4 = $pre_mid_x - $arrowwidth[$dir] * $anx;
919			$ay4 = $pre_mid_y - $arrowwidth[$dir] * $any;
920
921			# draw_spine($image,$spine[$dir],$map->selected);
922
923			$simple = simplify_spine($spine[$dir]);
924			$newn = count($simple);
925
926			# draw_spine($image,$simple,$map->selected);
927
928			# print "Simplified to $newn points\n";
929			# if($draw_skeleton) draw_spine_chain($im,$simple,$blue, 12);
930			# draw_spine_chain($image,$simple,$map->selected, 12);
931			 # draw_spine_chain($image,$spine[$dir],$map->selected, 10);
932
933			# draw_spine_chain($image,$simple,$map->selected, 12);
934			# draw_spine($image,$simple,$map->selected);
935
936			// now do the actual drawing....
937
938			$numpoints=0;
939			$numrpoints=0;
940
941			$finalpoints = array();
942			$reversepoints = array();
943
944			$finalpoints[] = $simple[0][X];
945			$finalpoints[] = $simple[0][Y];
946			$numpoints++;
947
948			$reversepoints[] = $simple[0][X];
949			$reversepoints[] = $simple[0][Y];
950			$numrpoints++;
951
952			// before the main loop, add in the jump out to the corners
953			// if this is the first step, then we need to go from the middle to the outside edge first
954			// ( the loop may not run, but these corners are required)
955			$i = 0;
956			$v1 = new Vector($simple[$i+1][X] - $simple[$i][X], $simple[$i+1][Y] - $simple[$i][Y]);
957			$n1 = $v1->get_normal();
958
959			$finalpoints[] = $simple[$i][X] + $n1->dx*$widths[$dir];
960			$finalpoints[] = $simple[$i][Y] + $n1->dy*$widths[$dir];
961			$numpoints++;
962
963			$reversepoints[] = $simple[$i][X] - $n1->dx*$widths[$dir];
964			$reversepoints[] = $simple[$i][Y] - $n1->dy*$widths[$dir];
965			$numrpoints++;
966
967			$max_start = count($simple)-2;
968			# print "max_start is $max_start\n";
969			for ($i=0; $i <$max_start; $i++)
970			{
971				$v1 = new Vector($simple[$i+1][X] - $simple[$i][X], $simple[$i+1][Y] - $simple[$i][Y]);
972				$v2 = new Vector($simple[$i+2][X] - $simple[$i+1][X], $simple[$i+2][Y] - $simple[$i+1][Y]);
973				$n1 = $v1->get_normal();
974				$n2 = $v2->get_normal();
975
976				$capping = FALSE;
977				// figure out the angle between the lines - for very sharp turns, we should do something special
978				// (actually, their normals, but the angle is the same and we need the normals later)
979				$angle = rad2deg(atan2($n2->dy,$n2->dx) - atan2($n1->dy,$n1->dx));
980				if($angle > 180) $angle -= 360;
981				if($angle < -180) $angle += 360;
982
983				if(abs($angle)>169)
984				{
985					$capping = TRUE;
986					# print "Would cap. ($angle)\n";
987				}
988
989				// $capping = FALSE; // override that for now
990				// now figure out the geometry for where the next corners are
991
992				list($xi1,$yi1) = line_crossing( $simple[$i][X] + $n1->dx * $widths[$dir], $simple[$i][Y] + $n1->dy * $widths[$dir],
993								$simple[$i+1][X] + $n1->dx * $widths[$dir], $simple[$i+1][Y] + $n1->dy * $widths[$dir],
994								$simple[$i+1][X] + $n2->dx * $widths[$dir], $simple[$i+1][Y] + $n2->dy * $widths[$dir],
995								$simple[$i+2][X] + $n2->dx * $widths[$dir], $simple[$i+2][Y] + $n2->dy * $widths[$dir]
996							);
997
998				list($xi2,$yi2) = line_crossing( $simple[$i][X] - $n1->dx * $widths[$dir], $simple[$i][Y] - $n1->dy * $widths[$dir],
999							$simple[$i+1][X] - $n1->dx * $widths[$dir], $simple[$i+1][Y] - $n1->dy * $widths[$dir],
1000							$simple[$i+1][X] - $n2->dx * $widths[$dir], $simple[$i+1][Y] - $n2->dy * $widths[$dir],
1001							$simple[$i+2][X] - $n2->dx * $widths[$dir], $simple[$i+2][Y] - $n2->dy * $widths[$dir]
1002							);
1003
1004				if(!$capping)
1005				{
1006					$finalpoints[] = $xi1;
1007					$finalpoints[] = $yi1;
1008					$numpoints++;
1009
1010					$reversepoints[] = $xi2;
1011					$reversepoints[] = $yi2;
1012					$numrpoints++;
1013				}
1014				else
1015				{
1016				// in here, we need to decide which is the 'outside' of the corner,
1017				// because that's what we flatten. The inside of the corner is left alone.
1018				// - depending on the relative angle between the two segments, it could
1019				//   be either one of these points.
1020
1021				list($xi3,$yi3) = line_crossing( $simple[$i][X] + $n1->dx*$widths[$dir], $simple[$i][Y] + $n1->dy*$widths[$dir],
1022							$simple[$i+1][X] + $n1->dx*$widths[$dir], $simple[$i+1][Y] + $n1->dy*$widths[$dir],
1023							$simple[$i+1][X] - $n2->dx*$widths[$dir], $simple[$i+1][Y] - $n2->dy*$widths[$dir],
1024							$simple[$i+2][X] - $n2->dx*$widths[$dir], $simple[$i+2][Y] - $n2->dy*$widths[$dir]
1025							);
1026
1027				list($xi4,$yi4) = line_crossing( $simple[$i][X] - $n1->dx*$widths[$dir], $simple[$i][Y] - $n1->dy*$widths[$dir],
1028							$simple[$i+1][X] - $n1->dx*$widths[$dir], $simple[$i+1][Y] - $n1->dy*$widths[$dir],
1029							$simple[$i+1][X] + $n2->dx*$widths[$dir], $simple[$i+1][Y] + $n2->dy*$widths[$dir],
1030							$simple[$i+2][X] + $n2->dx*$widths[$dir], $simple[$i+2][Y] + $n2->dy*$widths[$dir]
1031							);
1032				if($angle < 0)
1033				{
1034					$finalpoints[] = $xi3;
1035					$finalpoints[] = $yi3;
1036					$numpoints++;
1037
1038					$finalpoints[] = $xi4;
1039					$finalpoints[] = $yi4;
1040					$numpoints++;
1041
1042					$reversepoints[] = $xi2;
1043					$reversepoints[] = $yi2;
1044					$numrpoints++;
1045				}
1046				else
1047				{
1048					$reversepoints[] = $xi4;
1049					$reversepoints[] = $yi4;
1050					$numrpoints++;
1051
1052					$reversepoints[] = $xi3;
1053					$reversepoints[] = $yi3;
1054					$numrpoints++;
1055
1056					$finalpoints[] = $xi1;
1057					$finalpoints[] = $yi1;
1058					$numpoints++;
1059				}
1060
1061				}
1062			}
1063
1064			// at this end, we add the arrowhead
1065
1066			$finalpoints[] = $ax1;
1067			$finalpoints[] = $ay1;
1068			$finalpoints[] = $ax2;
1069			$finalpoints[] = $ay2;
1070			$finalpoints[] = $ax3;
1071			$finalpoints[] = $ay3;
1072			$finalpoints[] = $ax4;
1073			$finalpoints[] = $ay4;
1074			$finalpoints[] = $ax5;
1075			$finalpoints[] = $ay5;
1076
1077			$numpoints += 5;
1078
1079			// combine the forwards and backwards paths, to make a complete loop
1080			for($i=($numrpoints-1)*2; $i>=0; $i-=2)
1081			{
1082				$x = $reversepoints[$i];
1083				$y = $reversepoints[$i+1];
1084
1085				$finalpoints[] = $x;
1086				$finalpoints[] = $y;
1087				$numpoints++;
1088			}
1089			// $finalpoints[] contains a complete outline of the line at this stage
1090
1091			if (!is_null($fillcolours[$dir]))
1092			{
1093				wimagefilledpolygon($image, $finalpoints, count($finalpoints) / 2, $arrowsettings[4]);
1094			}
1095			else
1096			{
1097				debug("Not drawing $linkname ($dir) fill because there is no fill colour\n");
1098			}
1099
1100			$areaname = "LINK:L" . $map->links[$linkname]->id . ":$dir";
1101			$map->imap->addArea("Polygon", $areaname, '', $finalpoints);
1102			debug ("Adding Poly imagemap for $areaname\n");
1103
1104			if (!is_null($outlinecolour))
1105			{
1106				wimagepolygon($image, $finalpoints, count($finalpoints) / 2, $arrowsettings[5]);
1107			}
1108			else
1109			{
1110				debug("Not drawing $linkname ($dir) outline because there is no outline colour\n");
1111			}
1112	    }
1113	}
1114}
1115
1116// top-level function that takes a two lists to define some points, and draws a weathermap link
1117// - this takes care of all the extras, like arrowheads, and where to put the bandwidth labels
1118//    curvepoints is an array of the points the curve passes through
1119//    width is the link width (the actual width is twice this)
1120//    outlinecolour is a GD colour reference
1121//    fillcolours is an array of two more colour references, one for the out, and one for the in spans
1122function draw_curve($image, &$curvepoints, $widths, $outlinecolour, $fillcolours, $linkname, &$map,
1123	$q2_percent=50, $unidirectional=FALSE)
1124{
1125	// now we have a 'spine' - all the central points for this curve.
1126	// time to flesh it out to the right width, and figure out where to draw arrows and bandwidth boxes...
1127
1128	// get the full length of the curve from the last point
1129	$totaldistance = $curvepoints[count($curvepoints)-1][2];
1130	// find where the in and out arrows will join (normally halfway point)
1131	$halfway = $totaldistance * ($q2_percent/100);
1132
1133	$dirs = array(OUT,IN);
1134
1135	// for a unidirectional map, we just ignore the second half (direction = -1)
1136	if($unidirectional)
1137	{
1138		$halfway = $totaldistance;
1139		$dirs = array(OUT);
1140	}
1141
1142	// loop increment, start point, width, labelpos, fillcolour, outlinecolour, commentpos
1143	$arrowsettings[OUT] = array(+1, 0, $widths[OUT], 0, $fillcolours[OUT], $outlinecolour, 5);
1144	$arrowsettings[IN] = array(-1, count($curvepoints) - 1, $widths[IN], 0, $fillcolours[IN], $outlinecolour, 95);
1145
1146	// we calculate the arrow size up here, so that we can decide on the
1147	// minimum length for a link. The arrowheads are the limiting factor.
1148	list($arrowsize[IN],$arrowwidth[IN]) = calc_arrowsize($widths[IN], $map, $linkname);
1149	list($arrowsize[OUT],$arrowwidth[OUT]) = calc_arrowsize($widths[OUT], $map, $linkname);
1150
1151	// the 1.2 here is empirical. It ought to be 1 in theory.
1152	// in practice, a link this short is useless anyway, especially with bwlabels.
1153	$minimumlength = 1.2*($arrowsize[IN]+$arrowsize[OUT]);
1154
1155	# warn("$linkname: Total: $totaldistance $arrowsize $arrowwidth $minimumlength\n");
1156	if($totaldistance <= $minimumlength)
1157	{
1158		warn("Skipping drawing very short link ($linkname). Impossible to draw! Try changing WIDTH or ARROWSTYLE? [WMWARN01]\n");
1159		return;
1160	}
1161
1162
1163	list($halfway_x,$halfway_y,$halfwayindex) = find_distance_coords($curvepoints,$halfway);
1164
1165	// loop over direction here
1166	// direction is 1.0 for the first half (forwards through the pointlist), and -1.0 for the second half (backwards from the end)
1167	//  - used as a multiplier on anything that looks forwards or backwards through the list
1168
1169	foreach ($dirs as $dir)
1170	{
1171		$direction = $arrowsettings[$dir][0];
1172		// $width = $widths[$dir];
1173		// this is the last index before the arrowhead starts
1174		list($pre_mid_x,$pre_mid_y,$pre_midindex) = find_distance_coords($curvepoints,$halfway - $direction * $arrowsize[$dir]);
1175
1176		$there_points=array();
1177		$back_points=array();
1178		$arrowpoints=array();
1179
1180		# if ($direction < 0) { $start=count($curvepoints) - 1; }
1181		# else { $start=0; }
1182		$start = $arrowsettings[$dir][1];
1183
1184		for ($i=$start; $i != $pre_midindex; $i+=$direction)
1185		{
1186			// for each point on the spine, produce two points normal to it's direction,
1187			// each is $width away from the spine, but we build up the two lists in the opposite order,
1188			// so that when they are joined together, we get one continuous line
1189
1190			$dx=$curvepoints[$i + $direction][0] - $curvepoints[$i][0];
1191			$dy=$curvepoints[$i + $direction][1] - $curvepoints[$i][1];
1192			$l=sqrt(($dx * $dx) + ($dy * $dy));
1193			$nx=$dy / $l;
1194			$ny=-$dx / $l;
1195
1196			$there_points[]=$curvepoints[$i][0] + $direction * $widths[$dir] * $nx;
1197			$there_points[]=$curvepoints[$i][1] + $direction * $widths[$dir] * $ny;
1198
1199			$back_points[]=$curvepoints[$i][0] - $direction * $widths[$dir] * $nx;
1200			$back_points[]=$curvepoints[$i][1] - $direction * $widths[$dir] * $ny;
1201		}
1202
1203		// all the normal line is done, now lets add an arrowhead on
1204
1205		$adx=($halfway_x - $pre_mid_x);
1206		$ady=($halfway_y - $pre_mid_y);
1207		$l=sqrt(($adx * $adx) + ($ady * $ady));
1208
1209		$anx=$ady / $l;
1210		$any=-$adx / $l;
1211
1212		$there_points[]=$pre_mid_x + $direction * $widths[$dir] * $anx;
1213		$there_points[]=$pre_mid_y + $direction * $widths[$dir] * $any;
1214
1215		$there_points[]=$pre_mid_x + $direction * $arrowwidth[$dir] * $anx;
1216		$there_points[]=$pre_mid_y + $direction * $arrowwidth[$dir] * $any;
1217
1218		$there_points[]=$halfway_x;
1219		$there_points[]=$halfway_y;
1220
1221		$there_points[]=$pre_mid_x - $direction * $arrowwidth[$dir] * $anx;
1222		$there_points[]=$pre_mid_y - $direction * $arrowwidth[$dir] * $any;
1223
1224		$there_points[]=$pre_mid_x - $direction * $widths[$dir] * $anx;
1225		$there_points[]=$pre_mid_y - $direction * $widths[$dir] * $any;
1226
1227		// all points done, now combine the lists, and produce the final result.
1228		$metapts = "";
1229		$y=array_pop($back_points);
1230		$x=array_pop($back_points);
1231		do
1232		{
1233			$metapts .= " $x $y";
1234			$there_points[]=$x;
1235			$there_points[]=$y;
1236			$y=array_pop($back_points);
1237			$x=array_pop($back_points);
1238		} while (!is_null($y));
1239
1240		$arrayindex=1;
1241
1242		if ($direction < 0) $arrayindex=0;
1243
1244		if (!is_null($fillcolours[$arrayindex]))
1245		{
1246			wimagefilledpolygon($image, $there_points, count($there_points) / 2, $arrowsettings[$dir][4]);
1247		}
1248		else
1249		{
1250			debug("Not drawing $linkname ($dir) fill because there is no fill colour\n");
1251		}
1252
1253		# $areaname = "LINK:" . $linkname. ":$dir";
1254		$areaname = "LINK:L" . $map->links[$linkname]->id . ":$dir";
1255		$map->imap->addArea("Polygon", $areaname, '', $there_points);
1256		debug ("Adding Poly imagemap for $areaname\n");
1257
1258		if (!is_null($outlinecolour))
1259		{
1260			wimagepolygon($image, $there_points, count($there_points) / 2, $arrowsettings[$dir][5]);
1261		}
1262		else
1263		{
1264			debug("Not drawing $linkname ($dir) outline because there is no outline colour\n");
1265		}
1266	}
1267}
1268
1269// Take a spine, and strip out all the points that are co-linear with the points either side of them
1270function simplify_spine(&$input, $epsilon=1e-10)
1271{
1272    $output = array();
1273
1274    $output []= $input[0];
1275    $n=1;
1276    $c = count($input)-2;
1277    $skip=0;
1278
1279    for($n=1; $n<=$c; $n++)
1280    {
1281	$x = $input[$n][X];
1282	$y = $input[$n][Y];
1283
1284	// figure out the area of the triangle formed by this point, and the one before and after
1285	$a = 	abs($input[$n-1][X] * ( $input[$n][Y] - $input[$n+1][Y] )
1286		+ $input[$n][X] * ( $input[$n+1][Y] - $input[$n-1][Y] )
1287		+ $input[$n+1][X] * ( $input[$n-1][Y] - $input[$n][Y] ) );
1288
1289	# print "$n  $x,$y    $a";
1290
1291        if ( $a > $epsilon)
1292	// if(1==1)
1293        {
1294            $output []= $input[$n];
1295	#    print "  KEEP";
1296        }
1297        else
1298        {
1299            // ignore n
1300            $skip++;
1301	#    print "  SKIP";
1302
1303        }
1304	# print "\n";
1305    }
1306
1307    debug("Skipped $skip points of $c\n");
1308
1309#    print "------------------------\n";
1310
1311    $output []= $input[$c+1];
1312    return $output;
1313}
1314
1315function unformat_number($instring, $kilo = 1000)
1316{
1317	$matches=0;
1318	$number=0;
1319
1320	if (preg_match("/([0-9\.]+)(M|G|K|T|m|u)/", $instring, $matches))
1321	{
1322		$number=floatval($matches[1]);
1323
1324		if ($matches[2] == 'K') { $number=$number * $kilo; }
1325		if ($matches[2] == 'M') { $number=$number * $kilo * $kilo; }
1326		if ($matches[2] == 'G') { $number=$number * $kilo * $kilo * $kilo; }
1327		if ($matches[2] == 'T') { $number=$number * $kilo * $kilo * $kilo * $kilo; }
1328		// new, for absolute datastyle. Think seconds.
1329		if ($matches[2] == 'm') { $number=$number / $kilo; }
1330		if ($matches[2] == 'u') { $number=$number / ($kilo * $kilo); }
1331	}
1332	else { $number=floatval($instring); }
1333
1334	return ($number);
1335}
1336
1337// given a compass-point, and a width & height, return a tuple of the x,y offsets
1338function calc_offset($offsetstring, $width, $height)
1339{
1340	if(preg_match("/^([-+]?\d+):([-+]?\d+)$/",$offsetstring,$matches))
1341	{
1342		debug("Numeric Offset found\n");
1343		return(array($matches[1],$matches[2]));
1344	}
1345	elseif(preg_match("/(NE|SE|NW|SW|N|S|E|W|C)(\d+)?$/i",$offsetstring,$matches))
1346	{
1347		$multiply = 1;
1348		if( isset($matches[2] ) )
1349		{
1350			$multiply = intval($matches[2])/100;
1351			debug("Percentage compass offset: multiply by $multiply");
1352		}
1353
1354		$height = $height * $multiply;
1355		$width = $width * $multiply;
1356
1357		switch (strtoupper($matches[1]))
1358		{
1359		case 'N':
1360			return (array(0, -$height / 2));
1361
1362			break;
1363
1364		case 'S':
1365			return (array(0, $height / 2));
1366
1367			break;
1368
1369		case 'E':
1370			return (array(+$width / 2, 0));
1371
1372			break;
1373
1374		case 'W':
1375			return (array(-$width / 2, 0));
1376
1377			break;
1378
1379		case 'NW':
1380			return (array(-$width / 2, -$height / 2));
1381
1382			break;
1383
1384		case 'NE':
1385			return (array($width / 2, -$height / 2));
1386
1387			break;
1388
1389		case 'SW':
1390			return (array(-$width / 2, $height / 2));
1391
1392			break;
1393
1394		case 'SE':
1395			return (array($width / 2, $height / 2));
1396
1397			break;
1398
1399		case 'C':
1400		default:
1401			return (array(0, 0));
1402
1403			break;
1404		}
1405	}
1406	elseif( preg_match("/(-?\d+)r(\d+)$/i",$offsetstring,$matches) )
1407	{
1408		$angle = intval($matches[1]);
1409		$distance = intval($matches[2]);
1410
1411		$x = $distance * sin(deg2rad($angle));
1412		$y = - $distance * cos(deg2rad($angle));
1413
1414		return (array($x,$y));
1415
1416	}
1417	else
1418	{
1419		warn("Got a position offset that didn't make sense ($offsetstring).");
1420		return (array(0, 0));
1421	}
1422
1423
1424}
1425
1426// These next two are based on perl's Number::Format module
1427// by William R. Ward, chopped down to just what I needed
1428
1429function format_number($number, $precision = 2, $trailing_zeroes = 0)
1430{
1431	$sign=1;
1432
1433	if ($number < 0)
1434	{
1435		$number=abs($number);
1436		$sign=-1;
1437	}
1438
1439	$number=round($number, $precision);
1440	$integer=intval($number);
1441
1442	if (strlen($integer) < strlen($number)) { $decimal=substr($number, strlen($integer) + 1); }
1443
1444	if (!isset($decimal)) { $decimal=''; }
1445
1446	$integer=$sign * $integer;
1447
1448	if ($decimal == '') { return ($integer); }
1449	else { return ($integer . "." . $decimal); }
1450}
1451
1452function nice_bandwidth($number, $kilo = 1000,$decimals=1,$below_one=TRUE)
1453{
1454	$suffix='';
1455
1456	if ($number == 0)
1457		return '0';
1458
1459	$mega=$kilo * $kilo;
1460	$giga=$mega * $kilo;
1461	$tera=$giga * $kilo;
1462
1463        $milli = 1/$kilo;
1464	$micro = 1/$mega;
1465	$nano = 1/$giga;
1466
1467	if ($number >= $tera)
1468	{
1469		$number/=$tera;
1470		$suffix="T";
1471	}
1472	elseif ($number >= $giga)
1473	{
1474		$number/=$giga;
1475		$suffix="G";
1476	}
1477	elseif ($number >= $mega)
1478	{
1479		$number/=$mega;
1480		$suffix="M";
1481	}
1482	elseif ($number >= $kilo)
1483	{
1484		$number/=$kilo;
1485		$suffix="K";
1486	}
1487        elseif ($number >= 1)
1488        {
1489                $number = $number;
1490                $suffix="";
1491        }
1492	elseif (($below_one==TRUE) && ($number >= $milli))
1493	{
1494		$number/=$milli;
1495		$suffix="m";
1496	}
1497	elseif (($below_one==TRUE) && ($number >= $micro))
1498	{
1499		$number/=$micro;
1500		$suffix="u";
1501	}
1502	elseif (($below_one==TRUE) && ($number >= $nano))
1503	{
1504		$number/=$nano;
1505		$suffix="n";
1506	}
1507
1508	$result=format_number($number, $decimals) . $suffix;
1509	return ($result);
1510}
1511
1512function nice_scalar($number, $kilo = 1000, $decimals=1)
1513{
1514	$suffix = '';
1515	$prefix = '';
1516
1517	if ($number == 0)
1518		return '0';
1519
1520	if($number < 0)
1521	{
1522		$number = -$number;
1523		$prefix = '-';
1524	}
1525
1526	$mega=$kilo * $kilo;
1527	$giga=$mega * $kilo;
1528	$tera=$giga * $kilo;
1529
1530	if ($number > $tera)
1531	{
1532		$number/=$tera;
1533		$suffix="T";
1534	}
1535	elseif ($number > $giga)
1536	{
1537		$number/=$giga;
1538		$suffix="G";
1539	}
1540	elseif ($number > $mega)
1541	{
1542		$number/=$mega;
1543		$suffix="M";
1544	}
1545	elseif ($number > $kilo)
1546	{
1547		$number/=$kilo;
1548		$suffix="K";
1549	}
1550        elseif ($number > 1)
1551        {
1552                $number = $number;
1553                $suffix="";
1554        }
1555	elseif ($number < (1 / ($kilo)))
1556	{
1557		$number=$number * $mega;
1558		$suffix="u";
1559	}
1560	elseif ($number < 1)
1561	{
1562		$number=$number * $kilo;
1563		$suffix="m";
1564	}
1565
1566	$result = $prefix . format_number($number, $decimals) . $suffix;
1567	return ($result);
1568}
1569
1570
1571// ***********************************************
1572
1573// we use enough points in various places to make it worth a small class to save some variable-pairs.
1574class Point
1575{
1576	var $x, $y;
1577
1578	function Point($x=0,$y=0)
1579	{
1580		$this->x = $x;
1581		$this->y = $y;
1582	}
1583}
1584
1585// similarly for 2D vectors
1586class Vector
1587{
1588	var $dx, $dy;
1589
1590	function Vector($dx=0,$dy=0)
1591	{
1592		$this->dx = $dx;
1593		$this->dy = $dy;
1594	}
1595
1596	function get_normal()
1597	{
1598		$len = $this->length();
1599
1600		$nx1 = $this->dy / $len;
1601		$ny1 = -$this->dx / $len;
1602
1603		return( new Vector($nx1, $ny1));
1604	}
1605
1606	function normalise()
1607	{
1608		$len = $this->length();
1609		$this->dx = $this->dx/$len;
1610		$this->dy = $this->dy/$len;
1611	}
1612
1613	function length()
1614	{
1615		return( sqrt(($this->dx)*($this->dx) + ($this->dy)*($this->dy)) );
1616	}
1617}
1618
1619class Colour
1620{
1621	var $r,$g,$b, $alpha;
1622
1623
1624	// take in an existing value and create a Colour object for it
1625	function Colour()
1626	{
1627		if(func_num_args() == 3) # a set of 3 colours
1628		{
1629			$this->r = func_get_arg(0); # r
1630			$this->g = func_get_arg(1); # g
1631			$this->b = func_get_arg(2); # b
1632			#print "3 args";
1633			#print $this->as_string()."--";
1634		}
1635
1636		if( (func_num_args() == 1) && gettype(func_get_arg(0))=='array' ) # an array of 3 colours
1637		{
1638			#print "1 args";
1639			$ary = func_get_arg(0);
1640			$this->r = $ary[0];
1641			$this->g = $ary[1];
1642			$this->b = $ary[2];
1643		}
1644	}
1645
1646	// Is this a transparent/none colour?
1647	function is_real()
1648	{
1649		if($this->r >= 0 && $this->g >=0 && $this->b >= 0)
1650		{
1651			return true;
1652		}
1653		else
1654		{
1655			return false;
1656		}
1657	}
1658
1659	// Is this a transparent/none colour?
1660	function is_none()
1661	{
1662		if($this->r == -1 && $this->g == -1 && $this->b == -1)
1663		{
1664			return true;
1665		}
1666		else
1667		{
1668			return false;
1669		}
1670	}
1671
1672	// Is this a contrast colour?
1673	function is_contrast()
1674	{
1675		if($this->r == -3 && $this->g == -3 && $this->b == -3)
1676		{
1677			return true;
1678		}
1679		else
1680		{
1681			return false;
1682		}
1683	}
1684
1685	// Is this a copy colour?
1686	function is_copy()
1687	{
1688		if($this->r == -2 && $this->g == -2 && $this->b == -2)
1689		{
1690			return true;
1691		}
1692		else
1693		{
1694			return false;
1695		}
1696	}
1697
1698	// allocate a colour in the appropriate image context
1699	// - things like scale colours are used in multiple images now (the scale, several nodes, the main map...)
1700	function gdallocate($image_ref)
1701	{
1702		if($this->is_none())
1703		{
1704			return NULL;
1705		}
1706		else
1707		{
1708			return(myimagecolorallocate($image_ref, $this->r, $this->g, $this->b));
1709		}
1710	}
1711
1712	// based on an idea from: http://www.bennadel.com/index.cfm?dax=blog:902.view
1713	function contrast_ary()
1714	{
1715		if( (($this->r + $this->g + $this->b) > 500)
1716		 || ($this->g > 140)
1717		)
1718		{
1719			return( array(0,0,0) );
1720		}
1721		else
1722		{
1723			return( array(255,255,255) );
1724		}
1725	}
1726
1727	function contrast()
1728	{
1729		return( new Colour($this->contrast_ary() ) );
1730	}
1731
1732	// make a printable version, for debugging
1733	// - optionally take a format string, so we can use it for other things (like WriteConfig, or hex in stylesheets)
1734	function as_string($format = "RGB(%d,%d,%d)")
1735	{
1736		return (sprintf($format, $this->r, $this->g, $this->b));
1737	}
1738
1739	function as_config()
1740	{
1741		return $this->as_string("%d %d %d");
1742	}
1743
1744	function as_html()
1745	{
1746		if($this->is_real())
1747		{
1748			return $this->as_string("#%02x%02x%02x");
1749		}
1750		else
1751		{
1752			return "";
1753		}
1754	}
1755}
1756
1757// A series of wrapper functions around all the GD function calls
1758// - I added these in so I could make a 'metafile' easily of all the
1759//   drawing commands for a map. I have a basic Perl-Cairo script that makes
1760//   anti-aliased maps from these, using Cairo instead of GD.
1761
1762function metadump($string, $truncate=FALSE)
1763{
1764	// comment this line to get a metafile for this map
1765	return;
1766
1767	if($truncate)
1768	{
1769		$fd = fopen("metadump.txt","w+");
1770	}
1771	else
1772	{
1773		$fd = fopen("metadump.txt","a");
1774	}
1775	fputs($fd,$string."\n");
1776	fclose($fd);
1777}
1778
1779function metacolour(&$col)
1780{
1781	return ($col['red1']." ".$col['green1']." ".$col['blue1']);
1782}
1783
1784function wimagecreate($width,$height)
1785{
1786	metadump("NEWIMAGE $width $height");
1787	return(imagecreate($width,$height));
1788}
1789
1790function wimagefilledrectangle( $image ,$x1, $y1, $x2, $y2, $color )
1791{
1792	if ($color===NULL) return;
1793
1794	$col = imagecolorsforindex($image, $color);
1795	$r = $col['red']; $g = $col['green']; $b = $col['blue']; $a = $col['alpha'];
1796	$r = $r/255; $g=$g/255; $b=$b/255; $a=(127-$a)/127;
1797
1798	metadump("FRECT $x1 $y1 $x2 $y2 $r $g $b $a");
1799	return(imagefilledrectangle( $image ,$x1, $y1, $x2, $y2, $color ));
1800}
1801
1802function wimagerectangle( $image ,$x1, $y1, $x2, $y2, $color )
1803{
1804	if ($color===NULL) return;
1805
1806	$col = imagecolorsforindex($image, $color);
1807	$r = $col['red']; $g = $col['green']; $b = $col['blue']; $a = $col['alpha'];
1808	$r = $r/255; $g=$g/255; $b=$b/255; $a=(127-$a)/127;
1809
1810	metadump("RECT $x1 $y1 $x2 $y2 $r $g $b $a");
1811	return(imagerectangle( $image ,$x1, $y1, $x2, $y2, $color ));
1812}
1813
1814function wimagepolygon($image, $points, $num_points, $color)
1815{
1816	if ($color===NULL) return;
1817
1818	$col = imagecolorsforindex($image, $color);
1819	$r = $col['red']; $g = $col['green']; $b = $col['blue']; $a = $col['alpha'];
1820	$r = $r/255; $g=$g/255; $b=$b/255; $a=(127-$a)/127;
1821
1822	$pts = "";
1823	for ($i=0; $i < $num_points; $i++)
1824        {
1825		$pts .= $points[$i * 2]." ";
1826		$pts .= $points[$i * 2+1]." ";
1827        }
1828
1829	metadump("POLY $num_points ".$pts." $r $g $b $a");
1830
1831	return(imagepolygon($image, $points, $num_points, $color));
1832}
1833
1834function wimagefilledpolygon($image, $points, $num_points, $color)
1835{
1836	if ($color===NULL) return;
1837
1838	$col = imagecolorsforindex($image, $color);
1839	$r = $col['red']; $g = $col['green']; $b = $col['blue']; $a = $col['alpha'];
1840	$r = $r/255; $g=$g/255; $b=$b/255; $a=(127-$a)/127;
1841
1842	$pts = "";
1843	for ($i=0; $i < $num_points; $i++)
1844        {
1845		$pts .= $points[$i * 2]." ";
1846		$pts .= $points[$i * 2+1]." ";
1847        }
1848
1849	metadump("FPOLY $num_points ".$pts." $r $g $b $a");
1850
1851	return(imagefilledpolygon($image, $points, $num_points, $color));
1852}
1853
1854function wimagecreatetruecolor($width, $height)
1855{
1856
1857
1858	metadump("BLANKIMAGE $width $height");
1859
1860	return imagecreatetruecolor($width,$height);
1861
1862}
1863
1864function wimagettftext($image, $size, $angle, $x, $y, $color, $file, $string)
1865{
1866	if ($color===NULL) return;
1867
1868	$col = imagecolorsforindex($image, $color);
1869	$r = $col['red']; $g = $col['green']; $b = $col['blue']; $a = $col['alpha'];
1870	$r = $r/255; $g=$g/255; $b=$b/255; $a=(127-$a)/127;
1871
1872	metadump("TEXT $x $y $angle $size $file $r $g $b $a $string");
1873
1874	return(imagettftext($image, $size, $angle, $x, $y, $color, $file, $string));
1875}
1876
1877function wm_draw_marker_diamond($im, $col, $x, $y, $size=10)
1878{
1879	$points = array();
1880
1881	$points []= $x-$size;
1882	$points []= $y;
1883
1884	$points []= $x;
1885	$points []= $y-$size;
1886
1887	$points []= $x+$size;
1888	$points []= $y;
1889
1890	$points []= $x;
1891	$points []= $y+$size;
1892
1893	$num_points = 4;
1894
1895	imagepolygon($im, $points, $num_points, $col);
1896}
1897
1898function wm_draw_marker_box($im, $col, $x, $y, $size=10)
1899{
1900	$points = array();
1901
1902	$points []= $x-$size;
1903	$points []= $y-$size;
1904
1905	$points []= $x+$size;
1906	$points []= $y-$size;
1907
1908	$points []= $x+$size;
1909	$points []= $y+$size;
1910
1911	$points []= $x-$size;
1912	$points []= $y+$size;
1913
1914	$num_points = 4;
1915
1916	imagepolygon($im, $points, $num_points, $col);
1917}
1918
1919function wm_draw_marker_circle($im, $col, $x, $y, $size=10)
1920{
1921	imagearc($im,$x, $y ,$size,$size,0,360,$col);
1922}
1923
1924function draw_spine_chain($im,$spine,$col, $size=10)
1925{
1926    $newn = count($spine);
1927
1928    for ($i=0; $i < $newn; $i++)
1929    {
1930		imagearc($im,$spine[$i][X],$spine[$i][Y],$size,$size,0,360,$col);
1931    }
1932}
1933
1934function dump_spine($spine)
1935{
1936	print "===============\n";
1937	for($i=0; $i<count($spine); $i++)
1938	{
1939		printf ("  %3d: %d,%d (%d)\n", $i, $spine[$i][X], $spine[$i][Y], $spine[$i][DISTANCE] );
1940	}
1941	print "===============\n";
1942}
1943
1944function draw_spine($im, $spine,$col)
1945{
1946    $max_i = count($spine)-1;
1947
1948    for ($i=0; $i <$max_i; $i++)
1949    {
1950        imageline($im,
1951                    $spine[$i][X],$spine[$i][Y],
1952                    $spine[$i+1][X],$spine[$i+1][Y],
1953                    $col
1954                    );
1955    }
1956}
1957
1958// vim:ts=4:sw=4:
1959?>
1960