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