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