xref: /plugin/openlayersmap/syntax/olmap.php (revision dafaaac7a3cfff3db9159a7140b14e89128d24a0)
1<?php
2/*
3 * Copyright (c) 2008-2020 Mark C. Prins <mprins@users.sf.net>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17if (! defined ( 'DOKU_INC' ))
18	define ( 'DOKU_INC', realpath ( dirname ( __FILE__ ) . '/../../' ) . '/' );
19if (! defined ( 'DOKU_PLUGIN' ))
20	define ( 'DOKU_PLUGIN', DOKU_INC . 'lib/plugins/' );
21require_once (DOKU_PLUGIN . 'syntax.php');
22
23/**
24 * DokuWiki Plugin openlayersmap (Syntax Component).
25 * Provides for display of an OpenLayers based map in a wiki page.
26 *
27 * @author Mark Prins
28 */
29class syntax_plugin_openlayersmap_olmap extends DokuWiki_Syntax_Plugin {
30
31	/**
32	 * defaults of the known attributes of the olmap tag.
33	 */
34	private $dflt = array (
35			'id' => 'olmap',
36			'width' => '550px',
37			'height' => '450px',
38			'lat' => 50.0,
39			'lon' => 5.1,
40			'zoom' => 12,
41			'autozoom' => 1,
42			'statusbar' => true,
43			'toolbar' => true,
44			'controls' => true,
45			'poihoverstyle' => false,
46			'baselyr' => 'OpenStreetMap',
47			'gpxfile' => '',
48			'kmlfile' => '',
49			'geojsonfile' => '',
50			'summary' => ''
51	);
52
53	/**
54	 *
55	 * @see DokuWiki_Syntax_Plugin::getType()
56	 */
57	function getType() {
58		return 'substition';
59	}
60
61	/**
62	 *
63	 * @see DokuWiki_Syntax_Plugin::getPType()
64	 */
65	function getPType() {
66		return 'block';
67	}
68
69	/**
70	 *
71	 * @see Doku_Parser_Mode::getSort()
72	 */
73	function getSort() {
74		return 901;
75	}
76
77	/**
78	 *
79	 * @see Doku_Parser_Mode::connectTo()
80	 */
81	function connectTo($mode) {
82		$this->Lexer->addSpecialPattern ( '<olmap ?[^>\n]*>.*?</olmap>', $mode, 'plugin_openlayersmap_olmap' );
83	}
84
85	/**
86	 *
87	 * @see DokuWiki_Syntax_Plugin::handle()
88	 */
89	function handle($match, $state, $pos, Doku_Handler $handler) {
90		// break matched cdata into its components
91		list ( $str_params, $str_points ) = explode ( '>', substr ( $match, 7, - 9 ), 2 );
92		// get the lat/lon for adding them to the metadata (used by geotag)
93		preg_match ( '(lat[:|=]\"-?\d*\.?\d*\")', $match, $mainLat );
94		preg_match ( '(lon[:|=]\"-?\d*\.?\d*\")', $match, $mainLon );
95		$mainLat = substr ( $mainLat [0], 5, - 1 );
96		$mainLon = substr ( $mainLon [0], 5, - 1 );
97		if (!is_numeric($mainLat)) {
98			$mainLat = $this->dflt ['lat'];
99		}
100		if (!is_numeric($mainLon)) {
101			$mainLon = $this->dflt ['lon'];
102		}
103
104		$gmap = $this->_extract_params ( $str_params );
105		$overlay = $this->_extract_points ( $str_points );
106		$_firstimageID = '';
107
108		$_nocache = false;
109		// choose maptype based on the specified tag
110		$imgUrl = "{{";
111		if (stripos ( $gmap ['baselyr'], 'google' ) !== false) {
112			// Google
113			$imgUrl .= $this->_getGoogle ( $gmap, $overlay );
114			$imgUrl .= "&.png";
115		} elseif (stripos ( $gmap ['baselyr'], 'bing' ) !== false) {
116			// Bing
117			if (! $this->getConf ( 'bingAPIKey' )) {
118				// in case there is no Bing api key we'll use OSM
119				$_firstimageID = $this->_getStaticOSM ( $gmap, $overlay );
120				$imgUrl .= $_firstimageID;
121				if ($this->getConf ( 'optionStaticMapGenerator' ) == 'remote') {
122					$imgUrl .= "&.png";
123				}
124			} else {
125				// seems that Bing doesn't like the DW client, turn off caching
126				$_nocache = true;
127				$imgUrl .= $this->_getBing ( $gmap, $overlay ) . "&.png";
128			}
129		} /* elseif (stripos ( $gmap ['baselyr'], 'mapquest' ) !== false) {
130			// MapQuest
131			if (! $this->getConf ( 'mapquestAPIKey' )) {
132				// no API key for MapQuest, use OSM
133				$_firstimageID = $this->_getStaticOSM ( $gmap, $overlay );
134				$imgUrl .= $_firstimageID;
135				if ($this->getConf ( 'optionStaticMapGenerator' ) == 'remote') {
136					$imgUrl .= "&.png";
137				}
138			} else {
139				$imgUrl .= $this->_getMapQuest ( $gmap, $overlay );
140				$imgUrl .= "&.png";
141			}
142		} */ else {
143			// default OSM
144			$_firstimageID = $this->_getStaticOSM ( $gmap, $overlay );
145			$imgUrl .= $_firstimageID;
146			if ($this->getConf ( 'optionStaticMapGenerator' ) == 'remote') {
147				$imgUrl .= "&.png";
148			}
149		}
150
151		// append dw p_render specific params and render
152		$imgUrl .= "?" . str_replace ( "px", "", $gmap ['width'] ) . "x" . str_replace ( "px", "", $gmap ['height'] );
153		$imgUrl .= "&nolink";
154
155		// add nocache option for selected services
156		if ($_nocache) {
157			$imgUrl .= "&nocache";
158		}
159
160		$imgUrl .= " |".$gmap ['summary'] . " }}";
161
162		// dbglog($imgUrl,"complete image tags is:");
163
164		$mapid = $gmap ['id'];
165		// create a javascript parameter string for the map
166		$param = '';
167		foreach ( $gmap as $key => $val ) {
168			$param .= is_numeric ( $val ) ? "$key: $val, " : "$key: '" . hsc ( $val ) . "', ";
169		}
170		if (! empty ( $param )) {
171			$param = substr ( $param, 0, - 2 );
172		}
173		unset ( $gmap ['id'] );
174
175		// create a javascript serialisation of the point data
176		$poi = '';
177		$poitable = '';
178		$rowId = 0;
179		if (! empty ( $overlay )) {
180			foreach ( $overlay as $data ) {
181				list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data;
182				$rowId ++;
183				$poi .= ", {lat: $lat, lon: $lon, txt: '$text', angle: $angle, opacity: $opacity, img: '$img', rowId: $rowId}";
184
185				if ($this->getConf ( 'displayformat' ) === 'DMS') {
186					$lat = $this->convertLat ( $lat );
187					$lon = $this->convertLon ( $lon );
188				} else {
189					$lat .= 'º';
190					$lon .= 'º';
191				}
192
193				$poitable .= '
194					<tr>
195					<td class="rowId">' . $rowId . '</td>
196					<td class="icon"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/icons/' . $img . '" alt="'. substr($img, 0, -4) . $this->getlang('alt_legend_poi').' " /></td>
197					<td class="lat" title="' . $this->getLang ( 'olmapPOIlatTitle' ) . '">' . $lat . '</td>
198					<td class="lon" title="' . $this->getLang ( 'olmapPOIlonTitle' ) . '">' . $lon . '</td>
199					<td class="txt">' . $text . '</td>
200					</tr>';
201			}
202			$poi = substr ( $poi, 2 );
203		}
204		if (! empty ( $gmap ['kmlfile'] )) {
205			$poitable .= '
206					<tr>
207					<td class="rowId"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/toolbar/kml_file.png" alt="KML file" /></td>
208					<td class="icon"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/toolbar/kml_line.png" alt="' . $this->getlang('alt_legend_kml') .'" /></td>
209					<td class="txt" colspan="3">KML track: ' . $this->getFileName ( $gmap ['kmlfile'] ) . '</td>
210					</tr>';
211		}
212		if (! empty ( $gmap ['gpxfile'] )) {
213			$poitable .= '
214					<tr>
215					<td class="rowId"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/toolbar/gpx_file.png" alt="GPX file" /></td>
216					<td class="icon"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/toolbar/gpx_line.png" alt="' . $this->getlang('alt_legend_gpx') .'" /></td>
217					<td class="txt" colspan="3">GPX track: ' . $this->getFileName ( $gmap ['gpxfile'] ) . '</td>
218					</tr>';
219		}
220		if (! empty ( $gmap ['geojsonfile'] )) {
221			$poitable .= '
222					<tr>
223					<td class="rowId"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/toolbar/geojson_file.png" alt="GeoJSON file" /></td>
224					<td class="icon"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/toolbar/geojson_line.png" alt="' . $this->getlang('alt_legend_geojson') .'" /></td>
225					<td class="txt" colspan="3">GeoJSON track: ' . $this->getFileName ( $gmap ['geojsonfile'] ) . '</td>
226					</tr>';
227		}
228
229		$autozoom = empty ( $gmap ['autozoom'] ) ? $this->getConf ( 'autoZoomMap' ) : $gmap ['autozoom'];
230		$js .= "{mapOpts: {" . $param . ", displayformat: '" . $this->getConf ( 'displayformat' ) . "', autozoom: " . $autozoom . "}, poi: [$poi]};";
231		// unescape the json
232		$poitable = stripslashes ( $poitable );
233
234		return array (
235				$mapid,
236				$js,
237				$mainLat,
238				$mainLon,
239				$poitable,
240				$gmap ['summary'],
241				$imgUrl,
242				$_firstimageID
243		);
244	}
245
246	/**
247	 *
248	 * @see DokuWiki_Syntax_Plugin::render()
249	 */
250	function render($mode, Doku_Renderer $renderer, $data) {
251		// set to true after external scripts tags are written
252		static $initialised = false;
253		// incremented for each map tag in the page source so we can keep track of each map in this page
254		static $mapnumber = 0;
255
256		// dbglog($data, 'olmap::render() data.');
257		list ( $mapid, $param, $mainLat, $mainLon, $poitable, $poitabledesc, $staticImgUrl, $_firstimage ) = $data;
258
259		if ($mode == 'xhtml') {
260			$olscript = '';
261			$olEnable = false;
262			$gscript = '';
263			$gEnable = $this->getConf ( 'enableGoogle' );
264			$stamenEnable = $this->getConf ( 'enableStamen' );
265			$osmEnable = $this->getConf ( 'enableOSM' );
266			$enableBing = $this->getConf ( 'enableBing' );
267
268			$scriptEnable = '';
269			if (! $initialised) {
270				$initialised = true;
271				// render necessary script tags
272				if ($gEnable) {
273					$gscript = '<script charset="utf-8" defer="defer" src="//maps.google.com/maps/api/js?v=3.29&amp;key='.$this->getConf ( 'googleAPIkey' ).'"></script>';
274				}
275				$olscript = '<script charset="utf-8" defer="defer" src="' . DOKU_BASE . 'lib/plugins/openlayersmap/lib/OpenLayers.js"></script>';
276
277				$scriptEnable = '<script defer="defer" charset="utf-8" src="data:text/javascript;base64,';
278				$scriptSrc = $olscript ? 'olEnable = true;' : 'olEnable = false;';
279				$scriptSrc .= 'gEnable = ' . ($gEnable ? 'true' : 'false') . ';';
280				$scriptSrc .= 'osmEnable = ' . ($osmEnable ? 'true' : 'false') . ';';
281				$scriptSrc .= 'stamenEnable = ' . ($stamenEnable ? 'true' : 'false') . ';';
282				$scriptSrc .= 'bEnable = ' . ($enableBing ? 'true' : 'false') . ';';
283				$scriptSrc .= 'bApiKey="' . $this->getConf ( 'bingAPIKey' ) . '";';
284				$scriptSrc .= 'tfApiKey="' . $this->getConf ( 'tfApiKey' ) . '";';
285				$scriptSrc .= 'gApiKey="' . $this->getConf ( 'googleAPIkey' ) . '";';
286				$scriptEnable .= base64_encode($scriptSrc);
287				$scriptEnable .= '"></script>';
288			}
289			$renderer->doc .= "$gscript\n$olscript\n$scriptEnable";
290			$renderer->doc .= '<div class="olMapHelp">' . $this->locale_xhtml ( "help" ) . '</div>';
291			if ($this->getConf ( 'enableA11y' )) {
292				$renderer->doc .= '<div id="' . $mapid . '-static" class="olStaticMap">' . p_render ( $mode, p_get_instructions ( $staticImgUrl ), $info ) . '</div>';
293			}
294			$renderer->doc .= '<div id="' . $mapid . '-clearer" class="clearer"><p>&nbsp;</p></div>';
295			if ($this->getConf ( 'enableA11y' )) {
296				// render a table of the POI for the print and a11y presentation, it is hidden using javascript
297				$renderer->doc .= '<div class="olPOItableSpan" id="' . $mapid . '-table-span">
298					<table class="olPOItable" id="' . $mapid . '-table">
299					<caption class="olPOITblCaption">' . $this->getLang ( 'olmapPOItitle' ) . '</caption>
300					<thead class="olPOITblHeader">
301					<tr>
302					<th class="rowId" scope="col">id</th>
303					<th class="icon" scope="col">' . $this->getLang ( 'olmapPOIicon' ) . '</th>
304					<th class="lat" scope="col" title="' . $this->getLang ( 'olmapPOIlatTitle' ) . '">' . $this->getLang ( 'olmapPOIlat' ) . '</th>
305					<th class="lon" scope="col" title="' . $this->getLang ( 'olmapPOIlonTitle' ) . '">' . $this->getLang ( 'olmapPOIlon' ) . '</th>
306					<th class="txt" scope="col">' . $this->getLang ( 'olmapPOItxt' ) . '</th>
307					</tr>
308					</thead>';
309				if ($poitabledesc != '') {
310					$renderer->doc .= '<tfoot class="olPOITblFooter"><tr><td colspan="5">' . $poitabledesc . '</td></tr></tfoot>';
311				}
312				$renderer->doc .= '<tbody class="olPOITblBody">' . $poitable . '</tbody>
313					</table></div>';
314			}
315			// render inline mapscript parts
316			$renderer->doc .= '<script charset="utf-8" defer="defer" src="data:text/javascript;base64,';
317			$renderer->doc .= base64_encode("olMapData[$mapnumber] = $param");
318			$renderer->doc .= '"></script>';
319			$mapnumber ++;
320			return true;
321		} elseif ($mode == 'metadata') {
322			if (! (($this->dflt ['lat'] == $mainLat) && ($this->dflt ['lon'] == $mainLon))) {
323				// render geo metadata, unless they are the default
324				$renderer->meta ['geo'] ['lat'] = $mainLat;
325				$renderer->meta ['geo'] ['lon'] = $mainLon;
326				if ($geophp = &plugin_load ( 'helper', 'geophp' )) {
327					// if we have the geoPHP helper, add the geohash
328					// fails with older php versions.. $renderer->meta['geo']['geohash'] = (new Point($mainLon,$mainLat))->out('geohash');
329					$p = new Point ( $mainLon, $mainLat );
330					$renderer->meta ['geo'] ['geohash'] = $p->out ( 'geohash' );
331				}
332			}
333
334			if (($this->getConf ( 'enableA11y' )) && (! empty ( $_firstimage ))) {
335				// add map local image into relation/firstimage if not already filled and when it is a local image
336
337				global $ID;
338				$rel = p_get_metadata ( $ID, 'relation', METADATA_RENDER_USING_CACHE );
339				$img = $rel ['firstimage'];
340				if (empty ( $img ) /* || $img == $_firstimage*/){
341					//dbglog ( $_firstimage, 'olmap::render#rendering image relation metadata for _firstimage as $img was empty or the same.' );
342					// This seems to never work; the firstimage entry in the .meta file is empty
343					// $renderer->meta['relation']['firstimage'] = $_firstimage;
344
345					// ... and neither does this; the firstimage entry in the .meta file is empty
346					// $relation = array('relation'=>array('firstimage'=>$_firstimage));
347					// p_set_metadata($ID, $relation, false, false);
348
349					// ... this works
350					$renderer->internalmedia ( $_firstimage, $poitabledesc );
351				}
352			}
353			return true;
354		}
355		return false;
356	}
357
358	/**
359	 * extract parameters for the map from the parameter string
360	 *
361	 * @param string $str_params
362	 *        	string of key="value" pairs
363	 * @return array associative array of parameters key=>value
364	 */
365	private function _extract_params($str_params) {
366		$param = array ();
367		preg_match_all ( '/(\w*)="(.*?)"/us', $str_params, $param, PREG_SET_ORDER );
368		// parse match for instructions, break into key value pairs
369		$gmap = $this->dflt;
370		foreach ( $gmap as $key => &$value ) {
371			$defval = $this->getConf ( 'default_' . $key );
372			if ($defval !== '') {
373				$value = $defval;
374			}
375		}
376		unset ( $value );
377		foreach ( $param as $kvpair ) {
378			list ( $match, $key, $val ) = $kvpair;
379			$key = strtolower ( $key );
380			if (isset ( $gmap [$key] )) {
381				if ($key == 'summary') {
382					// preserve case for summary field
383					$gmap [$key] = $val;
384				} elseif ($key == 'id') {
385					// preserve case for id field
386					$gmap [$key] = $val;
387				} else {
388					$gmap [$key] = strtolower ( $val );
389				}
390			}
391		}
392		return $gmap;
393	}
394
395	/**
396	 * extract overlay points for the map from the wiki syntax data
397	 *
398	 * @param string $str_points
399	 *        	multi-line string of lat,lon,text triplets
400	 * @return array multi-dimensional array of lat,lon,text triplets
401	 */
402	private function _extract_points($str_points) {
403		$point = array ();
404		// preg_match_all('/^([+-]?[0-9].*?),\s*([+-]?[0-9].*?),(.*?),(.*?),(.*?),(.*)$/um', $str_points, $point, PREG_SET_ORDER);
405		/*
406		 * group 1: ([+-]?[0-9]+(?:\.[0-9]*)?) group 2: ([+-]?[0-9]+(?:\.[0-9]*)?) group 3: (.*?) group 4: (.*?) group 5: (.*?) group 6: (.*)
407		 */
408		preg_match_all ( '/^([+-]?[0-9]+(?:\.[0-9]*)?),\s*([+-]?[0-9]+(?:\.[0-9]*)?),(.*?),(.*?),(.*?),(.*)$/um', $str_points, $point, PREG_SET_ORDER );
409		// create poi array
410		$overlay = array ();
411		foreach ( $point as $pt ) {
412			list ( $match, $lat, $lon, $angle, $opacity, $img, $text ) = $pt;
413			$lat = is_numeric ( $lat ) ? $lat : 0;
414			$lon = is_numeric ( $lon ) ? $lon : 0;
415			$angle = is_numeric ( $angle ) ? $angle : 0;
416			$opacity = is_numeric ( $opacity ) ? $opacity : 0.8;
417			// TODO validate using exist & set up default img?
418			$img = trim ( $img );
419			$text = p_get_instructions ( $text );
420			// dbg ( $text );
421			$text = p_render ( "xhtml", $text, $info );
422			// dbg ( $text );
423			$text = addslashes ( str_replace ( "\n", "", $text ) );
424			$overlay [] = array (
425					$lat,
426					$lon,
427					$text,
428					$angle,
429					$opacity,
430					$img
431			);
432		}
433		return $overlay;
434	}
435
436	/**
437	 * Create a MapQuest static map API image url.
438	 *
439	 * @param array $gmap
440	 * @param array $overlay
441	 */
442	 /*
443	private function _getMapQuest($gmap, $overlay) {
444		$sUrl = $this->getConf ( 'iconUrlOverload' );
445		if (! $sUrl) {
446			$sUrl = DOKU_URL;
447		}
448		switch ($gmap ['baselyr']) {
449			case 'mapquest hybrid' :
450				$maptype = 'hyb';
451				break;
452			case 'mapquest sat' :
453				// because sat coverage is very limited use 'hyb' instead of 'sat' so we don't get a blank map
454				$maptype = 'hyb';
455				break;
456			case 'mapquest road' :
457			default :
458				$maptype = 'map';
459				break;
460		}
461		$imgUrl = "http://open.mapquestapi.com/staticmap/v4/getmap?declutter=true&";
462		if (count ( $overlay ) < 1) {
463			$imgUrl .= "?center=" . $gmap ['lat'] . "," . $gmap ['lon'];
464			// max level for mapquest is 16
465			if ($gmap ['zoom'] > 16) {
466				$imgUrl .= "&zoom=16";
467			} else {
468				$imgUrl .= "&zoom=" . $gmap ['zoom'];
469			}
470		}
471		// use bestfit instead of center/zoom, needs upperleft/lowerright corners
472		// $bbox=$this->_calcBBOX($overlay, $gmap['lat'], $gmap['lon']);
473		// $imgUrl .= "bestfit=".$bbox['minlat'].",".$bbox['maxlon'].",".$bbox['maxlat'].",".$bbox['minlon'];
474
475		// TODO declutter option works well for square maps but not for rectangular, maybe compensate for that or compensate the mbr..
476		$imgUrl .= "&size=" . str_replace ( "px", "", $gmap ['width'] ) . "," . str_replace ( "px", "", $gmap ['height'] );
477
478		// TODO mapquest allows using one image url with a multiplier $NUMBER eg:
479		// $NUMBER = 2
480		// $imgUrl .= DOKU_URL."/".DOKU_PLUGIN."/".getPluginName()."/icons/".$img.",$NUMBER,C,".$lat1.",".$lon1.",0,0,0,0,C,".$lat2.",".$lon2.",0,0,0,0";
481		if (! empty ( $overlay )) {
482			$imgUrl .= "&xis=";
483			foreach ( $overlay as $data ) {
484				list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data;
485				// $imgUrl .= $sUrl."lib/plugins/openlayersmap/icons/".$img.",1,C,".$lat.",".$lon.",0,0,0,0,";
486				$imgUrl .= $sUrl . "lib/plugins/openlayersmap/icons/" . $img . ",1,C," . $lat . "," . $lon . ",";
487			}
488			$imgUrl = substr ( $imgUrl, 0, - 1 );
489		}
490		$imgUrl .= "&imageType=png&type=" . $maptype;
491		$imgUrl .= "&key=".$this->getConf ( 'mapquestAPIKey' );
492		// dbglog($imgUrl,'syntax_plugin_openlayersmap_olmap::_getMapQuest: MapQuest image url is:');
493		return $imgUrl;
494	}
495	*/
496
497	/**
498	 * Create a Google maps static image url w/ the poi.
499	 *
500	 * @param array $gmap
501	 * @param array $overlay
502	 */
503	private function _getGoogle($gmap, $overlay) {
504		$sUrl = $this->getConf ( 'iconUrlOverload' );
505		if (! $sUrl) {
506			$sUrl = DOKU_URL;
507		}
508		switch ($gmap ['baselyr']) {
509			case 'google hybrid' :
510				$maptype = 'hybrid';
511				break;
512			case 'google sat' :
513				$maptype = 'satellite';
514				break;
515			case 'terrain' :
516			case 'google relief' :
517				$maptype = 'terrain';
518				break;
519			case 'google road' :
520			default :
521				$maptype = 'roadmap';
522				break;
523		}
524		// TODO maybe use viewport / visible instead of center/zoom,
525		// see: https://developers.google.com/maps/documentation/staticmaps/index#Viewports
526		// http://maps.google.com/maps/api/staticmap?center=51.565690,5.456756&zoom=16&size=600x400&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/marker.png|label:1|51.565690,5.456756&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/marker-blue.png|51.566197,5.458966|label:2&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/parking.png|51.567177,5.457909|label:3&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/parking.png|51.566283,5.457330|label:4&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/parking.png|51.565630,5.457695|label:5&sensor=false&format=png&maptype=roadmap
527		$imgUrl = "http://maps.googleapis.com/maps/api/staticmap?";
528		$imgUrl .= "&size=" . str_replace ( "px", "", $gmap ['width'] ) . "x" . str_replace ( "px", "", $gmap ['height'] );
529		//if (!$this->getConf( 'autoZoomMap')) { // no need for center & zoom params }
530		$imgUrl .= "&center=" . $gmap ['lat'] . "," . $gmap ['lon'];
531		// max is 21 (== building scale), but that's overkill..
532		if ($gmap ['zoom'] > 17) {
533			$imgUrl .= "&zoom=17";
534		} else {
535			$imgUrl .= "&zoom=" . $gmap ['zoom'];
536		}
537		if (! empty ( $overlay )) {
538			$rowId = 0;
539			foreach ( $overlay as $data ) {
540				list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data;
541				$imgUrl .= "&markers=icon%3a" . $sUrl . "lib/plugins/openlayersmap/icons/" . $img . "%7c" . $lat . "," . $lon . "%7clabel%3a" . ++ $rowId;
542			}
543		}
544		$imgUrl .= "&format=png&maptype=" . $maptype;
545		global $conf;
546		$imgUrl .= "&language=" . $conf ['lang'];
547		if ($this->getConf( 'googleAPIkey' )) {
548			$imgUrl .= "&key=" . $this->getConf( 'googleAPIkey' );
549		}
550		// dbglog($imgUrl,'syntax_plugin_openlayersmap_olmap::_getGoogle: Google image url is:');
551		return $imgUrl;
552	}
553
554	/**
555	 * Create a Bing maps static image url w/ the poi.
556	 *
557	 * @param array $gmap
558	 * @param array $overlay
559	 */
560	private function _getBing($gmap, $overlay) {
561		switch ($gmap ['baselyr']) {
562			case 've hybrid' :
563			case 'bing hybrid' :
564				$maptype = 'AerialWithLabels';
565				break;
566			case 've sat' :
567			case 'bing sat' :
568				$maptype = 'Aerial';
569				break;
570			case 've normal' :
571			case 've road' :
572			case 've' :
573			case 'bing road' :
574			default :
575				$maptype = 'Road';
576				break;
577		}
578		$imgUrl = "http://dev.virtualearth.net/REST/v1/Imagery/Map/" . $maptype;// . "/";
579		if ($this->getConf ( 'autoZoomMap' )) {
580			$bbox = $this->_calcBBOX ( $overlay, $gmap ['lat'], $gmap ['lon'] );
581			//$imgUrl .= "?ma=" . $bbox ['minlat'] . "," . $bbox ['minlon'] . "," . $bbox ['maxlat'] . "," . $bbox ['maxlon'];
582			$imgUrl .= "?ma=" . $bbox ['minlat'] . "%2C" . $bbox ['minlon'] . "%2C" . $bbox ['maxlat'] . "%2C" . $bbox ['maxlon'];
583			$imgUrl .= "&dcl=1";
584		}
585		if (strpos ( $imgUrl, "?" ) === false)
586			$imgUrl .= "?";
587
588		//$imgUrl .= "&ms=" . str_replace ( "px", "", $gmap ['width'] ) . "," . str_replace ( "px", "", $gmap ['height'] );
589		$imgUrl .= "&ms=" . str_replace ( "px", "", $gmap ['width'] ) . "%2C" . str_replace ( "px", "", $gmap ['height'] );
590		$imgUrl .= "&key=" . $this->getConf ( 'bingAPIKey' );
591		if (! empty ( $overlay )) {
592			$rowId = 0;
593			foreach ( $overlay as $data ) {
594				list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data;
595				// TODO icon style lookup, see: http://msdn.microsoft.com/en-us/library/ff701719.aspx for iconStyle
596				$iconStyle = 32;
597				$rowId ++;
598				// NOTE: the max number of pushpins is 18! or we have to use POST (http://msdn.microsoft.com/en-us/library/ff701724.aspx)
599				if ($rowId == 18) {
600					break;
601				}
602				//$imgUrl .= "&pp=$lat,$lon;$iconStyle;$rowId";
603				$imgUrl .= "&pp=$lat%2C$lon%3B$iconStyle%3B$rowId";
604
605			}
606		}
607		global $conf;
608		$imgUrl .= "&fmt=png";
609		$imgUrl .= "&c=" . $conf ['lang'];
610		// dbglog($imgUrl,'syntax_plugin_openlayersmap_olmap::_getBing: bing image url is:');
611		return $imgUrl;
612	}
613
614	/**
615	 * Create a static OSM map image url w/ the poi from http://staticmap.openstreetmap.de (staticMapLite)
616	 * use http://staticmap.openstreetmap.de "staticMapLite" or a local version
617	 *
618	 * @param array $gmap
619	 * @param array $overlay
620	 *
621	 * @todo implementation for http://ojw.dev.openstreetmap.org/StaticMapDev/
622	 */
623	private function _getStaticOSM($gmap, $overlay) {
624		global $conf;
625
626		if ($this->getConf ( 'optionStaticMapGenerator' ) == 'local') {
627			// using local basemap composer
628			if (! $myMap = &plugin_load ( 'helper', 'openlayersmap_staticmap' )) {
629				dbglog ( $myMap, 'syntax_plugin_openlayersmap_olmap::_getStaticOSM: openlayersmap_staticmap plugin is not available.' );
630			}
631			if (! $geophp = &plugin_load ( 'helper', 'geophp' )) {
632				dbglog ( $geophp, 'syntax_plugin_openlayersmap_olmap::_getStaticOSM: geophp plugin is not available.' );
633			}
634			$size = str_replace ( "px", "", $gmap ['width'] ) . "x" . str_replace ( "px", "", $gmap ['height'] );
635
636			$markers = array();
637			if (! empty ( $overlay )) {
638				foreach ( $overlay as $data ) {
639					list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data;
640					$iconStyle = substr ( $img, 0, strlen ( $img ) - 4 );
641					$markers [] = array (
642							'lat' => $lat,
643							'lon' => $lon,
644							'type' => $iconStyle
645					);
646				}
647			}
648
649			$apikey = '';
650			switch ($gmap ['baselyr']) {
651				case 'mapnik' :
652				case 'openstreetmap' :
653					$maptype = 'openstreetmap';
654					break;
655				case 'transport' :
656					$maptype = 'transport';
657					$apikey = $this->getConf ( 'tfApiKey' );
658					break;
659				case 'landscape' :
660					$maptype = 'landscape';
661					$apikey = $this->getConf ( 'tfApiKey' );
662					break;
663				case 'outdoors' :
664					$maptype = 'outdoors';
665					$apikey = $this->getConf ( 'tfApiKey' );
666					break;
667				case 'cycle map' :
668					$maptype = 'cycle';
669					$apikey = $this->getConf ( 'tfApiKey' );
670					break;
671				case 'hike and bike map' :
672					$maptype = 'hikeandbike';
673					break;
674				case 'mapquest hybrid' :
675				case 'mapquest road' :
676				case 'mapquest sat' :
677					$maptype = 'mapquest';
678					break;
679				default :
680					$maptype = '';
681					break;
682			}
683
684			$result = $myMap->getMap ( $gmap ['lat'], $gmap ['lon'], $gmap ['zoom'], $size, $maptype, $markers, $gmap ['gpxfile'], $gmap ['kmlfile'], $gmap ['geojsonfile'] );
685		} else {
686			// using external basemap composer
687
688			// http://staticmap.openstreetmap.de/staticmap.php?center=47.000622235634,10.117187497601&zoom=5&size=500x350
689			// &markers=48.999812532766,8.3593749976708,lightblue1|43.154850037315,17.499999997306,lightblue1|49.487527053077,10.820312497573,ltblu-pushpin|47.951071133739,15.917968747369,ol-marker|47.921629720114,18.027343747285,ol-marker-gold|47.951071133739,19.257812497236,ol-marker-blue|47.180141361692,19.257812497236,ol-marker-green
690			$imgUrl = "http://staticmap.openstreetmap.de/staticmap.php";
691			$imgUrl .= "?center=" . $gmap ['lat'] . "," . $gmap ['lon'];
692			$imgUrl .= "&size=" . str_replace ( "px", "", $gmap ['width'] ) . "x" . str_replace ( "px", "", $gmap ['height'] );
693
694			if ($gmap ['zoom'] > 16) {
695				// actually this could even be 18, but that seems overkill
696				$imgUrl .= "&zoom=16";
697			} else {
698				$imgUrl .= "&zoom=" . $gmap ['zoom'];
699			}
700
701			if (! empty ( $overlay )) {
702				$rowId = 0;
703				$imgUrl .= "&markers=";
704				foreach ( $overlay as $data ) {
705					list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data;
706					$rowId ++;
707					$iconStyle = "lightblue$rowId";
708					$imgUrl .= "$lat,$lon,$iconStyle%7c";
709				}
710				$imgUrl = substr ( $imgUrl, 0, - 3 );
711			}
712
713			$result = $imgUrl;
714		}
715		// dbglog ( $result, 'syntax_plugin_openlayersmap_olmap::_getStaticOSM: osm image url is:' );
716		return $result;
717	}
718
719	/**
720	 * Calculate the minimum bbox for a start location + poi.
721	 *
722	 * @param array $overlay
723	 *        	multi-dimensional array of array($lat, $lon, $text, $angle, $opacity, $img)
724	 * @param float $lat
725	 *        	latitude for map center
726	 * @param float $lon
727	 *        	longitude for map center
728	 * @return multitype:float array describing the mbr and center point
729	 */
730	private function _calcBBOX($overlay, $lat, $lon) {
731		$lats = array($lat);
732		$lons = array($lon);
733		foreach ( $overlay as $data ) {
734			list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data;
735			$lats [] = $lat;
736			$lons [] = $lon;
737		}
738		sort ( $lats );
739		sort ( $lons );
740		// TODO: make edge/wrap around cases work
741		$centerlat = $lats [0] + ($lats [count ( $lats ) - 1] - $lats [0]);
742		$centerlon = $lons [0] + ($lons [count ( $lats ) - 1] - $lons [0]);
743		return array (
744				'minlat' => $lats [0],
745				'minlon' => $lons [0],
746				'maxlat' => $lats [count ( $lats ) - 1],
747				'maxlon' => $lons [count ( $lats ) - 1],
748				'centerlat' => $centerlat,
749				'centerlon' => $centerlon
750		);
751	}
752
753	/**
754	 * Figures out the base filename of a media path.
755	 *
756	 * @param String $mediaLink
757	 */
758	private function getFileName($mediaLink) {
759		$mediaLink = str_replace ( '[[', '', $mediaLink );
760		$mediaLink = str_replace ( ']]', '', $mediaLink );
761		$mediaLink = substr ( $mediaLink, 0, - 4 );
762		$parts = explode ( ':', $mediaLink );
763		$mediaLink = end ( $parts );
764		return str_replace ( '_', ' ', $mediaLink );
765	}
766
767	/**
768	 * Convert decimal degrees to degrees, minutes, seconds format
769	 *
770	 * @todo move this into a shared library
771	 * @param float $decimaldegrees
772	 * @return string dms
773	 */
774	private function _convertDDtoDMS($decimaldegrees) {
775		$dms = floor ( $decimaldegrees );
776		$secs = ($decimaldegrees - $dms) * 3600;
777		$min = floor ( $secs / 60 );
778		$sec = round ( $secs - ($min * 60), 3 );
779		$dms .= 'º' . $min . '\'' . $sec . '"';
780		return $dms;
781	}
782
783	/**
784	 * convert latitude in decimal degrees to DMS+hemisphere.
785	 *
786	 * @todo move this into a shared library
787	 * @param float $decimaldegrees
788	 * @return string
789	 */
790	private function convertLat($decimaldegrees) {
791		if (strpos ( $decimaldegrees, '-' ) !== false) {
792			$latPos = "S";
793		} else {
794			$latPos = "N";
795		}
796		$dms = $this->_convertDDtoDMS ( abs ( floatval ( $decimaldegrees ) ) );
797		return hsc ( $dms . $latPos );
798	}
799
800	/**
801	 * convert longitude in decimal degrees to DMS+hemisphere.
802	 *
803	 * @todo move this into a shared library
804	 * @param float $decimaldegrees
805	 * @return string
806	 */
807	private function convertLon($decimaldegrees) {
808		if (strpos ( $decimaldegrees, '-' ) !== false) {
809			$lonPos = "W";
810		} else {
811			$lonPos = "E";
812		}
813		$dms = $this->_convertDDtoDMS ( abs ( floatval ( $decimaldegrees ) ) );
814		return hsc ( $dms . $lonPos );
815	}
816}
817