xref: /plugin/openlayersmap/StaticMap.php (revision 57e65445d0177f23b36a3959935b97af0314712c)
1<?php
2/*
3 * Copyright (c) 2012-2013 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 */
21include_once(realpath(dirname(__FILE__)).'/../geophp/geoPHP/geoPHP.inc');
22/**
23 * @author Mark C. Prins <mprins@users.sf.net>
24 * @author Gerhard Koch <gerhard.koch AT ymail.com>
25 *
26 */
27class StaticMap {
28	// these should probably not be changed
29	protected $tileSize = 256;
30
31	// the final output
32	var $doc = '';
33
34	protected $tileInfo = array(
35			// OSM sources
36			'openstreetmap'=>array(
37					'txt'=>'(c) OpenStreetMap CC-BY-SA',
38					'logo'=>'osm_logo.png',
39					'url'=>'http://tile.openstreetmap.org/{Z}/{X}/{Y}.png'),
40			// cloudmade
41			'cloudmade' =>array(
42					'txt'=>'CloudMade tiles',
43					'logo'=>'cloudmade_logo.png',
44					'url'=> 'http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/2/256/{Z}/{X}/{Y}.png'),
45			'fresh' =>array(
46					'txt'=>'CloudMade tiles',
47					'logo'=>'cloudmade_logo.png',
48					'url'=> 'http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{Z}/{X}/{Y}.png'),
49			// OCM sources
50			'cycle'=>array(
51					'txt'=>'OpenCycleMap tiles',
52					'logo'=>'cycle_logo.png',
53					'url'=>'http://tile.opencyclemap.org/cycle/{Z}/{X}/{Y}.png'),
54			'transport'=>array(
55					'txt'=>'OpenCycleMap tiles',
56					'logo'=>'cycle_logo.png',
57					'url'=>'http://tile2.opencyclemap.org/transport/{Z}/{X}/{Y}.png'),
58			'landscape'=>array(
59					'txt'=>'OpenCycleMap tiles',
60					'logo'=>'cycle_logo.png',
61					'url'=>'http://tile3.opencyclemap.org/landscape/{Z}/{X}/{Y}.png'),
62			// H&B sources
63			'hikeandbike'=>array(
64					'txt'=>'Hike & Bike Map',
65					'logo'=>'hnb_logo.png',
66					'url'=>'http://toolserver.org/tiles/hikebike/{Z}/{X}/{Y}.png'),
67			//'piste'=>array(
68			//		'txt'=>'OpenPisteMap tiles',
69			//		'logo'=>'piste_logo.png',
70			//		'url'=>''),
71			//'sea'=>array(
72			//		'txt'=>'OpenSeaMap tiles',
73			//		'logo'=>'sea_logo.png',
74			//		'url'=>''),
75			// MapQuest
76			'mapquest'=>array(
77					'txt'=>'MapQuest tiles',
78					'logo'=>'mq_logo.png',
79					'url'=>'http://otile3.mqcdn.com/tiles/1.0.0/map/{Z}/{X}/{Y}.png')
80	);
81	protected $tileDefaultSrc = 'openstreetmap';
82
83	// set up markers
84	protected $markerPrototypes = array(
85			// found at http://www.mapito.net/map-marker-icons.html
86			// these are 17x19 px with a pointer at the bottom left
87			'lighblue' => array('regex'=>'/^lightblue([0-9]+)$/',
88					'extension'=>'.png',
89					'shadow'=>false,
90					'offsetImage'=>'0,-19',
91					'offsetShadow'=>false
92			),
93			// openlayers std markers are 21x25px with shadow
94			'ol-marker'=> array('regex'=>'/^marker(|-blue|-gold|-green|-red)+$/',
95					'extension'=>'.png',
96					'shadow'=>'marker_shadow.png',
97					'offsetImage'=>'-10,-25',
98					'offsetShadow'=>'-1,-13'
99			),
100			// these are 16x16 px
101			'ww_icon'=> array('regex'=>'/ww_\S+$/',
102					'extension'=>'.png',
103					'shadow'=>false,
104					'offsetImage'=>'-8,-8',
105					'offsetShadow'=>false
106			),
107			// assume these are 16x16 px
108			'rest' => array('regex'=>'/^(?!lightblue([0-9]+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/',
109					'extension'=>'.png',
110					'shadow'=>'marker_shadow.png',
111					'offsetImage'=>'-8,-8',
112					'offsetShadow'=>'-1,-1'
113							)
114	);
115	protected $centerX, $centerY, $offsetX, $offsetY, $image;
116	protected $zoom, $lat, $lon, $width, $height, $markers, $maptype, $kmlFileName, $gpxFileName, $geojsonFileName, $autoZoomExtent;
117	protected $tileCacheBaseDir, $mapCacheBaseDir, $mediaBaseDir;
118	protected $useTileCache = true;
119	protected $mapCacheID = '';
120	protected $mapCacheFile = '';
121	protected $mapCacheExtension = 'png';
122
123	/**
124	 * Constructor.
125	 * @param float $lat Latitude (x) of center of map
126	 * @param float $lon Longitude (y) of center of map
127	 * @param int $zoom Zoomlevel
128	 * @param int $width Width in pixels
129	 * @param int $height Height in pixels
130	 * @param string $maptype Name of the map
131	 * @param mixed $markers array of markers
132	 * @param string $gpx GPX filename
133	 * @param string $kml KML filename
134	 * @param string $mediaDir Directory to store/cache maps
135	 * @param string $tileCacheBaseDir Directory to cache map tiles
136	 * @param boolean $autoZoomExtent Wheter or not to override zoom/lat/lon and zoom to the extent of gpx/kml and markers
137	 */
138	public function __construct($lat, $lon, $zoom, $width, $height, $maptype, $markers, $gpx, $kml, $geojson, $mediaDir, $tileCacheBaseDir, $autoZoomExtent=TRUE){
139		$this->zoom = $zoom;
140		$this->lat = $lat;
141		$this->lon = $lon;
142		$this->width = $width;
143		$this->height = $height;
144		// validate + set maptype
145		$this->maptype = $this->tileDefaultSrc;
146		if(array_key_exists($maptype,$this->tileInfo)) {
147			$this->maptype = $maptype;
148		}
149		$this->markers = $markers;
150		$this->kmlFileName = $kml;
151		$this->gpxFileName = $gpx;
152		$this->geojsonFileName = $geojson;
153		$this->mediaBaseDir = $mediaDir;
154		$this->tileCacheBaseDir= $tileCacheBaseDir.'/olmaptiles';
155		$this->useTileCache = $this->tileCacheBaseDir !=='';
156		$this->mapCacheBaseDir = $mediaDir.'/olmapmaps';
157		$this->autoZoomExtent = $autoZoomExtent;
158	}
159
160	/**
161	 *
162	 * @param number $long
163	 * @param number $zoom
164	 * @return number
165	 */
166	public function lonToTile($long, $zoom){
167		return (($long + 180) / 360) * pow(2, $zoom);
168	}
169	/**
170	 *
171	 * @param number $lat
172	 * @param number $zoom
173	 * @return number
174	 */
175	public function latToTile($lat, $zoom){
176		return (1 - log(tan($lat * pi()/180) + 1 / cos($lat* M_PI/180)) / M_PI) /2 * pow(2, $zoom);
177	}
178
179	/**
180	 *
181	 */
182	public function initCoords(){
183		$this->centerX = $this->lonToTile($this->lon, $this->zoom);
184		$this->centerY = $this->latToTile($this->lat, $this->zoom);
185		$this->offsetX = floor((floor($this->centerX)-$this->centerX)*$this->tileSize);
186		$this->offsetY = floor((floor($this->centerY)-$this->centerY)*$this->tileSize);
187	}
188
189	/**
190	 * make basemap image.
191	 */
192	public function createBaseMap(){
193		$this->image = imagecreatetruecolor($this->width, $this->height);
194		$startX = floor($this->centerX-($this->width/$this->tileSize)/2);
195		$startY = floor($this->centerY-($this->height/$this->tileSize)/2);
196		$endX = ceil($this->centerX+($this->width/$this->tileSize)/2);
197		$endY = ceil($this->centerY+($this->height/$this->tileSize)/2);
198		$this->offsetX = -floor(($this->centerX-floor($this->centerX))*$this->tileSize);
199		$this->offsetY = -floor(($this->centerY-floor($this->centerY))*$this->tileSize);
200		$this->offsetX += floor($this->width/2);
201		$this->offsetY += floor($this->height/2);
202		$this->offsetX += floor($startX-floor($this->centerX))*$this->tileSize;
203		$this->offsetY += floor($startY-floor($this->centerY))*$this->tileSize;
204
205		for($x=$startX; $x<=$endX; $x++){
206			for($y=$startY; $y<=$endY; $y++){
207				$url = str_replace(array('{Z}','{X}','{Y}'),array($this->zoom, $x, $y), $this->tileInfo[$this->maptype]['url']);
208				$tileData = $this->fetchTile($url);
209				if($tileData){
210					$tileImage = imagecreatefromstring($tileData);
211				} else {
212					$tileImage = imagecreate($this->tileSize,$this->tileSize);
213					$color = imagecolorallocate($tileImage, 255, 255, 255);
214					@imagestring($tileImage,1,127,127,'err',$color);
215				}
216				$destX = ($x-$startX)*$this->tileSize+$this->offsetX;
217				$destY = ($y-$startY)*$this->tileSize+$this->offsetY;
218				imagecopy($this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, $this->tileSize);
219			}
220		}
221	}
222
223	/**
224	 * Place markers on the map and number them in the same order as they are listed in the html.
225	 */
226	public function placeMarkers(){
227		$count=0;
228		$color=imagecolorallocate ($this->image,0,0,0 );
229		$bgcolor=imagecolorallocate ($this->image,200,200,200 );
230		$markerBaseDir = dirname(__FILE__).'/icons';
231		// loop thru marker array
232		foreach($this->markers as $marker) {
233			// set some local variables
234			$markerLat = $marker['lat'];
235			$markerLon = $marker['lon'];
236			$markerType = $marker['type'];
237			// clear variables from previous loops
238			$markerFilename = '';
239			$markerShadow = '';
240			$matches = false;
241			// check for marker type, get settings from markerPrototypes
242			if($markerType){
243				foreach($this->markerPrototypes as $markerPrototype){
244					if(preg_match($markerPrototype['regex'],$markerType,$matches)){
245						$markerFilename = $matches[0].$markerPrototype['extension'];
246						if($markerPrototype['offsetImage']){
247							list($markerImageOffsetX, $markerImageOffsetY)  = split(",",$markerPrototype['offsetImage']);
248						}
249						$markerShadow = $markerPrototype['shadow'];
250						if($markerShadow){
251							list($markerShadowOffsetX, $markerShadowOffsetY)  = split(",",$markerPrototype['offsetShadow']);
252						}
253					}
254				}
255			}
256			// create img resource
257			if(file_exists($markerBaseDir.'/'.$markerFilename)){
258				$markerImg = imagecreatefrompng($markerBaseDir.'/'.$markerFilename);
259			} else {
260				$markerImg = imagecreatefrompng($markerBaseDir.'/marker.png');
261			}
262			// check for shadow + create shadow recource
263			if($markerShadow && file_exists($markerBaseDir.'/'.$markerShadow)){
264				$markerShadowImg = imagecreatefrompng($markerBaseDir.'/'.$markerShadow);
265			}
266			// calc position
267			$destX = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($markerLon, $this->zoom)));
268			$destY = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($markerLat, $this->zoom)));
269			// copy shadow on basemap
270			if($markerShadow && $markerShadowImg){
271				imagecopy($this->image, $markerShadowImg, $destX+intval($markerShadowOffsetX), $destY+intval($markerShadowOffsetY),
272				0, 0, imagesx($markerShadowImg), imagesy($markerShadowImg));
273			}
274			// copy marker on basemap above shadow
275			imagecopy($this->image, $markerImg, $destX+intval($markerImageOffsetX), $destY+intval($markerImageOffsetY),
276			0, 0, imagesx($markerImg), imagesy($markerImg));
277			// add label
278			imagestring ($this->image , 3 , $destX-imagesx($markerImg)+1 , $destY+intval($markerImageOffsetY)+1 , ++$count , $bgcolor );
279			imagestring ($this->image , 3 , $destX-imagesx($markerImg) , $destY+intval($markerImageOffsetY) , $count , $color );
280		};
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	 *
294	 * @param string $url
295	 */
296	public function checkTileCache($url){
297		$filename = $this->tileUrlToFilename($url);
298		if(file_exists($filename)){
299			return file_get_contents($filename);
300		}
301	}
302
303	public function checkMapCache(){
304		$this->mapCacheID = md5($this->serializeParams());
305		$filename = $this->mapCacheIDToFilename();
306		if(file_exists($filename)) return true;
307	}
308
309	public function serializeParams(){
310		return join("&",array($this->zoom,$this->lat,$this->lon,$this->width,$this->height, serialize($this->markers),$this->maptype, $this->kmlFileName, $this->gpxFileName, $this->geojsonFileName));
311	}
312
313	public function mapCacheIDToFilename(){
314		if(!$this->mapCacheFile){
315			$this->mapCacheFile = $this->mapCacheBaseDir."/".$this->maptype."/".$this->zoom."/cache_".substr($this->mapCacheID,0,2)."/".substr($this->mapCacheID,2,2)."/".substr($this->mapCacheID,4);
316		}
317		return $this->mapCacheFile.".".$this->mapCacheExtension;
318	}
319
320	/**
321	 * Recursively create the directory.
322	 * @param string $pathname The directory path.
323	 * @param int $mode File access mode. For more information on modes, read the details on the chmod manpage.
324	 */
325	public function mkdir_recursive($pathname, $mode){
326		is_dir(dirname($pathname)) || $this->mkdir_recursive(dirname($pathname), $mode);
327		return is_dir($pathname) || @mkdir($pathname, $mode);
328	}
329
330	/**
331	 * Write a tile into the cache.
332	 * @param string $url
333	 * @param mixed $data
334	 */
335	public function writeTileToCache($url, $data){
336		$filename = $this->tileUrlToFilename($url);
337		$this->mkdir_recursive(dirname($filename),0777);
338		file_put_contents($filename, $data);
339	}
340
341	/**
342	 * Fetch a tile and (if configured) store it in the cache.
343	 * @param string $url
344	 */
345	public function fetchTile($url){
346		if($this->useTileCache && ($cached = $this->checkTileCache($url))) return $cached;
347
348		$_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; '.PHP_OS.')';
349		if(function_exists("curl_init")){
350			// use cUrl
351			$ch = curl_init();
352			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
353			curl_setopt($ch, CURLOPT_USERAGENT, $_UA);
354			curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
355			curl_setopt($ch, CURLOPT_URL, $url);
356			$tile = curl_exec($ch);
357			curl_close($ch);
358		} else {
359			// use file_get_contents
360			global $conf;
361			$opts = array(
362					'http'=>array(
363							'method'=>"GET",
364							'header'=>"Accept-language: en\r\n" .
365									"User-Agent: $_UA\r\n".
366									"accept: image/png\r\n",
367							'proxy' => "tcp://".$conf['proxy']['host'] . ":" . $conf['proxy']['port'],
368							'request_fulluri' => true,
369					)
370			);
371			$context = stream_context_create($opts);
372			$tile = file_get_contents($url, false, $context);
373		}
374		if($tile && $this->useTileCache){
375			$this->writeTileToCache($url,$tile);
376		}
377		return $tile;
378	}
379
380	/**
381	 * Draw gpx trace on the map.
382	 */
383	public function drawGPX(){
384		$col = imagecolorallocatealpha($this->image, 0, 0, 255, .4*127);
385		$gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName),'gpx');
386		$this->drawGeometry($gpxgeom, $col);
387	}
388
389	/**
390	 * Draw geojson on the map.
391	 */
392	public function drawGeojson(){
393		$col = imagecolorallocatealpha($this->image, 255, 0, 255, .4*127);
394		$gpxgeom = geoPHP::load(file_get_contents($this->geojsonFileName),'json');
395		$this->drawGeometry($gpxgeom, $col);
396	}
397
398	/**
399	 * Draw kml trace on the map.
400	 */
401	public function drawKML(){
402		// TODO get colour from kml node (not currently supported in geoPHP)
403		$col = imagecolorallocatealpha($this->image, 255, 0, 0, .4*127);
404		$kmlgeom = geoPHP::load(file_get_contents($this->kmlFileName),'kml');
405		$this->drawGeometry($kmlgeom, $col);
406	}
407
408	/**
409	 * Draw geometry or geometry collection on the map.
410	 * @param Geometry $geom
411	 * @param int $colour drawing colour
412	 */
413	private function drawGeometry($geom, $colour){
414		switch ($geom->geometryType()) {
415			case 'GeometryCollection':
416				// recursively draw part of the collection
417				for ($i = 1; $i < $geom->numGeometries()+1; $i++) {
418					$_geom = $geom->geometryN($i);
419					$this->drawGeometry($_geom, $colour);
420				}
421				break;
422			case 'MultiPolygon':
423				// TODO implement / do nothing
424				break;
425			case 'MultiLineString':
426				// TODO implement / do nothing
427				break;
428			case 'MultiPoint':
429				// TODO implement / do nothing
430				break;
431			case 'Polygon':
432				$this->drawPolygon($geom, $colour);
433				break;
434			case 'LineString':
435				$this->drawLineString($geom, $colour);
436				break;
437			case 'Point':
438				$this->drawPoint($geom, $colour);
439				break;
440			default:
441				//draw nothing
442				break;
443		}
444	}
445
446	/**
447	 * Draw a line on the map.
448	 * @param LineString $line
449	 * @param int $colour drawing colour
450	 */
451	private function drawLineString($line, $colour){
452		imagesetthickness($this->image,2);
453		for ($p = 1; $p < $line->numGeometries(); $p++) {
454			// get first pair of points
455			$p1 = $line->geometryN($p);
456			$p2 = $line->geometryN($p+1);
457			// translate to paper space
458			$x1 = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($p1->x(), $this->zoom)));
459			$y1 = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($p1->y(), $this->zoom)));
460			$x2 = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($p2->x(), $this->zoom)));
461			$y2 = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($p2->y(), $this->zoom)));
462			// draw to image
463			imageline ($this->image, $x1, $y1, $x2, $y2, $colour);
464		}
465		imagesetthickness($this->image,1);
466	}
467
468	/**
469	 * Draw a point on the map.
470	 * @param Point $point
471	 * @param int $colour drawing colour
472	 */
473	private function drawPoint($point, $colour){
474		imagesetthickness($this->image,2);
475		// translate to paper space
476		$cx = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($point->x(), $this->zoom)));
477		$cy = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($point->y(), $this->zoom)));
478		$r = 5;
479		// draw to image
480		// imageellipse($this->image, $cx, $cy,$r, $r, $colour);
481		imagefilledellipse ($this->image, $cx, $cy, $r, $r, $colour);
482		// don't use imageellipse because the imagesetthickness function has
483		// no effect. So the better workaround is to use imagearc.
484		imagearc($this->image, $cx, $cy, $r, $r, 0, 359, $colour);
485		imagesetthickness($this->image,1);
486	}
487
488	/**
489	 * Draw a polygon on the map.
490	 * @param Polygon $polygon
491	 * @param int $colour drawing colour
492	 */
493	private function drawPolygon($polygon, $colour){
494		// TODO implementation of drawing holes,
495		// maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol?
496
497		//print_r('Polygon:<br />');
498		//print_r($polygon);
499
500		$extPoints = array();
501		// extring is a linestring actually..
502		$extRing = $polygon->exteriorRing();
503
504		for ($i = 1; $i < $extRing->numGeometries(); $i++) {
505			$p1 = $extRing->geometryN($i);
506			$x = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($p1->x(), $this->zoom)));
507			$y = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($p1->y(), $this->zoom)));
508			$extPoints[]=$x;
509			$extPoints[]=$y;
510		}
511		//print_r('points:('.($i-1).')<br />');
512		//print_r($extPoints);
513		//imagepolygon ($this->image, $extPoints, $i-1, $colour );
514		imagefilledpolygon($this->image, $extPoints, $i-1, $colour );
515	}
516
517	/**
518	 * add copyright and origin notice and icons to the map.
519	 */
520	public function drawCopyright(){
521		$logoBaseDir = dirname(__FILE__).'/'.'logo/';
522		$logoImg = imagecreatefrompng($logoBaseDir.$this->tileInfo['openstreetmap']['logo']);
523		$textcolor = imagecolorallocate($this->image, 0, 0, 0);
524		$bgcolor = imagecolorallocate($this->image, 200, 200, 200);
525
526		imagecopy($this->image, $logoImg, 0, imagesy($this->image)-imagesy($logoImg), 0, 0, imagesx($logoImg), imagesy($logoImg));
527		imagestring($this->image , 1, imagesx($logoImg)+2 ,imagesy($this->image)-imagesy($logoImg)+1 ,$this->tileInfo['openstreetmap']['txt'], $bgcolor );
528		imagestring($this->image , 1, imagesx($logoImg)+1 ,imagesy($this->image)-imagesy($logoImg) ,$this->tileInfo['openstreetmap']['txt'] ,$textcolor );
529
530		// additional tile source info, ie. who created/hosted the tiles
531		if ($this->maptype!='openstreetmap') {
532			$iconImg = imagecreatefrompng($logoBaseDir.$this->tileInfo[$this->maptype]['logo']);
533			imagecopy($this->image, $iconImg, imagesx($logoImg)+1, imagesy($this->image)-imagesy($iconImg), 0, 0, imagesx($iconImg), imagesy($iconImg));
534			imagestring($this->image, 1, imagesx($logoImg)+imagesx($iconImg)+4, imagesy($this->image)-ceil(imagesy($logoImg)/2)+1, $this->tileInfo[$this->maptype]['txt'], $bgcolor );
535			imagestring($this->image, 1, imagesx($logoImg)+imagesx($iconImg)+3, imagesy($this->image)-ceil(imagesy($logoImg)/2), $this->tileInfo[$this->maptype]['txt'], $textcolor );
536		}
537	}
538
539	/**
540	 * make the map.
541	 */
542	public function makeMap(){
543		$this->initCoords();
544		$this->createBaseMap();
545		if(!empty($this->markers))$this->placeMarkers();
546		if(file_exists($this->kmlFileName)) $this->drawKML();
547		if(file_exists($this->gpxFileName)) $this->drawGPX();
548		if(file_exists($this->geojsonFileName)) $this->drawGeojson();
549
550		$this->drawCopyright();
551	}
552
553	/**
554	 * Calculate the lat/lon/zoom values to make sure that all of the markers and gpx/kml are on the map.
555	 * @param float $paddingFactor buffer constant to enlarge (>1.0) the zoom level
556	 */
557	private function autoZoom($paddingFactor=1.0){
558		$geoms = array();
559		$geoms[] = new Point($this->lon, $this->lat);
560		if(!empty($this->markers)){
561			foreach($this->markers as $marker){
562				$geoms[] = new Point($marker['lon'],$marker['lat']);
563			}
564		}
565		if(file_exists($this->kmlFileName)){
566			$geoms[] = geoPHP::load(file_get_contents($this->kmlFileName),'kml');
567		}
568		if(file_exists($this->gpxFileName)) {
569			$geoms[] = geoPHP::load(file_get_contents($this->gpxFileName),'gpx');
570		}
571		if(file_exists($this->geojsonFileName)) {
572			$geoms[] = geoPHP::load(file_get_contents($this->geojsonFileName),'geojson');
573		}
574
575		if (count($geoms)<=1) return;
576
577		$geom = new GeometryCollection($geoms);
578		$centroid=$geom->centroid();
579		$bbox=$geom->getBBox();
580
581		// determine vertical resolution, this depends on the distance from the equator
582		//	$vy00 = log(tan(M_PI*(0.25 + $centroid->getY()/360)));
583		$vy0 = log(tan(M_PI*(0.25 + $bbox['miny']/360)));
584		$vy1 = log(tan(M_PI*(0.25 + $bbox['maxy']/360)));
585		$zoomFactorPowered = ($this->height/2) / (40.7436654315252 * ($vy1 - $vy0));
586		$resolutionVertical = 360 / ($zoomFactorPowered * $this->tileSize);
587		// determine horizontal resolution
588		$resolutionHorizontal = ($bbox['maxx']-$bbox['minx']) / $this->width;
589		$resolution = max($resolutionHorizontal, $resolutionVertical) * $paddingFactor;
590		$zoom = log(360 / ($resolution * $this->tileSize), 2);
591
592		$this->zoom = floor($zoom);
593		$this->lon=$centroid->getX();
594		$this->lat=$centroid->getY();
595	}
596
597	/**
598	 * get the map, this may return a reference to a cached copy.
599	 * @return string url relative to media dir
600	 */
601	public function getMap(){
602		if($this->autoZoomExtent) $this->autoZoom() ;
603
604		// use map cache, so check cache for map
605		if(!$this->checkMapCache()){
606			// map is not in cache, needs to be build
607			$this->makeMap();
608			$this->mkdir_recursive(dirname($this->mapCacheIDToFilename()),0777);
609			imagepng($this->image,$this->mapCacheIDToFilename(),9);
610		}
611		$this->doc =$this->mapCacheIDToFilename();
612		// make url relative to media dir
613		return str_replace($this->mediaBaseDir, '', $this->doc);
614	}
615}
616