1<?php 2 3/* 4 * Copyright (c) 2012 Mark C. Prins <mprins@users.sf.net> 5* 6* Based on staticMapLite 0.03 available at http://staticmaplite.svn.sourceforge.net/viewvc/staticmaplite/ 7* 8* Copyright (c) 2009 Gerhard Koch <gerhard.koch AT ymail.com> 9* 10* Licensed under the Apache License, Version 2.0 (the "License"); 11* you may not use this file except in compliance with the License. 12* You may obtain a copy of the License at 13* 14* http://www.apache.org/licenses/LICENSE-2.0 15* 16* Unless required by applicable law or agreed to in writing, software 17* distributed under the License is distributed on an "AS IS" BASIS, 18* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19* See the License for the specific language governing permissions and 20* limitations under the License. 21*/ 22 23/** 24 * @author Mark C. Prins <mprins@users.sf.net> 25 * @author Gerhard Koch <gerhard.koch AT ymail.com> 26 * 27 * USAGE: 28 * 29 * map.php?center=40.714728,-73.998672&zoom=14&size=512x512&maptype=cycle&markers=40.702147,-74.015794,lightblue1|40.711614,-74.012318,lightblue2|40.718217,-73.998284,lightblue3&gpx=&kml= 30*/ 31class StaticMap { 32 // these should probably not be changed 33 protected $tileSize = 256; 34 35 // the final output 36 var $doc = ''; 37 38 protected $tileInfo = array( 39 // OSM sources 40 'openstreetmap'=>array( 41 'txt'=>'(c) OpenStreetMap CC-BY-SA', 42 'logo'=>'osm_logo.png', 43 'url'=>'http://tile.openstreetmap.org/{Z}/{X}/{Y}.png'), 44 // cloudmade 45 'cloudmade' =>array( 46 'txt'=>'CloudMade tiles', 47 'logo'=>'cloudmade_logo.png', 48 'url'=> 'http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/2/256/{Z}/{X}/{Y}.png'), 49 'fresh' =>array( 50 'txt'=>'CloudMade tiles', 51 'logo'=>'cloudmade_logo.png', 52 'url'=> 'http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{Z}/{X}/{Y}.png'), 53 // OCM sources 54 'cycle'=>array( 55 'txt'=>'OpenCycleMap tiles', 56 'logo'=>'cycle_logo.png', 57 'url'=>'http://tile.opencyclemap.org/cycle/{Z}/{X}/{Y}.png'), 58 'transport'=>array( 59 'txt'=>'OpenCycleMap tiles', 60 'logo'=>'cycle_logo.png', 61 'url'=>'http://tile2.opencyclemap.org/transport/{Z}/{X}/{Y}.png'), 62 'landscape'=>array( 63 'txt'=>'OpenCycleMap tiles', 64 'logo'=>'cycle_logo.png', 65 'url'=>'http://tile3.opencyclemap.org/landscape/{Z}/{X}/{Y}.png'), 66 // H&B sources 67 'hikeandbike'=>array( 68 'txt'=>'Hike & Bike Map', 69 'logo'=>'hnb_logo.png', 70 'url'=>'http://toolserver.org/tiles/hikebike/{Z}/{X}/{Y}.png'), 71 //'piste'=>array( 72 // 'txt'=>'OpenPisteMap tiles', 73 // 'logo'=>'piste_logo.png', 74 // 'url'=>''), 75 //'sea'=>array( 76 // 'txt'=>'OpenSeaMap tiles', 77 // 'logo'=>'sea_logo.png', 78 // 'url'=>''), 79 // MapQuest 80 'mapquest'=>array( 81 'txt'=>'MapQuest tiles', 82 'logo'=>'mq_logo.png', 83 'url'=>'http://otile3.mqcdn.com/tiles/1.0.0/osm/{Z}/{X}/{Y}.png') 84 ); 85 protected $tileDefaultSrc = 'openstreetmap'; 86 87 // set up markers 88 protected $markerPrototypes = array( 89 // found at http://www.mapito.net/map-marker-icons.html 90 // these are 17x19 px with a pointer at the bottom left 91 'lighblue' => array('regex'=>'/^lightblue([0-9]+)$/', 92 'extension'=>'.png', 93 'shadow'=>false, 94 'offsetImage'=>'0,-19', 95 'offsetShadow'=>false 96 ), 97 // openlayers std markers are 21x25px with shadow 98 'ol-marker'=> array('regex'=>'/^marker(|-blue|-gold|-green|-red)+$/', 99 'extension'=>'.png', 100 'shadow'=>'marker_shadow.png', 101 'offsetImage'=>'-10,-25', 102 'offsetShadow'=>'-1,-13' 103 ), 104 // these are 16x16 px 105 'ww_icon'=> array('regex'=>'/ww_\S+$/', 106 'extension'=>'.png', 107 'shadow'=>false, 108 'offsetImage'=>'-8,-8', 109 'offsetShadow'=>false 110 ), 111 // assume these are 16x16 px 112 'rest' => array('regex'=>'/^(?!lightblue([0-9]+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/', 113 'extension'=>'.png', 114 'shadow'=>'marker_shadow.png', 115 'offsetImage'=>'-8,-8', 116 'offsetShadow'=>'-1,-1' 117 ) 118 ); 119 protected $centerX, $centerY, $offsetX, $offsetY, $image; 120 protected $zoom, $lat, $lon, $width, $height, $markers, $maptype, $kmlFileName, $gpxFileName; 121 protected $tileCacheBaseDir, $mapCacheBaseDir, $mediaBaseDir; 122 protected $useTileCache = true; 123 protected $mapCacheID = ''; 124 protected $mapCacheFile = ''; 125 protected $mapCacheExtension = 'png'; 126 127 /** 128 * 129 * @param number $lat 130 * @param number $lon 131 * @param number $zoom 132 * @param number $width 133 * @param number $height 134 * @param string $maptype 135 * @param mixed $markers 136 * @param string $gpx 137 * @param string $kml 138 * @param string $mediaDir 139 * @param string $tileCacheBaseDir 140 */ 141 public function __construct($lat,$lon,$zoom,$width,$height,$maptype, $markers,$gpx,$kml,$mediaDir,$tileCacheBaseDir){ 142 $this->zoom = $zoom; 143 $this->lat = $lat; 144 $this->lon = $lon; 145 $this->width = $width; 146 $this->height = $height; 147 $this->markers = $markers; 148 $this->mediaBaseDir = $mediaDir; 149 // validate + set maptype 150 $this->maptype = $this->tileDefaultSrc; 151 if(array_key_exists($maptype,$this->tileInfo)) { 152 $this->maptype = $maptype; 153 } 154 155 $this->tileCacheBaseDir= $tileCacheBaseDir.'/olmaptiles'; 156 $this->useTileCache = $this->tileCacheBaseDir !==''; 157 $this->mapCacheBaseDir = $mediaDir.'/olmapmaps'; 158 159 $this->kmlFileName = $this->mediaBaseDir.str_replace(":","/",$kml); 160 $this->gpxFileName = $this->mediaBaseDir.str_replace(":","/",$gpx); 161 } 162 163 /** 164 * 165 * @param number $long 166 * @param number $zoom 167 */ 168 public function lonToTile($long, $zoom){ 169 return (($long + 180) / 360) * pow(2, $zoom); 170 } 171 /** 172 * 173 * @param number $lat 174 * @param number $zoom 175 * @return number 176 */ 177 public function latToTile($lat, $zoom){ 178 return (1 - log(tan($lat * pi()/180) + 1 / cos($lat* pi()/180)) / pi()) /2 * pow(2, $zoom); 179 } 180 /** 181 * 182 */ 183 public function initCoords(){ 184 $this->centerX = $this->lonToTile($this->lon, $this->zoom); 185 $this->centerY = $this->latToTile($this->lat, $this->zoom); 186 $this->offsetX = floor((floor($this->centerX)-$this->centerX)*$this->tileSize); 187 $this->offsetY = floor((floor($this->centerY)-$this->centerY)*$this->tileSize); 188 } 189 190 /** 191 * make basemap image. 192 */ 193 public function createBaseMap(){ 194 $this->image = imagecreatetruecolor($this->width, $this->height); 195 $startX = floor($this->centerX-($this->width/$this->tileSize)/2); 196 $startY = floor($this->centerY-($this->height/$this->tileSize)/2); 197 $endX = ceil($this->centerX+($this->width/$this->tileSize)/2); 198 $endY = ceil($this->centerY+($this->height/$this->tileSize)/2); 199 $this->offsetX = -floor(($this->centerX-floor($this->centerX))*$this->tileSize); 200 $this->offsetY = -floor(($this->centerY-floor($this->centerY))*$this->tileSize); 201 $this->offsetX += floor($this->width/2); 202 $this->offsetY += floor($this->height/2); 203 $this->offsetX += floor($startX-floor($this->centerX))*$this->tileSize; 204 $this->offsetY += floor($startY-floor($this->centerY))*$this->tileSize; 205 206 for($x=$startX; $x<=$endX; $x++){ 207 for($y=$startY; $y<=$endY; $y++){ 208 $url = str_replace(array('{Z}','{X}','{Y}'),array($this->zoom, $x, $y), $this->tileInfo[$this->maptype]['url']); 209 $tileData = $this->fetchTile($url); 210 if($tileData){ 211 $tileImage = imagecreatefromstring($tileData); 212 } else { 213 $tileImage = imagecreate($this->tileSize,$this->tileSize); 214 $color = imagecolorallocate($tileImage, 255, 255, 255); 215 @imagestring($tileImage,1,127,127,'err',$color); 216 } 217 $destX = ($x-$startX)*$this->tileSize+$this->offsetX; 218 $destY = ($y-$startY)*$this->tileSize+$this->offsetY; 219 imagecopy($this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, $this->tileSize); 220 } 221 } 222 } 223 224 /** 225 * Place markers on the map and number them in the same order as they are listed in the html. 226 */ 227 public function placeMarkers(){ 228 $count=0; 229 $color=imagecolorallocate ($this->image,0,0,0 ); 230 $bgcolor=imagecolorallocate ($this->image,200,200,200 ); 231 $markerBaseDir = dirname(__FILE__).'/icons'; 232 // loop thru marker array 233 foreach($this->markers as $marker){ 234 // set some local variables 235 $markerLat = $marker['lat']; 236 $markerLon = $marker['lon']; 237 $markerType = $marker['type']; 238 // clear variables from previous loops 239 $markerFilename = ''; 240 $markerShadow = ''; 241 $matches = false; 242 // check for marker type, get settings from markerPrototypes 243 if($markerType){ 244 foreach($this->markerPrototypes as $markerPrototype){ 245 if(preg_match($markerPrototype['regex'],$markerType,$matches)){ 246 $markerFilename = $matches[0].$markerPrototype['extension']; 247 if($markerPrototype['offsetImage']){ 248 list($markerImageOffsetX, $markerImageOffsetY) = split(",",$markerPrototype['offsetImage']); 249 } 250 $markerShadow = $markerPrototype['shadow']; 251 if($markerShadow){ 252 list($markerShadowOffsetX, $markerShadowOffsetY) = split(",",$markerPrototype['offsetShadow']); 253 } 254 } 255 } 256 } 257 // create img resource 258 if(file_exists($markerBaseDir.'/'.$markerFilename)){ 259 $markerImg = imagecreatefrompng($markerBaseDir.'/'.$markerFilename); 260 } else { 261 $markerImg = imagecreatefrompng($markerBaseDir.'/marker.png'); 262 } 263 // check for shadow + create shadow recource 264 if($markerShadow && file_exists($markerBaseDir.'/'.$markerShadow)){ 265 $markerShadowImg = imagecreatefrompng($markerBaseDir.'/'.$markerShadow); 266 } 267 // calc position 268 $destX = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($markerLon, $this->zoom))); 269 $destY = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($markerLat, $this->zoom))); 270 // copy shadow on basemap 271 if($markerShadow && $markerShadowImg){ 272 imagecopy($this->image, $markerShadowImg, $destX+intval($markerShadowOffsetX), $destY+intval($markerShadowOffsetY), 273 0, 0, imagesx($markerShadowImg), imagesy($markerShadowImg)); 274 } 275 // copy marker on basemap above shadow 276 imagecopy($this->image, $markerImg, $destX+intval($markerImageOffsetX), $destY+intval($markerImageOffsetY), 277 0, 0, imagesx($markerImg), imagesy($markerImg)); 278 // add label 279 imagestring ($this->image , 3 , $destX-imagesx($markerImg)+1 , $destY+intval($markerImageOffsetY)+1 , ++$count , $bgcolor ); 280 imagestring ($this->image , 3 , $destX-imagesx($markerImg) , $destY+intval($markerImageOffsetY) , $count , $color ); 281 }; 282 } 283 /** 284 * 285 * @param string $url 286 * @return string 287 */ 288 public function tileUrlToFilename($url){ 289 return $this->tileCacheBaseDir."/".str_replace(array('http://'),'',$url); 290 } 291 /** 292 * 293 * @param string $url 294 */ 295 public function checkTileCache($url){ 296 $filename = $this->tileUrlToFilename($url); 297 if(file_exists($filename)){ 298 return file_get_contents($filename); 299 } 300 } 301 302 public function checkMapCache(){ 303 $this->mapCacheID = md5($this->serializeParams()); 304 $filename = $this->mapCacheIDToFilename(); 305 if(file_exists($filename)) return true; 306 } 307 308 public function serializeParams(){ 309 return join("&",array($this->zoom,$this->lat,$this->lon,$this->width,$this->height, serialize($this->markers),$this->maptype)); 310 } 311 312 public function mapCacheIDToFilename(){ 313 if(!$this->mapCacheFile){ 314 $this->mapCacheFile = $this->mapCacheBaseDir."/".$this->maptype."/".$this->zoom."/cache_".substr($this->mapCacheID,0,2)."/".substr($this->mapCacheID,2,2)."/".substr($this->mapCacheID,4); 315 } 316 return $this->mapCacheFile.".".$this->mapCacheExtension; 317 } 318 319 public function mkdir_recursive($pathname, $mode){ 320 is_dir(dirname($pathname)) || $this->mkdir_recursive(dirname($pathname), $mode); 321 return is_dir($pathname) || @mkdir($pathname, $mode); 322 } 323 324 public function writeTileToCache($url, $data){ 325 $filename = $this->tileUrlToFilename($url); 326 $this->mkdir_recursive(dirname($filename),0777); 327 file_put_contents($filename, $data); 328 } 329 330 public function fetchTile($url){ 331 if($this->useTileCache && ($cached = $this->checkTileCache($url))) return $cached; 332 $ch = curl_init(); 333 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 334 curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; '.PHP_OS.')'); 335 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); 336 curl_setopt($ch, CURLOPT_URL, $url); 337 $tile = curl_exec($ch); 338 curl_close($ch); 339 if($tile && $this->useTileCache){ 340 $this->writeTileToCache($url,$tile); 341 } 342 return $tile; 343 } 344 345 /** 346 * Draw gpx trace on the map. 347 */ 348 public function drawGPX(){ 349 //TODO implementation 350 //determine bbox gpx 351 } 352 353 /** 354 * Draw kml trace on the map. 355 */ 356 public function drawKML(){ 357 //TODO implementation 358 } 359 360 /** 361 * add copyright and origin notice and icons to the map. 362 */ 363 public function drawCopyright(){ 364 $logoBaseDir = dirname(__FILE__).'/'.'logo/'; 365 $logoImg = imagecreatefrompng($logoBaseDir.$this->tileInfo['openstreetmap']['logo']); 366 $textcolor = imagecolorallocate($this->image, 0, 0, 0); 367 $bgcolor = imagecolorallocate($this->image, 200, 200, 200); 368 369 imagecopy($this->image, 370 $logoImg, 371 0, 372 imagesy($this->image)-imagesy($logoImg), 373 0, 374 0, 375 imagesx($logoImg), 376 imagesy($logoImg) 377 ); 378 imagestring ($this->image , 1 , imagesx($logoImg)+2 , imagesy($this->image)-imagesy($logoImg)+1 , $this->tileInfo['openstreetmap']['txt'],$bgcolor ); 379 imagestring ($this->image , 1 , imagesx($logoImg)+1 , imagesy($this->image)-imagesy($logoImg) , $this->tileInfo['openstreetmap']['txt'] ,$textcolor ); 380 381 // additional tile source info, ie. who created/hosted the tiles 382 if ($this->maptype!='openstreetmap') { 383 $iconImg = imagecreatefrompng($logoBaseDir.$this->tileInfo[$this->maptype]['logo']); 384 imagecopy($this->image, 385 $iconImg, 386 imagesx($logoImg)+1, 387 imagesy($this->image)-imagesy($iconImg), 388 0, 389 0, 390 imagesx($iconImg), 391 imagesy($iconImg) 392 ); 393 imagestring ($this->image , 1 , imagesx($logoImg)+imagesx($iconImg)+4 , imagesy($this->image)-ceil(imagesy($logoImg)/2)+1 , $this->tileInfo[$this->maptype]['txt'],$bgcolor ); 394 imagestring ($this->image , 1 , imagesx($logoImg)+imagesx($iconImg)+3 , imagesy($this->image)-ceil(imagesy($logoImg)/2) , $this->tileInfo[$this->maptype]['txt'] ,$textcolor ); 395 } 396 } 397 /** 398 * make the map. 399 */ 400 public function makeMap(){ 401 $this->initCoords(); 402 $this->createBaseMap(); 403 if(count($this->markers))$this->placeMarkers(); 404 if(file_exists($this->kmlFileName)) $this->drawKML(); 405 if(file_exists($this->gpxFileName)) $this->drawGPX(); 406 $this->drawCopyright(); 407 } 408 /** 409 * get the map, this may return a reference to a cached copy. 410 * @return string url relative to media dir 411 */ 412 public function getMap(){ 413 // use map cache, so check cache for map 414 if(!$this->checkMapCache()){ 415 // map is not in cache, needs to be build 416 $this->makeMap(); 417 $this->mkdir_recursive(dirname($this->mapCacheIDToFilename()),0777); 418 imagepng($this->image,$this->mapCacheIDToFilename(),9); 419 } 420 $this->doc =$this->mapCacheIDToFilename(); 421 // make url relative to media dir 422 return str_replace($this->mediaBaseDir, '', $this->doc); 423 } 424} 425