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