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