xref: /plugin/openlayersmap/StaticMap.php (revision d768c53817b6707a5904421c74f317a4deabf5ed)
1628e43ccSMark Prins<?php
2628e43ccSMark Prins/*
3*d768c538SMark Prins * Copyright (c) 2012-2014 Mark C. Prins <mprins@users.sf.net>
4628e43ccSMark Prins *
5f4b9bdacSMark Prins * In part based on staticMapLite 0.03 available at http://staticmaplite.svn.sourceforge.net/viewvc/staticmaplite/
6628e43ccSMark Prins *
7628e43ccSMark Prins * Copyright (c) 2009 Gerhard Koch <gerhard.koch AT ymail.com>
8628e43ccSMark Prins *
9628e43ccSMark Prins * Licensed under the Apache License, Version 2.0 (the "License");
10628e43ccSMark Prins * you may not use this file except in compliance with the License.
11628e43ccSMark Prins * You may obtain a copy of the License at
12628e43ccSMark Prins *
13628e43ccSMark Prins *     http://www.apache.org/licenses/LICENSE-2.0
14628e43ccSMark Prins *
15628e43ccSMark Prins * Unless required by applicable law or agreed to in writing, software
16628e43ccSMark Prins * distributed under the License is distributed on an "AS IS" BASIS,
17628e43ccSMark Prins * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18628e43ccSMark Prins * See the License for the specific language governing permissions and
19628e43ccSMark Prins * limitations under the License.
20628e43ccSMark Prins */
21f4b9bdacSMark Prinsinclude_once(realpath(dirname(__FILE__)).'/../geophp/geoPHP/geoPHP.inc');
22628e43ccSMark Prins/**
23628e43ccSMark Prins * @author Mark C. Prins <mprins@users.sf.net>
24628e43ccSMark Prins * @author Gerhard Koch <gerhard.koch AT ymail.com>
25628e43ccSMark Prins *
26628e43ccSMark Prins */
27628e43ccSMark Prinsclass StaticMap {
28628e43ccSMark Prins	// these should probably not be changed
29628e43ccSMark Prins	protected $tileSize = 256;
30628e43ccSMark Prins
31628e43ccSMark Prins	// the final output
32628e43ccSMark Prins	var $doc = '';
33628e43ccSMark Prins
34628e43ccSMark Prins	protected $tileInfo = array(
35628e43ccSMark Prins			// OSM sources
36628e43ccSMark Prins			'openstreetmap'=>array(
37628e43ccSMark Prins					'txt'=>'(c) OpenStreetMap CC-BY-SA',
38628e43ccSMark Prins					'logo'=>'osm_logo.png',
39628e43ccSMark Prins					'url'=>'http://tile.openstreetmap.org/{Z}/{X}/{Y}.png'),
40628e43ccSMark Prins			// cloudmade
41628e43ccSMark Prins			'cloudmade' =>array(
42628e43ccSMark Prins					'txt'=>'CloudMade tiles',
43628e43ccSMark Prins					'logo'=>'cloudmade_logo.png',
44628e43ccSMark Prins					'url'=> 'http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/2/256/{Z}/{X}/{Y}.png'),
45628e43ccSMark Prins			'fresh' =>array(
46628e43ccSMark Prins					'txt'=>'CloudMade tiles',
47628e43ccSMark Prins					'logo'=>'cloudmade_logo.png',
48628e43ccSMark Prins					'url'=> 'http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{Z}/{X}/{Y}.png'),
49628e43ccSMark Prins			// OCM sources
50628e43ccSMark Prins			'cycle'=>array(
51628e43ccSMark Prins					'txt'=>'OpenCycleMap tiles',
52628e43ccSMark Prins					'logo'=>'cycle_logo.png',
53628e43ccSMark Prins					'url'=>'http://tile.opencyclemap.org/cycle/{Z}/{X}/{Y}.png'),
54628e43ccSMark Prins			'transport'=>array(
55628e43ccSMark Prins					'txt'=>'OpenCycleMap tiles',
56628e43ccSMark Prins					'logo'=>'cycle_logo.png',
57628e43ccSMark Prins					'url'=>'http://tile2.opencyclemap.org/transport/{Z}/{X}/{Y}.png'),
58628e43ccSMark Prins			'landscape'=>array(
59628e43ccSMark Prins					'txt'=>'OpenCycleMap tiles',
60628e43ccSMark Prins					'logo'=>'cycle_logo.png',
61628e43ccSMark Prins					'url'=>'http://tile3.opencyclemap.org/landscape/{Z}/{X}/{Y}.png'),
62628e43ccSMark Prins			// H&B sources
63628e43ccSMark Prins			'hikeandbike'=>array(
64628e43ccSMark Prins					'txt'=>'Hike & Bike Map',
65628e43ccSMark Prins					'logo'=>'hnb_logo.png',
66628e43ccSMark Prins					'url'=>'http://toolserver.org/tiles/hikebike/{Z}/{X}/{Y}.png'),
67628e43ccSMark Prins			//'piste'=>array(
68628e43ccSMark Prins			//		'txt'=>'OpenPisteMap tiles',
69628e43ccSMark Prins			//		'logo'=>'piste_logo.png',
70628e43ccSMark Prins			//		'url'=>''),
71628e43ccSMark Prins			//'sea'=>array(
72628e43ccSMark Prins			//		'txt'=>'OpenSeaMap tiles',
73628e43ccSMark Prins			//		'logo'=>'sea_logo.png',
74628e43ccSMark Prins			//		'url'=>''),
75628e43ccSMark Prins			// MapQuest
76628e43ccSMark Prins			'mapquest'=>array(
77628e43ccSMark Prins					'txt'=>'MapQuest tiles',
78628e43ccSMark Prins					'logo'=>'mq_logo.png',
797e0d6b41SMark Prins					'url'=>'http://otile3.mqcdn.com/tiles/1.0.0/map/{Z}/{X}/{Y}.png')
80628e43ccSMark Prins	);
81628e43ccSMark Prins	protected $tileDefaultSrc = 'openstreetmap';
82628e43ccSMark Prins
83628e43ccSMark Prins	// set up markers
84628e43ccSMark Prins	protected $markerPrototypes = array(
85628e43ccSMark Prins			// found at http://www.mapito.net/map-marker-icons.html
86628e43ccSMark Prins			// these are 17x19 px with a pointer at the bottom left
87628e43ccSMark Prins			'lighblue' => array('regex'=>'/^lightblue([0-9]+)$/',
88628e43ccSMark Prins					'extension'=>'.png',
89628e43ccSMark Prins					'shadow'=>false,
90628e43ccSMark Prins					'offsetImage'=>'0,-19',
91628e43ccSMark Prins					'offsetShadow'=>false
92628e43ccSMark Prins			),
93628e43ccSMark Prins			// openlayers std markers are 21x25px with shadow
94628e43ccSMark Prins			'ol-marker'=> array('regex'=>'/^marker(|-blue|-gold|-green|-red)+$/',
95628e43ccSMark Prins					'extension'=>'.png',
96628e43ccSMark Prins					'shadow'=>'marker_shadow.png',
97628e43ccSMark Prins					'offsetImage'=>'-10,-25',
98628e43ccSMark Prins					'offsetShadow'=>'-1,-13'
99628e43ccSMark Prins			),
100628e43ccSMark Prins			// these are 16x16 px
101628e43ccSMark Prins			'ww_icon'=> array('regex'=>'/ww_\S+$/',
102628e43ccSMark Prins					'extension'=>'.png',
103628e43ccSMark Prins					'shadow'=>false,
104628e43ccSMark Prins					'offsetImage'=>'-8,-8',
105628e43ccSMark Prins					'offsetShadow'=>false
106628e43ccSMark Prins			),
107628e43ccSMark Prins			// assume these are 16x16 px
108628e43ccSMark Prins			'rest' => array('regex'=>'/^(?!lightblue([0-9]+)$)(?!(ww_\S+$))(?!marker(|-blue|-gold|-green|-red)+$)(.*)/',
109628e43ccSMark Prins					'extension'=>'.png',
110628e43ccSMark Prins					'shadow'=>'marker_shadow.png',
111628e43ccSMark Prins					'offsetImage'=>'-8,-8',
112628e43ccSMark Prins					'offsetShadow'=>'-1,-1'
113628e43ccSMark Prins							)
114628e43ccSMark Prins	);
115628e43ccSMark Prins	protected $centerX, $centerY, $offsetX, $offsetY, $image;
1166914b920SMark Prins	protected $zoom, $lat, $lon, $width, $height, $markers, $maptype, $kmlFileName, $gpxFileName, $geojsonFileName, $autoZoomExtent;
117628e43ccSMark Prins	protected $tileCacheBaseDir, $mapCacheBaseDir, $mediaBaseDir;
118628e43ccSMark Prins	protected $useTileCache = true;
119628e43ccSMark Prins	protected $mapCacheID = '';
120628e43ccSMark Prins	protected $mapCacheFile = '';
121628e43ccSMark Prins	protected $mapCacheExtension = 'png';
122628e43ccSMark Prins
123628e43ccSMark Prins	/**
124f4b9bdacSMark Prins	 * Constructor.
1252d11d700SMark Prins	 * @param float $lat Latitude (x) of center of map
1262d11d700SMark Prins	 * @param float $lon Longitude (y) of center of map
1272d11d700SMark Prins	 * @param int $zoom Zoomlevel
1282d11d700SMark Prins	 * @param int $width Width in pixels
1292d11d700SMark Prins	 * @param int $height Height in pixels
1302d11d700SMark Prins	 * @param string $maptype Name of the map
1312d11d700SMark Prins	 * @param mixed $markers array of markers
1322d11d700SMark Prins	 * @param string $gpx GPX filename
1332d11d700SMark Prins	 * @param string $kml KML filename
1342d11d700SMark Prins	 * @param string $mediaDir Directory to store/cache maps
1352d11d700SMark Prins	 * @param string $tileCacheBaseDir Directory to cache map tiles
1362d11d700SMark Prins	 * @param boolean $autoZoomExtent Wheter or not to override zoom/lat/lon and zoom to the extent of gpx/kml and markers
137628e43ccSMark Prins	 */
1386914b920SMark Prins	public function __construct($lat, $lon, $zoom, $width, $height, $maptype, $markers, $gpx, $kml, $geojson, $mediaDir, $tileCacheBaseDir, $autoZoomExtent=TRUE){
139628e43ccSMark Prins		$this->zoom = $zoom;
140628e43ccSMark Prins		$this->lat = $lat;
141628e43ccSMark Prins		$this->lon = $lon;
142628e43ccSMark Prins		$this->width = $width;
143628e43ccSMark Prins		$this->height = $height;
144628e43ccSMark Prins		// validate + set maptype
145628e43ccSMark Prins		$this->maptype = $this->tileDefaultSrc;
146628e43ccSMark Prins		if(array_key_exists($maptype,$this->tileInfo)) {
147628e43ccSMark Prins			$this->maptype = $maptype;
148628e43ccSMark Prins		}
1492d11d700SMark Prins		$this->markers = $markers;
1502d11d700SMark Prins		$this->kmlFileName = $kml;
1512d11d700SMark Prins		$this->gpxFileName = $gpx;
1526914b920SMark Prins		$this->geojsonFileName = $geojson;
1532d11d700SMark Prins		$this->mediaBaseDir = $mediaDir;
154628e43ccSMark Prins		$this->tileCacheBaseDir= $tileCacheBaseDir.'/olmaptiles';
155628e43ccSMark Prins		$this->useTileCache = $this->tileCacheBaseDir !=='';
156628e43ccSMark Prins		$this->mapCacheBaseDir = $mediaDir.'/olmapmaps';
1572d11d700SMark Prins		$this->autoZoomExtent = $autoZoomExtent;
158628e43ccSMark Prins	}
159628e43ccSMark Prins
160628e43ccSMark Prins	/**
161628e43ccSMark Prins	 *
162628e43ccSMark Prins	 * @param number $long
163628e43ccSMark Prins	 * @param number $zoom
164f4b9bdacSMark Prins	 * @return number
165628e43ccSMark Prins	 */
166628e43ccSMark Prins	public function lonToTile($long, $zoom){
167628e43ccSMark Prins		return (($long + 180) / 360) * pow(2, $zoom);
168628e43ccSMark Prins	}
169628e43ccSMark Prins	/**
170628e43ccSMark Prins	 *
171628e43ccSMark Prins	 * @param number $lat
172628e43ccSMark Prins	 * @param number $zoom
173628e43ccSMark Prins	 * @return number
174628e43ccSMark Prins	 */
175628e43ccSMark Prins	public function latToTile($lat, $zoom){
1762d11d700SMark Prins		return (1 - log(tan($lat * pi()/180) + 1 / cos($lat* M_PI/180)) / M_PI) /2 * pow(2, $zoom);
177628e43ccSMark Prins	}
178f4b9bdacSMark Prins
179628e43ccSMark Prins	/**
180628e43ccSMark Prins	 *
181628e43ccSMark Prins	 */
182628e43ccSMark Prins	public function initCoords(){
183628e43ccSMark Prins		$this->centerX = $this->lonToTile($this->lon, $this->zoom);
184628e43ccSMark Prins		$this->centerY = $this->latToTile($this->lat, $this->zoom);
185628e43ccSMark Prins		$this->offsetX = floor((floor($this->centerX)-$this->centerX)*$this->tileSize);
186628e43ccSMark Prins		$this->offsetY = floor((floor($this->centerY)-$this->centerY)*$this->tileSize);
187628e43ccSMark Prins	}
188628e43ccSMark Prins
189628e43ccSMark Prins	/**
190628e43ccSMark Prins	 * make basemap image.
191628e43ccSMark Prins	 */
192628e43ccSMark Prins	public function createBaseMap(){
193628e43ccSMark Prins		$this->image = imagecreatetruecolor($this->width, $this->height);
194628e43ccSMark Prins		$startX = floor($this->centerX-($this->width/$this->tileSize)/2);
195628e43ccSMark Prins		$startY = floor($this->centerY-($this->height/$this->tileSize)/2);
196628e43ccSMark Prins		$endX = ceil($this->centerX+($this->width/$this->tileSize)/2);
197628e43ccSMark Prins		$endY = ceil($this->centerY+($this->height/$this->tileSize)/2);
198628e43ccSMark Prins		$this->offsetX = -floor(($this->centerX-floor($this->centerX))*$this->tileSize);
199628e43ccSMark Prins		$this->offsetY = -floor(($this->centerY-floor($this->centerY))*$this->tileSize);
200628e43ccSMark Prins		$this->offsetX += floor($this->width/2);
201628e43ccSMark Prins		$this->offsetY += floor($this->height/2);
202628e43ccSMark Prins		$this->offsetX += floor($startX-floor($this->centerX))*$this->tileSize;
203628e43ccSMark Prins		$this->offsetY += floor($startY-floor($this->centerY))*$this->tileSize;
204628e43ccSMark Prins
205628e43ccSMark Prins		for($x=$startX; $x<=$endX; $x++){
206628e43ccSMark Prins			for($y=$startY; $y<=$endY; $y++){
207628e43ccSMark Prins				$url = str_replace(array('{Z}','{X}','{Y}'),array($this->zoom, $x, $y), $this->tileInfo[$this->maptype]['url']);
208628e43ccSMark Prins				$tileData = $this->fetchTile($url);
209628e43ccSMark Prins				if($tileData){
210628e43ccSMark Prins					$tileImage = imagecreatefromstring($tileData);
211628e43ccSMark Prins				} else {
212628e43ccSMark Prins					$tileImage = imagecreate($this->tileSize,$this->tileSize);
213628e43ccSMark Prins					$color = imagecolorallocate($tileImage, 255, 255, 255);
214628e43ccSMark Prins					@imagestring($tileImage,1,127,127,'err',$color);
215628e43ccSMark Prins				}
216628e43ccSMark Prins				$destX = ($x-$startX)*$this->tileSize+$this->offsetX;
217628e43ccSMark Prins				$destY = ($y-$startY)*$this->tileSize+$this->offsetY;
218628e43ccSMark Prins				imagecopy($this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, $this->tileSize);
219628e43ccSMark Prins			}
220628e43ccSMark Prins		}
221628e43ccSMark Prins	}
222628e43ccSMark Prins
223628e43ccSMark Prins	/**
224628e43ccSMark Prins	 * Place markers on the map and number them in the same order as they are listed in the html.
225628e43ccSMark Prins	 */
226628e43ccSMark Prins	public function placeMarkers(){
227628e43ccSMark Prins		$count=0;
228628e43ccSMark Prins		$color=imagecolorallocate ($this->image,0,0,0 );
229628e43ccSMark Prins		$bgcolor=imagecolorallocate ($this->image,200,200,200 );
230628e43ccSMark Prins		$markerBaseDir = dirname(__FILE__).'/icons';
231628e43ccSMark Prins		// loop thru marker array
232628e43ccSMark Prins		foreach($this->markers as $marker) {
233628e43ccSMark Prins			// set some local variables
234628e43ccSMark Prins			$markerLat = $marker['lat'];
235628e43ccSMark Prins			$markerLon = $marker['lon'];
236628e43ccSMark Prins			$markerType = $marker['type'];
237628e43ccSMark Prins			// clear variables from previous loops
238628e43ccSMark Prins			$markerFilename = '';
239628e43ccSMark Prins			$markerShadow = '';
240628e43ccSMark Prins			$matches = false;
241628e43ccSMark Prins			// check for marker type, get settings from markerPrototypes
242628e43ccSMark Prins			if($markerType){
243628e43ccSMark Prins				foreach($this->markerPrototypes as $markerPrototype){
244628e43ccSMark Prins					if(preg_match($markerPrototype['regex'],$markerType,$matches)){
245628e43ccSMark Prins						$markerFilename = $matches[0].$markerPrototype['extension'];
246628e43ccSMark Prins						if($markerPrototype['offsetImage']){
247628e43ccSMark Prins							list($markerImageOffsetX, $markerImageOffsetY)  = split(",",$markerPrototype['offsetImage']);
248628e43ccSMark Prins						}
249628e43ccSMark Prins						$markerShadow = $markerPrototype['shadow'];
250628e43ccSMark Prins						if($markerShadow){
251628e43ccSMark Prins							list($markerShadowOffsetX, $markerShadowOffsetY)  = split(",",$markerPrototype['offsetShadow']);
252628e43ccSMark Prins						}
253628e43ccSMark Prins					}
254628e43ccSMark Prins				}
255628e43ccSMark Prins			}
256628e43ccSMark Prins			// create img resource
257628e43ccSMark Prins			if(file_exists($markerBaseDir.'/'.$markerFilename)){
258628e43ccSMark Prins				$markerImg = imagecreatefrompng($markerBaseDir.'/'.$markerFilename);
259628e43ccSMark Prins			} else {
260628e43ccSMark Prins				$markerImg = imagecreatefrompng($markerBaseDir.'/marker.png');
261628e43ccSMark Prins			}
262628e43ccSMark Prins			// check for shadow + create shadow recource
263628e43ccSMark Prins			if($markerShadow && file_exists($markerBaseDir.'/'.$markerShadow)){
264628e43ccSMark Prins				$markerShadowImg = imagecreatefrompng($markerBaseDir.'/'.$markerShadow);
265628e43ccSMark Prins			}
266628e43ccSMark Prins			// calc position
267628e43ccSMark Prins			$destX = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($markerLon, $this->zoom)));
268628e43ccSMark Prins			$destY = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($markerLat, $this->zoom)));
269628e43ccSMark Prins			// copy shadow on basemap
270628e43ccSMark Prins			if($markerShadow && $markerShadowImg){
271628e43ccSMark Prins				imagecopy($this->image, $markerShadowImg, $destX+intval($markerShadowOffsetX), $destY+intval($markerShadowOffsetY),
272628e43ccSMark Prins				0, 0, imagesx($markerShadowImg), imagesy($markerShadowImg));
273628e43ccSMark Prins			}
274628e43ccSMark Prins			// copy marker on basemap above shadow
275628e43ccSMark Prins			imagecopy($this->image, $markerImg, $destX+intval($markerImageOffsetX), $destY+intval($markerImageOffsetY),
276628e43ccSMark Prins			0, 0, imagesx($markerImg), imagesy($markerImg));
277628e43ccSMark Prins			// add label
278628e43ccSMark Prins			imagestring ($this->image , 3 , $destX-imagesx($markerImg)+1 , $destY+intval($markerImageOffsetY)+1 , ++$count , $bgcolor );
279628e43ccSMark Prins			imagestring ($this->image , 3 , $destX-imagesx($markerImg) , $destY+intval($markerImageOffsetY) , $count , $color );
280628e43ccSMark Prins		};
281628e43ccSMark Prins	}
28257e65445SMark Prins
283628e43ccSMark Prins	/**
284628e43ccSMark Prins	 *
285628e43ccSMark Prins	 * @param string $url
286628e43ccSMark Prins	 * @return string
287628e43ccSMark Prins	 */
288628e43ccSMark Prins	public function tileUrlToFilename($url){
289628e43ccSMark Prins		return $this->tileCacheBaseDir."/".str_replace(array('http://'),'',$url);
290628e43ccSMark Prins	}
29157e65445SMark Prins
292628e43ccSMark Prins	/**
293628e43ccSMark Prins	 *
294628e43ccSMark Prins	 * @param string $url
295628e43ccSMark Prins	 */
296628e43ccSMark Prins	public function checkTileCache($url){
297628e43ccSMark Prins		$filename = $this->tileUrlToFilename($url);
298628e43ccSMark Prins		if(file_exists($filename)){
299628e43ccSMark Prins			return file_get_contents($filename);
300628e43ccSMark Prins		}
301628e43ccSMark Prins	}
302628e43ccSMark Prins
303628e43ccSMark Prins	public function checkMapCache(){
304628e43ccSMark Prins		$this->mapCacheID = md5($this->serializeParams());
305628e43ccSMark Prins		$filename = $this->mapCacheIDToFilename();
306628e43ccSMark Prins		if(file_exists($filename)) return true;
307628e43ccSMark Prins	}
308628e43ccSMark Prins
309628e43ccSMark Prins	public function serializeParams(){
3106914b920SMark Prins		return join("&",array($this->zoom,$this->lat,$this->lon,$this->width,$this->height, serialize($this->markers),$this->maptype, $this->kmlFileName, $this->gpxFileName, $this->geojsonFileName));
311628e43ccSMark Prins	}
312628e43ccSMark Prins
313628e43ccSMark Prins	public function mapCacheIDToFilename(){
314628e43ccSMark Prins		if(!$this->mapCacheFile){
315628e43ccSMark Prins			$this->mapCacheFile = $this->mapCacheBaseDir."/".$this->maptype."/".$this->zoom."/cache_".substr($this->mapCacheID,0,2)."/".substr($this->mapCacheID,2,2)."/".substr($this->mapCacheID,4);
316628e43ccSMark Prins		}
317628e43ccSMark Prins		return $this->mapCacheFile.".".$this->mapCacheExtension;
318628e43ccSMark Prins	}
31957e65445SMark Prins
3202d11d700SMark Prins	/**
3212d11d700SMark Prins	 * Recursively create the directory.
3222d11d700SMark Prins	 * @param string $pathname The directory path.
3232d11d700SMark Prins	 * @param int $mode File access mode. For more information on modes, read the details on the chmod manpage.
3242d11d700SMark Prins	 */
325628e43ccSMark Prins	public function mkdir_recursive($pathname, $mode){
326628e43ccSMark Prins		is_dir(dirname($pathname)) || $this->mkdir_recursive(dirname($pathname), $mode);
327628e43ccSMark Prins		return is_dir($pathname) || @mkdir($pathname, $mode);
328628e43ccSMark Prins	}
329628e43ccSMark Prins
3302d11d700SMark Prins	/**
3312d11d700SMark Prins	 * Write a tile into the cache.
3322d11d700SMark Prins	 * @param string $url
3332d11d700SMark Prins	 * @param mixed $data
3342d11d700SMark Prins	 */
335628e43ccSMark Prins	public function writeTileToCache($url, $data){
336628e43ccSMark Prins		$filename = $this->tileUrlToFilename($url);
337628e43ccSMark Prins		$this->mkdir_recursive(dirname($filename),0777);
338628e43ccSMark Prins		file_put_contents($filename, $data);
339628e43ccSMark Prins	}
34057e65445SMark Prins
3412d11d700SMark Prins	/**
3422d11d700SMark Prins	 * Fetch a tile and (if configured) store it in the cache.
3432d11d700SMark Prins	 * @param string $url
3442d11d700SMark Prins	 */
345628e43ccSMark Prins	public function fetchTile($url){
346628e43ccSMark Prins		if($this->useTileCache && ($cached = $this->checkTileCache($url))) return $cached;
347e4f115f4SMark Prins
348e4f115f4SMark Prins		$_UA = 'Mozilla/4.0 (compatible; DokuWikiSpatial HTTP Client; '.PHP_OS.')';
349e4f115f4SMark Prins		if(function_exists("curl_init")){
350e4f115f4SMark Prins			// use cUrl
351628e43ccSMark Prins			$ch = curl_init();
352628e43ccSMark Prins			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
353e4f115f4SMark Prins			curl_setopt($ch, CURLOPT_USERAGENT, $_UA);
354628e43ccSMark Prins			curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
355628e43ccSMark Prins			curl_setopt($ch, CURLOPT_URL, $url);
356628e43ccSMark Prins			$tile = curl_exec($ch);
357628e43ccSMark Prins			curl_close($ch);
358e4f115f4SMark Prins		} else {
359e4f115f4SMark Prins			// use file_get_contents
360e4f115f4SMark Prins			global $conf;
361e4f115f4SMark Prins			$opts = array(
362e4f115f4SMark Prins					'http'=>array(
363e4f115f4SMark Prins							'method'=>"GET",
364e4f115f4SMark Prins							'header'=>"Accept-language: en\r\n" .
365e4f115f4SMark Prins									"User-Agent: $_UA\r\n".
366e4f115f4SMark Prins									"accept: image/png\r\n",
367e4f115f4SMark Prins							'proxy' => "tcp://".$conf['proxy']['host'] . ":" . $conf['proxy']['port'],
368e4f115f4SMark Prins							'request_fulluri' => true,
369e4f115f4SMark Prins					)
370e4f115f4SMark Prins			);
371e4f115f4SMark Prins			$context = stream_context_create($opts);
372e4f115f4SMark Prins			$tile = file_get_contents($url, false, $context);
373e4f115f4SMark Prins		}
374628e43ccSMark Prins		if($tile && $this->useTileCache){
375628e43ccSMark Prins			$this->writeTileToCache($url,$tile);
376628e43ccSMark Prins		}
377628e43ccSMark Prins		return $tile;
378628e43ccSMark Prins	}
379628e43ccSMark Prins
380628e43ccSMark Prins	/**
381628e43ccSMark Prins	 * Draw gpx trace on the map.
382628e43ccSMark Prins	 */
383628e43ccSMark Prins	public function drawGPX(){
384c977deacSMark Prins		$col = imagecolorallocatealpha($this->image, 0, 0, 255, .4*127);
3856c6bb022SMark Prins		$gpxgeom = geoPHP::load(file_get_contents($this->gpxFileName),'gpx');
386c977deacSMark Prins		$this->drawGeometry($gpxgeom, $col);
387628e43ccSMark Prins	}
388628e43ccSMark Prins
3896914b920SMark Prins	/**
3906914b920SMark Prins	 * Draw geojson on the map.
3916914b920SMark Prins	 */
3926914b920SMark Prins	public function drawGeojson(){
3936914b920SMark Prins		$col = imagecolorallocatealpha($this->image, 255, 0, 255, .4*127);
3946914b920SMark Prins		$gpxgeom = geoPHP::load(file_get_contents($this->geojsonFileName),'json');
3956914b920SMark Prins		$this->drawGeometry($gpxgeom, $col);
3966914b920SMark Prins	}
39757e65445SMark Prins
398628e43ccSMark Prins	/**
399628e43ccSMark Prins	 * Draw kml trace on the map.
400628e43ccSMark Prins	 */
401628e43ccSMark Prins	public function drawKML(){
4022d11d700SMark Prins		// TODO get colour from kml node (not currently supported in geoPHP)
403c977deacSMark Prins		$col = imagecolorallocatealpha($this->image, 255, 0, 0, .4*127);
404c977deacSMark Prins		$kmlgeom = geoPHP::load(file_get_contents($this->kmlFileName),'kml');
405c977deacSMark Prins		$this->drawGeometry($kmlgeom, $col);
406c977deacSMark Prins	}
40757e65445SMark Prins
408c977deacSMark Prins	/**
409c977deacSMark Prins	 * Draw geometry or geometry collection on the map.
410c977deacSMark Prins	 * @param Geometry $geom
411c977deacSMark Prins	 * @param int $colour drawing colour
412c977deacSMark Prins	 */
413c977deacSMark Prins	private function drawGeometry($geom, $colour){
4146c6bb022SMark Prins		switch ($geom->geometryType()) {
415c977deacSMark Prins			case 'GeometryCollection':
416c977deacSMark Prins				// recursively draw part of the collection
417c977deacSMark Prins				for ($i = 1; $i < $geom->numGeometries()+1; $i++) {
418c977deacSMark Prins					$_geom = $geom->geometryN($i);
419c977deacSMark Prins					$this->drawGeometry($_geom, $colour);
420c977deacSMark Prins				}
4216c6bb022SMark Prins				break;
422c977deacSMark Prins			case 'MultiPolygon':
423c977deacSMark Prins				// TODO implement / do nothing
424c977deacSMark Prins				break;
425c977deacSMark Prins			case 'MultiLineString':
426c977deacSMark Prins				// TODO implement / do nothing
427c977deacSMark Prins				break;
428c977deacSMark Prins			case 'MultiPoint':
429c977deacSMark Prins				// TODO implement / do nothing
4306c6bb022SMark Prins				break;
4316c6bb022SMark Prins			case 'Polygon':
432c977deacSMark Prins				$this->drawPolygon($geom, $colour);
433c977deacSMark Prins				break;
434c977deacSMark Prins			case 'LineString':
435c977deacSMark Prins				$this->drawLineString($geom, $colour);
436c977deacSMark Prins				break;
437c977deacSMark Prins			case 'Point':
438c977deacSMark Prins				$this->drawPoint($geom, $colour);
4396c6bb022SMark Prins				break;
4406c6bb022SMark Prins			default:
4412d11d700SMark Prins				//draw nothing
4426c6bb022SMark Prins				break;
4436c6bb022SMark Prins		}
4446c6bb022SMark Prins	}
445c977deacSMark Prins
446e61425c7SMark Prins	/**
447e61425c7SMark Prins	 * Draw a line on the map.
448e61425c7SMark Prins	 * @param LineString $line
449c977deacSMark Prins	 * @param int $colour drawing colour
450e61425c7SMark Prins	 */
451da6f229fSMark Prins	private function drawLineString($line, $colour){
452cc74a83cSMark Prins		imagesetthickness($this->image,2);
453da6f229fSMark Prins		for ($p = 1; $p < $line->numGeometries(); $p++) {
454da6f229fSMark Prins			// get first pair of points
455da6f229fSMark Prins			$p1 = $line->geometryN($p);
456da6f229fSMark Prins			$p2 = $line->geometryN($p+1);
457da6f229fSMark Prins			// translate to paper space
458da6f229fSMark Prins			$x1 = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($p1->x(), $this->zoom)));
459da6f229fSMark Prins			$y1 = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($p1->y(), $this->zoom)));
460da6f229fSMark Prins			$x2 = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($p2->x(), $this->zoom)));
461da6f229fSMark Prins			$y2 = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($p2->y(), $this->zoom)));
462da6f229fSMark Prins			// draw to image
463da6f229fSMark Prins			imageline ($this->image, $x1, $y1, $x2, $y2, $colour);
464da6f229fSMark Prins		}
465e61425c7SMark Prins		imagesetthickness($this->image,1);
466da6f229fSMark Prins	}
467c977deacSMark Prins
468e61425c7SMark Prins	/**
469e61425c7SMark Prins	 * Draw a point on the map.
470e61425c7SMark Prins	 * @param Point $point
471c977deacSMark Prins	 * @param int $colour drawing colour
472e61425c7SMark Prins	 */
473da6f229fSMark Prins	private function drawPoint($point, $colour){
474c977deacSMark Prins		imagesetthickness($this->image,2);
475da6f229fSMark Prins		// translate to paper space
476da6f229fSMark Prins		$cx = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($point->x(), $this->zoom)));
477da6f229fSMark Prins		$cy = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($point->y(), $this->zoom)));
478cc74a83cSMark Prins		$r = 5;
479da6f229fSMark Prins		// draw to image
480cc74a83cSMark Prins		// imageellipse($this->image, $cx, $cy,$r, $r, $colour);
481cc74a83cSMark Prins		imagefilledellipse ($this->image, $cx, $cy, $r, $r, $colour);
482cc74a83cSMark Prins		// don't use imageellipse because the imagesetthickness function has
483c977deacSMark Prins		// no effect. So the better workaround is to use imagearc.
484cc74a83cSMark Prins		imagearc($this->image, $cx, $cy, $r, $r, 0, 359, $colour);
485e61425c7SMark Prins		imagesetthickness($this->image,1);
486da6f229fSMark Prins	}
487c977deacSMark Prins
488e61425c7SMark Prins	/**
489e61425c7SMark Prins	 * Draw a polygon on the map.
490e61425c7SMark Prins	 * @param Polygon $polygon
491c977deacSMark Prins	 * @param int $colour drawing colour
492e61425c7SMark Prins	 */
493e61425c7SMark Prins	private function drawPolygon($polygon, $colour){
494c977deacSMark Prins		// TODO implementation of drawing holes,
495c977deacSMark Prins		// maybe draw the polygon to an in-memory image and use imagecopy, draw polygon in col., draw holes in bgcol?
496c977deacSMark Prins
497c977deacSMark Prins		//print_r('Polygon:<br />');
498c977deacSMark Prins		//print_r($polygon);
499c977deacSMark Prins
500c977deacSMark Prins		$extPoints = array();
501c977deacSMark Prins		// extring is a linestring actually..
502c977deacSMark Prins		$extRing = $polygon->exteriorRing();
503c977deacSMark Prins
504c977deacSMark Prins		for ($i = 1; $i < $extRing->numGeometries(); $i++) {
505c977deacSMark Prins			$p1 = $extRing->geometryN($i);
506c977deacSMark Prins			$x = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($p1->x(), $this->zoom)));
507c977deacSMark Prins			$y = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($p1->y(), $this->zoom)));
508c977deacSMark Prins			$extPoints[]=$x;
509c977deacSMark Prins			$extPoints[]=$y;
510e61425c7SMark Prins		}
511c977deacSMark Prins		//print_r('points:('.($i-1).')<br />');
512c977deacSMark Prins		//print_r($extPoints);
513c977deacSMark Prins		//imagepolygon ($this->image, $extPoints, $i-1, $colour );
514c977deacSMark Prins		imagefilledpolygon($this->image, $extPoints, $i-1, $colour );
515c977deacSMark Prins	}
516c977deacSMark Prins
517628e43ccSMark Prins	/**
518628e43ccSMark Prins	 * add copyright and origin notice and icons to the map.
519628e43ccSMark Prins	 */
520628e43ccSMark Prins	public function drawCopyright(){
521628e43ccSMark Prins		$logoBaseDir = dirname(__FILE__).'/'.'logo/';
522628e43ccSMark Prins		$logoImg = imagecreatefrompng($logoBaseDir.$this->tileInfo['openstreetmap']['logo']);
523628e43ccSMark Prins		$textcolor = imagecolorallocate($this->image, 0, 0, 0);
524628e43ccSMark Prins		$bgcolor = imagecolorallocate($this->image, 200, 200, 200);
525628e43ccSMark Prins
5262d11d700SMark Prins		imagecopy($this->image, $logoImg, 0, imagesy($this->image)-imagesy($logoImg), 0, 0, imagesx($logoImg), imagesy($logoImg));
527628e43ccSMark Prins		imagestring($this->image , 1, imagesx($logoImg)+2 ,imagesy($this->image)-imagesy($logoImg)+1 ,$this->tileInfo['openstreetmap']['txt'], $bgcolor );
528628e43ccSMark Prins		imagestring($this->image , 1, imagesx($logoImg)+1 ,imagesy($this->image)-imagesy($logoImg) ,$this->tileInfo['openstreetmap']['txt'] ,$textcolor );
529628e43ccSMark Prins
530628e43ccSMark Prins		// additional tile source info, ie. who created/hosted the tiles
531628e43ccSMark Prins		if ($this->maptype!='openstreetmap') {
532628e43ccSMark Prins			$iconImg = imagecreatefrompng($logoBaseDir.$this->tileInfo[$this->maptype]['logo']);
5332d11d700SMark Prins			imagecopy($this->image, $iconImg, imagesx($logoImg)+1, imagesy($this->image)-imagesy($iconImg), 0, 0, imagesx($iconImg), imagesy($iconImg));
534628e43ccSMark Prins			imagestring($this->image, 1, imagesx($logoImg)+imagesx($iconImg)+4, imagesy($this->image)-ceil(imagesy($logoImg)/2)+1, $this->tileInfo[$this->maptype]['txt'], $bgcolor );
535628e43ccSMark Prins			imagestring($this->image, 1, imagesx($logoImg)+imagesx($iconImg)+3, imagesy($this->image)-ceil(imagesy($logoImg)/2), $this->tileInfo[$this->maptype]['txt'], $textcolor );
536628e43ccSMark Prins		}
537628e43ccSMark Prins	}
538cc74a83cSMark Prins
539628e43ccSMark Prins	/**
540628e43ccSMark Prins	 * make the map.
541628e43ccSMark Prins	 */
542628e43ccSMark Prins	public function makeMap(){
543628e43ccSMark Prins		$this->initCoords();
544628e43ccSMark Prins		$this->createBaseMap();
545e4f115f4SMark Prins		if(!empty($this->markers))$this->placeMarkers();
546628e43ccSMark Prins		if(file_exists($this->kmlFileName)) $this->drawKML();
547628e43ccSMark Prins		if(file_exists($this->gpxFileName)) $this->drawGPX();
5486914b920SMark Prins		if(file_exists($this->geojsonFileName)) $this->drawGeojson();
5496914b920SMark Prins
550628e43ccSMark Prins		$this->drawCopyright();
551628e43ccSMark Prins	}
552cc74a83cSMark Prins
553628e43ccSMark Prins	/**
554e4f115f4SMark Prins	 * Calculate the lat/lon/zoom values to make sure that all of the markers and gpx/kml are on the map.
5552d11d700SMark Prins	 * @param float $paddingFactor buffer constant to enlarge (>1.0) the zoom level
5562d11d700SMark Prins	 */
5572d11d700SMark Prins	private function autoZoom($paddingFactor=1.0){
5582d11d700SMark Prins		$geoms = array();
559e4f115f4SMark Prins		$geoms[] = new Point($this->lon, $this->lat);
560e4f115f4SMark Prins		if(!empty($this->markers)){
5612d11d700SMark Prins			foreach($this->markers as $marker){
5622d11d700SMark Prins				$geoms[] = new Point($marker['lon'],$marker['lat']);
5632d11d700SMark Prins			}
5642d11d700SMark Prins		}
5652d11d700SMark Prins		if(file_exists($this->kmlFileName)){
5662d11d700SMark Prins			$geoms[] = geoPHP::load(file_get_contents($this->kmlFileName),'kml');
5672d11d700SMark Prins		}
5682d11d700SMark Prins		if(file_exists($this->gpxFileName)) {
5692d11d700SMark Prins			$geoms[] = geoPHP::load(file_get_contents($this->gpxFileName),'gpx');
5702d11d700SMark Prins		}
5716914b920SMark Prins		if(file_exists($this->geojsonFileName)) {
5726914b920SMark Prins			$geoms[] = geoPHP::load(file_get_contents($this->geojsonFileName),'geojson');
5736914b920SMark Prins		}
5746914b920SMark Prins
575e4f115f4SMark Prins		if (count($geoms)<=1) return;
57653bfe4a3SMark Prins
5772d11d700SMark Prins		$geom = new GeometryCollection($geoms);
5782d11d700SMark Prins		$centroid=$geom->centroid();
5792d11d700SMark Prins		$bbox=$geom->getBBox();
5802d11d700SMark Prins
5812d11d700SMark Prins		// determine vertical resolution, this depends on the distance from the equator
5822d11d700SMark Prins		//	$vy00 = log(tan(M_PI*(0.25 + $centroid->getY()/360)));
5832d11d700SMark Prins		$vy0 = log(tan(M_PI*(0.25 + $bbox['miny']/360)));
5842d11d700SMark Prins		$vy1 = log(tan(M_PI*(0.25 + $bbox['maxy']/360)));
58553bfe4a3SMark Prins		$zoomFactorPowered = ($this->height/2) / (40.7436654315252 * ($vy1 - $vy0));
5862d11d700SMark Prins		$resolutionVertical = 360 / ($zoomFactorPowered * $this->tileSize);
5872d11d700SMark Prins		// determine horizontal resolution
5882d11d700SMark Prins		$resolutionHorizontal = ($bbox['maxx']-$bbox['minx']) / $this->width;
5892d11d700SMark Prins		$resolution = max($resolutionHorizontal, $resolutionVertical) * $paddingFactor;
5902d11d700SMark Prins		$zoom = log(360 / ($resolution * $this->tileSize), 2);
5912d11d700SMark Prins
5922d11d700SMark Prins		$this->zoom = floor($zoom);
5932d11d700SMark Prins		$this->lon=$centroid->getX();
5942d11d700SMark Prins		$this->lat=$centroid->getY();
5952d11d700SMark Prins	}
5962d11d700SMark Prins
5972d11d700SMark Prins	/**
598628e43ccSMark Prins	 * get the map, this may return a reference to a cached copy.
599628e43ccSMark Prins	 * @return string url relative to media dir
600628e43ccSMark Prins	 */
601628e43ccSMark Prins	public function getMap(){
6022d11d700SMark Prins		if($this->autoZoomExtent) $this->autoZoom() ;
6032d11d700SMark Prins
604628e43ccSMark Prins		// use map cache, so check cache for map
605628e43ccSMark Prins		if(!$this->checkMapCache()){
606628e43ccSMark Prins			// map is not in cache, needs to be build
607628e43ccSMark Prins			$this->makeMap();
608628e43ccSMark Prins			$this->mkdir_recursive(dirname($this->mapCacheIDToFilename()),0777);
609628e43ccSMark Prins			imagepng($this->image,$this->mapCacheIDToFilename(),9);
610628e43ccSMark Prins		}
611628e43ccSMark Prins		$this->doc =$this->mapCacheIDToFilename();
612628e43ccSMark Prins		// make url relative to media dir
613628e43ccSMark Prins		return str_replace($this->mediaBaseDir, '', $this->doc);
614628e43ccSMark Prins	}
615628e43ccSMark Prins}
616