1<?php 2/* 3 * Copyright (c) 2012-2018 Mark C. Prins <mprins@users.sf.net> 4 * 5 * In part based on staticMapLite 0.03 available at http://staticmaplite.svn.sourceforge.net/viewvc/staticmaplite/ 6 * 7 * Copyright (c) 2009 Gerhard Koch <gerhard.koch AT ymail.com> 8 * 9 * Licensed under the Apache License, Version 2.0 (the "License"); 10 * you may not use this file except in compliance with the License. 11 * You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 */ 21 22// phpcs:disable PSR1.Files.SideEffects 23// TODO resolve side effect 24include_once(realpath(__DIR__) . '/../geophp/geoPHP/geoPHP.inc'); 25 26/** 27 * 28 * @author Mark C. Prins <mprins@users.sf.net> 29 * @author Gerhard Koch <gerhard.koch AT ymail.com> 30 * 31 */ 32class StaticMap { 33 34 // the final output 35 private $tileSize = 256; 36 private $tileInfo = array( 37 // OSM sources 38 'openstreetmap' => array( 39 'txt' => '(c) OpenStreetMap data', 40 'logo' => 'osm_logo.png', 41 'url' => 'https://tile.openstreetmap.org/{Z}/{X}/{Y}.png' 42 ), 43 // OCM sources 44 'cycle' => array( 45 'txt' => '(c) Thunderforest maps', 46 'logo' => 'tf_logo.png', 47 'url' => 'https://tile.thunderforest.com/cycle/{Z}/{X}/{Y}.png' 48 ), 49 'transport' => array( 50 'txt' => '(c) Thunderforest maps', 51 'logo' => 'tf_logo.png', 52 'url' => 'https://tile.thunderforest.com/transport/{Z}/{X}/{Y}.png' 53 ), 54 'landscape' => array( 55 'txt' => '(c) Thunderforest maps', 56 'logo' => 'tf_logo.png', 57 'url' => 'https://tile.thunderforest.com/landscape/{Z}/{X}/{Y}.png' 58 ), 59 'outdoors' => array( 60 'txt' => '(c) Thunderforest maps', 61 'logo' => 'tf_logo.png', 62 'url' => 'https://tile.thunderforest.com/outdoors/{Z}/{X}/{Y}.png' 63 ), 64 'toner-lite' => array( 65 'txt' => 'Stamen tiles', 66 'logo' => 'stamen.png', 67 'url' => 'http://tile.stamen.com/toner-lite/{Z}/{X}/{Y}.png' 68 ), 69 'terrain' => array( 70 'txt' => 'Stamen tiles', 71 'logo' => 'stamen.png', 72 'url' => 'http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png' 73 ) 74 //, 75 // 'piste'=>array( 76 // 'txt'=>'OpenPisteMap tiles', 77 // 'logo'=>'piste_logo.png', 78 // 'url'=>''), 79 // 'sea'=>array( 80 // 'txt'=>'OpenSeaMap tiles', 81 // 'logo'=>'sea_logo.png', 82 // 'url'=>''), 83 // H&B sources 84 // 'hikeandbike' => array ( 85 // 'txt' => 'Hike & Bike Map', 86 // 'logo' => 'hnb_logo.png', 87 // //'url' => 'http://toolserver.org/tiles/hikebike/{Z}/{X}/{Y}.png' 88 // //moved to: https://www.toolserver.org/tiles/hikebike/12/2105/1388.png 89 // 'url' => 'http://c.tiles.wmflabs.org/hikebike/{Z}/{X}/{Y}.png' 90 // ) 91 ); 92 private $tileDefaultSrc = 'openstreetmap'; 93 94 // set up markers 95 private $markerPrototypes = array( 96 // found at http://www.mapito.net/map-marker-icons.html 97 // these are 17x19 px with a pointer at the bottom left 98 'lightblue' => array( 99 'regex' => '/^lightblue([0-9]+)$/', 100 'extension' => '.png', 101 'shadow' => false, 102 'offsetImage' => '0,-19', 103 'offsetShadow' => false 104 ), 105 // openlayers std markers are 21x25px with shadow 106 'ol-marker' => array( 107 'regex' => '/^marker(|-blue|-gold|-green|-red)+$/', 108 'extension' => '.png', 109 'shadow' => 'marker_shadow.png', 110 'offsetImage' => '-10,-25', 111 'offsetShadow' => '-1,-13' 112 ), 113 // these are 16x16 px 114 'ww_icon' => array( 115 'regex' => '/ww_\S+$/', 116 'extension' => '.png', 117 'shadow' => false, 118 'offsetImage' => '-8,-8', 119 'offsetShadow' => false 120 ), 121 // assume these are 16x16 px 122 'rest' => array( 123 'regex' => '/^(?!lightblue([0-9]+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/', 124 'extension' => '.png', 125 'shadow' => 'marker_shadow.png', 126 'offsetImage' => '-8,-8', 127 'offsetShadow' => '-1,-1' 128 ) 129 ); 130 private $centerX; 131 private $centerY; 132 private $offsetX; 133 private $offsetY; 134 private $image; 135 private $zoom; 136 private $lat; 137 private $lon; 138 private $width; 139 private $height; 140 private $markers; 141 private $maptype; 142 private $kmlFileName; 143 private $gpxFileName; 144 private $geojsonFileName; 145 private $autoZoomExtent; 146 private $apikey; 147 private $tileCacheBaseDir; 148 private $mapCacheBaseDir; 149 private $mediaBaseDir; 150 private $useTileCache = true; 151 private $mapCacheID = ''; 152 private $mapCacheFile = ''; 153 private $mapCacheExtension = 'png'; 154 155 /** 156 * Constructor. 157 * 158 * @param float $lat 159 * Latitude (x) of center of map 160 * @param float $lon 161 * Longitude (y) of center of map 162 * @param int $zoom 163 * Zoomlevel 164 * @param int $width 165 * Width in pixels 166 * @param int $height 167 * Height in pixels 168 * @param string $maptype 169 * Name of the map 170 * @param array $markers 171 * array of markers 172 * @param string $gpx 173 * GPX filename 174 * @param string $kml 175 * KML filename 176 * @param string $mediaDir 177 * Directory to store/cache maps 178 * @param string $tileCacheBaseDir 179 * Directory to cache map tiles 180 * @param bool $autoZoomExtent 181 * Wheter or not to override zoom/lat/lon and zoom to the extent of gpx/kml and markers 182 * @param string apikey 183 * Some service require a key to access 184 */ 185 public function __construct( 186 float $lat, 187 float $lon, 188 int $zoom, 189 int $width, 190 int $height, 191 string $maptype, 192 array $markers, 193 string $gpx, 194 string $kml, 195 string $geojson, 196 string $mediaDir, 197 string $tileCacheBaseDir, 198 bool $autoZoomExtent = true, 199 string $apikey 200 ) { 201 $this->zoom = $zoom; 202 $this->lat = $lat; 203 $this->lon = $lon; 204 $this->width = $width; 205 $this->height = $height; 206 // validate + set maptype 207 $this->maptype = $this->tileDefaultSrc; 208 if(array_key_exists($maptype, $this->tileInfo)) { 209 $this->maptype = $maptype; 210 } 211 $this->markers = $markers; 212 $this->kmlFileName = $kml; 213 $this->gpxFileName = $gpx; 214 $this->geojsonFileName = $geojson; 215 $this->mediaBaseDir = $mediaDir; 216 $this->tileCacheBaseDir = $tileCacheBaseDir . '/olmaptiles'; 217 $this->useTileCache = $this->tileCacheBaseDir !== ''; 218 $this->mapCacheBaseDir = $mediaDir . '/olmapmaps'; 219 $this->autoZoomExtent = $autoZoomExtent; 220 $this->apikey = $apikey; 221 } 222 223 /** 224 * get the map, this may return a reference to a cached copy. 225 * 226 * @return string url relative to media dir 227 */ 228 public function getMap(): string { 229 try { 230 if($this->autoZoomExtent) { 231 $this->autoZoom(); 232 } 233 } catch(Exception $e) { 234 dbglog($e); 235 } 236 237 // use map cache, so check cache for map 238 if(!$this->checkMapCache()) { 239 // map is not in cache, needs to be build 240 $this->makeMap(); 241 $this->mkdirRecursive(dirname($this->mapCacheIDToFilename()), 0777); 242 imagepng($this->image, $this->mapCacheIDToFilename(), 9); 243 } 244 $doc = ''; 245 $doc = $this->mapCacheIDToFilename(); 246 // make url relative to media dir 247 return str_replace($this->mediaBaseDir, '', $doc); 248 } 249 250 /** 251 * Calculate the lat/lon/zoom values to make sure that all of the markers and gpx/kml are on the map. 252 * can throw an error like 253 * "Fatal error: Uncaught Exception: Cannot create a collection with non-geometries in 254 * D:\www\wild-water.nl\www\dokuwiki\lib\plugins\geophp\geoPHP\lib\geometry\Collection.class.php:29" 255 * 256 * @param float $paddingFactor 257 * buffer constant to enlarge (>1.0) the zoom level 258 */ 259 private function autoZoom(float $paddingFactor = 1.0): void { 260 $geoms = array(); 261 $geoms [] = new Point ($this->lon, $this->lat); 262 if(!empty ($this->markers)) { 263 foreach($this->markers as $marker) { 264 $geoms [] = new Point ($marker ['lon'], $marker ['lat']); 265 } 266 } 267 $g = false; 268 if(file_exists($this->kmlFileName)) { 269 $g = geoPHP::load(file_get_contents($this->kmlFileName), 'kml'); 270 if($g !== false) { 271 $geoms [] = $g; 272 } 273 } 274 if(file_exists($this->gpxFileName)) { 275 $g = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx'); 276 if($g !== false) { 277 $geoms [] = $g; 278 } 279 } 280 if(file_exists($this->geojsonFileName)) { 281 $g = geoPHP::load(file_get_contents($this->geojsonFileName), 'geojson'); 282 if($g !== false) { 283 $geoms [] = $g; 284 } 285 } 286 287 if(count($geoms) <= 1) { 288 dbglog($geoms, "StaticMap::autoZoom: Skip setting autozoom options"); 289 return; 290 } 291 292 $geom = new GeometryCollection ($geoms); 293 $centroid = $geom->centroid(); 294 $bbox = $geom->getBBox(); 295 296 // determine vertical resolution, this depends on the distance from the equator 297 // $vy00 = log(tan(M_PI*(0.25 + $centroid->getY()/360))); 298 $vy0 = log(tan(M_PI * (0.25 + $bbox ['miny'] / 360))); 299 $vy1 = log(tan(M_PI * (0.25 + $bbox ['maxy'] / 360))); 300 dbglog("StaticMap::autoZoom: vertical resolution: $vy0, $vy1"); 301 $zoomFactorPowered = ($this->height / 2) / (40.7436654315252 * ($vy1 - $vy0)); 302 $resolutionVertical = 360 / ($zoomFactorPowered * $this->tileSize); 303 // determine horizontal resolution 304 $resolutionHorizontal = ($bbox ['maxx'] - $bbox ['minx']) / $this->width; 305 $resolution = max($resolutionHorizontal, $resolutionVertical) * $paddingFactor; 306 $zoom = log(360 / ($resolution * $this->tileSize), 2); 307 308 if(is_finite($zoom) && $zoom < 15 && $zoom > 2) { 309 $this->zoom = floor($zoom); 310 } 311 $this->lon = $centroid->getX(); 312 $this->lat = $centroid->getY(); 313 dbglog("StaticMap::autoZoom: Set autozoom options to: z: $this->zoom, lon: $this->lon, lat: $this->lat"); 314 } 315 316 public function checkMapCache(): bool { 317 // side effect: set the mapCacheID 318 $this->mapCacheID = md5($this->serializeParams()); 319 $filename = $this->mapCacheIDToFilename(); 320 return file_exists($filename); 321 } 322 323 public function serializeParams() { 324 return join( 325 "&", array( 326 $this->zoom, 327 $this->lat, 328 $this->lon, 329 $this->width, 330 $this->height, 331 serialize($this->markers), 332 $this->maptype, 333 $this->kmlFileName, 334 $this->gpxFileName, 335 $this->geojsonFileName 336 ) 337 ); 338 } 339 340 public function mapCacheIDToFilename(): string { 341 if(!$this->mapCacheFile) { 342 $this->mapCacheFile = $this->mapCacheBaseDir . "/" . $this->maptype . "/" . $this->zoom . "/cache_" 343 . substr($this->mapCacheID, 0, 2) . "/" . substr($this->mapCacheID, 2, 2) 344 . "/" . substr($this->mapCacheID, 4); 345 } 346 return $this->mapCacheFile . "." . $this->mapCacheExtension; 347 } 348 349 /** 350 * make the map. 351 */ 352 public function makeMap(): void { 353 $this->initCoords(); 354 $this->createBaseMap(); 355 if(!empty ($this->markers)) 356 $this->placeMarkers(); 357 if(file_exists($this->kmlFileName)) 358 $this->drawKML(); 359 if(file_exists($this->gpxFileName)) 360 $this->drawGPX(); 361 if(file_exists($this->geojsonFileName)) 362 $this->drawGeojson(); 363 364 $this->drawCopyright(); 365 } 366 367 /** 368 */ 369 public function initCoords(): void { 370 $this->centerX = $this->lonToTile($this->lon, $this->zoom); 371 $this->centerY = $this->latToTile($this->lat, $this->zoom); 372 $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize); 373 $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize); 374 } 375 376 /** 377 * 378 * @param float $long 379 * @param int $zoom 380 * @return float|int 381 */ 382 public function lonToTile(float $long, int $zoom) { 383 return (($long + 180) / 360) * pow(2, $zoom); 384 } 385 386 /** 387 * 388 * @param float $lat 389 * @param int $zoom 390 * @return float|int 391 */ 392 public function latToTile(float $lat, int $zoom) { 393 return (1 - log(tan($lat * pi() / 180) + 1 / cos($lat * M_PI / 180)) / M_PI) / 2 * pow(2, $zoom); 394 } 395 396 /** 397 * make basemap image. 398 */ 399 public function createBaseMap(): void { 400 $this->image = imagecreatetruecolor($this->width, $this->height); 401 $startX = floor($this->centerX - ($this->width / $this->tileSize) / 2); 402 $startY = floor($this->centerY - ($this->height / $this->tileSize) / 2); 403 $endX = ceil($this->centerX + ($this->width / $this->tileSize) / 2); 404 $endY = ceil($this->centerY + ($this->height / $this->tileSize) / 2); 405 $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize); 406 $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize); 407 $this->offsetX += floor($this->width / 2); 408 $this->offsetY += floor($this->height / 2); 409 $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize; 410 $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize; 411 412 for($x = $startX; $x <= $endX; $x++) { 413 for($y = $startY; $y <= $endY; $y++) { 414 $url = str_replace( 415 array( 416 '{Z}', 417 '{X}', 418 '{Y}' 419 ), array( 420 $this->zoom, 421 $x, 422 $y 423 ), $this->tileInfo [$this->maptype] ['url'] 424 ); 425 426 $tileData = $this->fetchTile($url); 427 if($tileData) { 428 $tileImage = imagecreatefromstring($tileData); 429 } else { 430 $tileImage = imagecreate($this->tileSize, $this->tileSize); 431 $color = imagecolorallocate($tileImage, 255, 255, 255); 432 @imagestring($tileImage, 1, 127, 127, 'err', $color); 433 } 434 $destX = ($x - $startX) * $this->tileSize + $this->offsetX; 435 $destY = ($y - $startY) * $this->tileSize + $this->offsetY; 436 dbglog($this->tileSize, "imagecopy tile into image: $destX, $destY"); 437 imagecopy( 438 $this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, 439 $this->tileSize 440 ); 441 } 442 } 443 } 444 445 /** 446 * Fetch a tile and (if configured) store it in the cache. 447 * 448 * @param string $url 449 */ 450 public function fetchTile($url) { 451 if($this->useTileCache && ($cached = $this->checkTileCache($url))) 452 return $cached; 453 454 $_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; ' . PHP_OS . ')'; 455 if(function_exists("curl_init")) { 456 // use cUrl 457 $ch = curl_init(); 458 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 459 curl_setopt($ch, CURLOPT_USERAGENT, $_UA); 460 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); 461 curl_setopt($ch, CURLOPT_URL, $url . $this->apikey); 462 $tile = curl_exec($ch); 463 curl_close($ch); 464 } else { 465 // use file_get_contents 466 global $conf; 467 $opts = array( 468 'http' => array( 469 'method' => "GET", 470 'header' => "Accept-language: en\r\n" . "User-Agent: $_UA\r\n" . "accept: image/png\r\n", 471 'proxy' => "tcp://" . $conf ['proxy'] ['host'] . ":" . $conf ['proxy'] ['port'], 472 'request_fulluri' => true 473 ) 474 ); 475 $context = stream_context_create($opts); 476 $tile = file_get_contents($url . $this->apikey, false, $context); 477 } 478 if($tile && $this->useTileCache) { 479 $this->writeTileToCache($url, $tile); 480 } 481 return $tile; 482 } 483 484 /** 485 * 486 * @param string $url 487 */ 488 public function checkTileCache($url) { 489 $filename = $this->tileUrlToFilename($url); 490 if(file_exists($filename)) { 491 return file_get_contents($filename); 492 } 493 } 494 495 /** 496 * 497 * @param string $url 498 * @return string 499 */ 500 public function tileUrlToFilename(string $url): string { 501 return $this->tileCacheBaseDir . "/" . str_replace( 502 array( 503 'http://' 504 ), '', $url 505 ); 506 } 507 508 /** 509 * Write a tile into the cache. 510 * 511 * @param string $url 512 * @param mixed $data 513 */ 514 public function writeTileToCache($url, $data): void { 515 $filename = $this->tileUrlToFilename($url); 516 $this->mkdirRecursive(dirname($filename), 0777); 517 file_put_contents($filename, $data); 518 } 519 520 /** 521 * Recursively create the directory. 522 * 523 * @param string $pathname 524 * The directory path. 525 * @param int $mode 526 * File access mode. For more information on modes, read the details on the chmod manpage. 527 */ 528 public function mkdirRecursive(string $pathname, int $mode): bool { 529 is_dir(dirname($pathname)) || $this->mkdirRecursive(dirname($pathname), $mode); 530 return is_dir($pathname) || mkdir($pathname, $mode) || is_dir($pathname); 531 } 532 533 /** 534 * Place markers on the map and number them in the same order as they are listed in the html. 535 */ 536 public function placeMarkers(): void { 537 $count = 0; 538 $color = imagecolorallocate($this->image, 0, 0, 0); 539 $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 540 $markerBaseDir = __DIR__ . '/icons'; 541 // loop thru marker array 542 foreach($this->markers as $marker) { 543 // set some local variables 544 $markerLat = $marker ['lat']; 545 $markerLon = $marker ['lon']; 546 $markerType = $marker ['type']; 547 // clear variables from previous loops 548 $markerFilename = ''; 549 $markerShadow = ''; 550 $matches = false; 551 // check for marker type, get settings from markerPrototypes 552 if($markerType) { 553 foreach($this->markerPrototypes as $markerPrototype) { 554 if(preg_match($markerPrototype ['regex'], $markerType, $matches)) { 555 $markerFilename = $matches [0] . $markerPrototype ['extension']; 556 if($markerPrototype ['offsetImage']) { 557 list ($markerImageOffsetX, $markerImageOffsetY) = explode( 558 ",", 559 $markerPrototype ['offsetImage'] 560 ); 561 } 562 $markerShadow = $markerPrototype ['shadow']; 563 if($markerShadow) { 564 list ($markerShadowOffsetX, $markerShadowOffsetY) = explode( 565 ",", 566 $markerPrototype ['offsetShadow'] 567 ); 568 } 569 } 570 } 571 } 572 // create img resource 573 if(file_exists($markerBaseDir . '/' . $markerFilename)) { 574 $markerImg = imagecreatefrompng($markerBaseDir . '/' . $markerFilename); 575 } else { 576 $markerImg = imagecreatefrompng($markerBaseDir . '/marker.png'); 577 } 578 // check for shadow + create shadow recource 579 if($markerShadow && file_exists($markerBaseDir . '/' . $markerShadow)) { 580 $markerShadowImg = imagecreatefrompng($markerBaseDir . '/' . $markerShadow); 581 } 582 // calc position 583 $destX = floor( 584 ($this->width / 2) - 585 $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom)) 586 ); 587 $destY = floor( 588 ($this->height / 2) - 589 $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom)) 590 ); 591 // copy shadow on basemap 592 if($markerShadow && $markerShadowImg) { 593 imagecopy( 594 $this->image, 595 $markerShadowImg, 596 $destX + (int) $markerShadowOffsetX, 597 $destY + (int) $markerShadowOffsetY, 598 0, 599 0, 600 imagesx($markerShadowImg), 601 imagesy($markerShadowImg) 602 ); 603 } 604 // copy marker on basemap above shadow 605 imagecopy( 606 $this->image, 607 $markerImg, 608 $destX + (int) $markerImageOffsetX, 609 $destY + (int) $markerImageOffsetY, 610 0, 611 0, 612 imagesx($markerImg), 613 imagesy($markerImg) 614 ); 615 // add label 616 imagestring( 617 $this->image, 618 3, 619 $destX - imagesx($markerImg) + 1, 620 $destY + (int) $markerImageOffsetY + 1, 621 ++$count, 622 $bgcolor 623 ); 624 imagestring( 625 $this->image, 626 3, 627 $destX - imagesx($markerImg), 628 $destY + (int) $markerImageOffsetY, 629 $count, 630 $color 631 ); 632 } 633 } 634 635 /** 636 * Draw kml trace on the map. 637 */ 638 public function drawKML(): void { 639 // TODO get colour from kml node (not currently supported in geoPHP) 640 $col = imagecolorallocatealpha($this->image, 255, 0, 0, .4 * 127); 641 $kmlgeom = geoPHP::load(file_get_contents($this->kmlFileName), 'kml'); 642 $this->drawGeometry($kmlgeom, $col); 643 } 644 645 /** 646 * Draw geometry or geometry collection on the map. 647 * 648 * @param Geometry $geom 649 * @param int $colour 650 * drawing colour 651 */ 652 private function drawGeometry(Geometry $geom, int $colour): void { 653 if(empty($geom)) { 654 return; 655 } 656 657 switch($geom->geometryType()) { 658 case 'GeometryCollection' : 659 // recursively draw part of the collection 660 for($i = 1; $i < $geom->numGeometries() + 1; $i++) { 661 $_geom = $geom->geometryN($i); 662 $this->drawGeometry($_geom, $colour); 663 } 664 break; 665 case 'MultiPolygon' : 666 case 'MultiLineString' : 667 case 'MultiPoint' : 668 // TODO implement / do nothing 669 break; 670 case 'Polygon' : 671 $this->drawPolygon($geom, $colour); 672 break; 673 case 'LineString' : 674 $this->drawLineString($geom, $colour); 675 break; 676 case 'Point' : 677 $this->drawPoint($geom, $colour); 678 break; 679 default : 680 // draw nothing 681 break; 682 } 683 } 684 685 /** 686 * Draw a polygon on the map. 687 * 688 * @param Polygon $polygon 689 * @param int $colour 690 * drawing colour 691 */ 692 private function drawPolygon($polygon, $colour) { 693 // TODO implementation of drawing holes, 694 // maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol? 695 696 // print_r('Polygon:<br />'); 697 // print_r($polygon); 698 $extPoints = array(); 699 // extring is a linestring actually.. 700 $extRing = $polygon->exteriorRing(); 701 702 for($i = 1; $i < $extRing->numGeometries(); $i++) { 703 $p1 = $extRing->geometryN($i); 704 $x = floor( 705 ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom)) 706 ); 707 $y = floor( 708 ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom)) 709 ); 710 $extPoints [] = $x; 711 $extPoints [] = $y; 712 } 713 // print_r('points:('.($i-1).')<br />'); 714 // print_r($extPoints); 715 // imagepolygon ($this->image, $extPoints, $i-1, $colour ); 716 imagefilledpolygon($this->image, $extPoints, $i - 1, $colour); 717 } 718 719 /** 720 * Draw a line on the map. 721 * 722 * @param LineString $line 723 * @param int $colour 724 * drawing colour 725 */ 726 private function drawLineString($line, $colour) { 727 imagesetthickness($this->image, 2); 728 for($p = 1; $p < $line->numGeometries(); $p++) { 729 // get first pair of points 730 $p1 = $line->geometryN($p); 731 $p2 = $line->geometryN($p + 1); 732 // translate to paper space 733 $x1 = floor( 734 ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p1->x(), $this->zoom)) 735 ); 736 $y1 = floor( 737 ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p1->y(), $this->zoom)) 738 ); 739 $x2 = floor( 740 ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($p2->x(), $this->zoom)) 741 ); 742 $y2 = floor( 743 ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($p2->y(), $this->zoom)) 744 ); 745 // draw to image 746 imageline($this->image, $x1, $y1, $x2, $y2, $colour); 747 } 748 imagesetthickness($this->image, 1); 749 } 750 751 /** 752 * Draw a point on the map. 753 * 754 * @param Point $point 755 * @param int $colour 756 * drawing colour 757 */ 758 private function drawPoint($point, $colour) { 759 imagesetthickness($this->image, 2); 760 // translate to paper space 761 $cx = floor( 762 ($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($point->x(), $this->zoom)) 763 ); 764 $cy = floor( 765 ($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($point->y(), $this->zoom)) 766 ); 767 $r = 5; 768 // draw to image 769 // imageellipse($this->image, $cx, $cy,$r, $r, $colour); 770 imagefilledellipse($this->image, $cx, $cy, $r, $r, $colour); 771 // don't use imageellipse because the imagesetthickness function has 772 // no effect. So the better workaround is to use imagearc. 773 imagearc($this->image, $cx, $cy, $r, $r, 0, 359, $colour); 774 imagesetthickness($this->image, 1); 775 } 776 777 /** 778 * Draw gpx trace on the map. 779 */ 780 public function drawGPX() { 781 $col = imagecolorallocatealpha($this->image, 0, 0, 255, .4 * 127); 782 $gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName), 'gpx'); 783 $this->drawGeometry($gpxgeom, $col); 784 } 785 786 /** 787 * Draw geojson on the map. 788 */ 789 public function drawGeojson() { 790 $col = imagecolorallocatealpha($this->image, 255, 0, 255, .4 * 127); 791 $gpxgeom = geoPHP::load(file_get_contents($this->geojsonFileName), 'json'); 792 $this->drawGeometry($gpxgeom, $col); 793 } 794 795 /** 796 * add copyright and origin notice and icons to the map. 797 */ 798 public function drawCopyright() { 799 $logoBaseDir = dirname(__FILE__) . '/' . 'logo/'; 800 $logoImg = imagecreatefrompng($logoBaseDir . $this->tileInfo ['openstreetmap'] ['logo']); 801 $textcolor = imagecolorallocate($this->image, 0, 0, 0); 802 $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 803 804 imagecopy( 805 $this->image, 806 $logoImg, 807 0, 808 imagesy($this->image) - imagesy($logoImg), 809 0, 810 0, 811 imagesx($logoImg), 812 imagesy($logoImg) 813 ); 814 imagestring( 815 $this->image, 816 1, 817 imagesx($logoImg) + 2, 818 imagesy($this->image) - imagesy($logoImg) + 1, 819 $this->tileInfo ['openstreetmap'] ['txt'], 820 $bgcolor 821 ); 822 imagestring( 823 $this->image, 824 1, 825 imagesx($logoImg) + 1, 826 imagesy($this->image) - imagesy($logoImg), 827 $this->tileInfo ['openstreetmap'] ['txt'], 828 $textcolor 829 ); 830 831 // additional tile source info, ie. who created/hosted the tiles 832 if($this->maptype != 'openstreetmap') { 833 $iconImg = imagecreatefrompng($logoBaseDir . $this->tileInfo [$this->maptype] ['logo']); 834 imagecopy( 835 $this->image, 836 $iconImg, imagesx($logoImg) + 1, 837 imagesy($this->image) - imagesy($iconImg), 838 0, 839 0, 840 imagesx($iconImg), imagesy($iconImg) 841 ); 842 imagestring( 843 $this->image, 844 1, imagesx($logoImg) + imagesx($iconImg) + 4, 845 imagesy($this->image) - ceil(imagesy($logoImg) / 2) + 1, 846 $this->tileInfo [$this->maptype] ['txt'], 847 $bgcolor 848 ); 849 imagestring( 850 $this->image, 851 1, imagesx($logoImg) + imagesx($iconImg) + 3, 852 imagesy($this->image) - ceil(imagesy($logoImg) / 2), 853 $this->tileInfo [$this->maptype] ['txt'], 854 $textcolor 855 ); 856 } 857 } 858} 859