xref: /plugin/openlayersmap/script.js (revision d974ea02de1d2e4fdc884948603c98663e51adc1)
1/*
2 * Copyright (c) 2008-2014 Mark C. Prins <mprins@users.sf.net>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17/**
18 * @fileoverview Javascript voor OpenLayers plugin.
19 *
20 * @requires {lib/OpenLayers.js} or a full openlayers build
21 * @author Mark C. Prins <mprins@users.sf.net>
22 *
23 */
24
25/**
26 * Openlayers selectcontrol.
27 *
28 * @type {OpenLayers.Control.SelectFeature}
29 * @private
30 */
31var selectControl;
32
33/**
34 * handle feature select event.
35 *
36 * @param {OpenLayers.Feature.Vector}
37 *            selFeature the selected feature
38 */
39function onFeatureSelect(selFeature) {
40	// 'this' is selectFeature control
41	var pPos = selFeature.geometry.getBounds().getCenterLonLat();
42	// != OpenLayers.Geometry.Point
43	if (selFeature.geometry.CLASS_NAME === "OpenLayers.Geometry.LineString") {
44		try {
45			// for lines make the popup show at the cursor position
46			// TODO this will fail for keyboard select
47			pPos = selFeature.layer.map.getLonLatFromViewPortPx(this.handlers.feature.evt.xy);
48		} catch (anErr) {
49			OpenLayers.Console.warn("unable to get event position; reverting to boundingbox center.");
50			pPos = selFeature.geometry.getBounds().getCenterLonLat();
51		}
52	}
53
54	var pContent = '<div class="spacer">&nbsp;</div>';
55	if (selFeature.data.rowId !== undefined) {
56		pContent += '<span class="rowId">' + selFeature.data.rowId + ': </span>';
57	}
58	if (selFeature.data.name !== undefined) {
59		pContent += '<span class="txt">' + selFeature.data.name + '</span>';
60	}
61	if (selFeature.data.ele !== undefined) {
62		pContent += '<div class="ele">elevation: ' + selFeature.data.ele + '</div>';
63	}
64	if (selFeature.data.type !== undefined) {
65		pContent += '<div>' + selFeature.data.type + '</div>';
66	}
67	if (selFeature.data.time !== undefined) {
68		pContent += '<div class="time">time: ' + selFeature.data.time + '</div>';
69	}
70	if (selFeature.data.description !== undefined) {
71		pContent += '<div class="desc">' + selFeature.data.description + '</div>';
72	}
73
74	if (pContent.length > 0) {
75		// only show when there is something to show...
76		var popup = new OpenLayersMap.Popup.FramedCloud("olPopup", pPos, null, pContent, null, true, function() {
77			selectControl.unselect(selFeature);
78			jQuery('#' + selectControl.layer.map.div.id).focus();
79		});
80		selFeature.popup = popup;
81		selFeature.layer.map.addPopup(popup);
82		jQuery('#olPopup').attr("tabindex", -1).focus();
83	}
84}
85
86/**
87 * handle feature unselect event. remove & destroy the popup.
88 *
89 * @param {OpenLayers.Feature.Vector}
90 *            selFeature the un-selected feature
91 */
92function onFeatureUnselect(selFeature) {
93	if (selFeature.popup !== null) {
94		selFeature.layer.map.removePopup(selFeature.popup);
95		selFeature.popup.destroy();
96		selFeature.popup = null;
97	}
98}
99/**
100 * Test for css support in the browser by sniffing for a css class we added
101 * using javascript added by the action plugin; this is an edge case because
102 * browsers that support javascript generally support css as well.
103 *
104 * @returns {Boolean} true when the browser supports css (and implicitly
105 *          javascript)
106 */
107function olTestCSSsupport() {
108	return (jQuery('.olCSSsupported').length > 0);
109}
110
111/**
112 * Creates a DocumentFragment to insert into the dom.
113 *
114 * @param mapid
115 *            id for the map div
116 * @param width
117 *            width for the map div
118 * @param height
119 *            height for the map div
120 * @returns a {DocumentFragment} element that can be injected into the dom
121 */
122function olCreateMaptag(mapid, width, height) {
123	var mEl = '<div id="' + mapid + '-olContainer" class="olContainer olWebOnly">'
124	// map
125	+ '<div id="' + mapid + '" tabindex="0" style="width:' + width + ';height:' + height + ';" class="olMap"></div>'
126	// statusbar
127	+ '<div id="' + mapid + '-olStatusBar" style="width:' + width + ';"class="olStatusBarContainer">'
128	+ '  <div id="' + mapid + '-statusbar-scale" class="olStatusBar olStatusBarScale">scale</div>'
129	+ '  <div id="' + mapid + '-statusbar-mouseposition" class="olStatusBar olStatusBarMouseposition"></div>'
130	+ '  <div id="' + mapid + '-statusbar-projection" class="olStatusBar olStatusBarProjection">proj</div>'
131	+ '  <div id="' + mapid + '-statusbar-text" class="olStatusBar olStatusBarText">txt</div>'
132	+ '</div>'
133	//
134	+ '</div>',
135	// fragment
136	frag = document.createDocumentFragment(),
137	// temp node
138	temp = document.createElement('div');
139	temp.innerHTML = mEl;
140	while (temp.firstChild) {
141		frag.appendChild(temp.firstChild);
142	}
143	return frag;
144}
145
146/**
147 * Create the map based on the params given.
148 *
149 * @param {Object}
150 *            mapOpts MapOptions hash {id:'olmap', width:500px, height:500px,
151 *            lat:6710200, lon:506500, zoom:13, statusbar:1, controls:1,
152 *            poihoverstyle:1, baselyr:'', kmlfile:'', gpxfile:'', geojsonfile,
153 *            summary:''}
154 * @param {Array}
155 *            OLmapPOI array with POI's [ {lat:6710300,lon:506000,txt:'instap
156 *            punt',angle:180,opacity:.9,img:'', rowId:n},... ]);
157 *
158 * @return {OpenLayers.Map} the created map
159 */
160function createMap(mapOpts, OLmapPOI) {
161	if (!olEnable) {
162		return;
163	}
164	if (!olTestCSSsupport()) {
165		olEnable = false;
166		return;
167	}
168	OpenLayers.ImgPath = DOKU_BASE + 'lib/plugins/openlayersmap/lib/img/';
169	OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
170
171	// find map element location
172	var cleartag = document.getElementById(mapOpts.id + '-clearer');
173	if (cleartag === null) {
174		return;
175	}
176	// create map element and add to document
177	var fragment = olCreateMaptag(mapOpts.id, mapOpts.width, mapOpts.height);
178	cleartag.parentNode.insertBefore(fragment, cleartag);
179
180	/** dynamic map extent. */
181	var extent = new OpenLayers.Bounds(),
182
183	/** map. */
184	m = new OpenLayers.Map({
185		div : mapOpts.id,
186		projection : 'EPSG:900913',
187		displayProjection : new OpenLayers.Projection("EPSG:4326"),
188		numZoomLevels : 22,
189		controls : [],
190		theme : null
191	});
192
193	if (osmEnable) {
194		/* add OSM map layers */
195		m.addLayer(new OpenLayers.Layer.OSM());
196
197		m.addLayer(new OpenLayersMap.Layer.OCM());
198		/* open cycle map */
199		m.addLayer(new OpenLayersMap.Layer.OCM("transport", [
200				"http://a.tile2.opencyclemap.org/transport/${z}/${x}/${y}.png",
201				"http://b.tile2.opencyclemap.org/transport/${z}/${x}/${y}.png",
202				"http://c.tile2.opencyclemap.org/transport/${z}/${x}/${y}.png" ], {
203			visibility : mapOpts.baselyr === "transport"
204		}));
205		m.addLayer(new OpenLayersMap.Layer.OCM("landscape", [
206				"http://a.tile3.opencyclemap.org/landscape/${z}/${x}/${y}.png",
207				"http://b.tile3.opencyclemap.org/landscape/${z}/${x}/${y}.png",
208				"http://c.tile3.opencyclemap.org/landscape/${z}/${x}/${y}.png" ], {
209			visibility : mapOpts.baselyr === "landscape"
210		}));
211
212		m.addLayer(new OpenLayers.Layer.OSM(
213				"hike and bike map", "http://toolserver.org/tiles/hikebike/${z}/${x}/${y}.png", {
214					visibility : mapOpts.baselyr === "hike and bike map",
215					tileOptions : {
216						crossOriginKeyword : null
217					}
218				}));
219	}
220	/*
221	 * add MapQuest map layers, see:
222	 * http://developer.mapquest.com/web/products/open/map
223	 */
224	if (mqEnable) {
225		m.addLayer(new OpenLayersMap.Layer.MapQuest());
226		m.addLayer(new OpenLayersMap.Layer.MapQuest("mapquest sat", [
227				"//otile1-s.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
228				"//otile2-s.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
229				"//otile3-s.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
230				"//otile4-s.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg" ], {
231			// note that global coverage is provided at zoom levels 0-11. Zoom
232			// Levels 12+ are provided only in the United States (lower 48).
233			numZoomLevels : 12,
234			visibility : mapOpts.baselyr === "mapquest sat"
235		}));
236
237	}
238
239	if (gEnable) {
240		/* load google maps */
241		try {
242			m.addLayer(new OpenLayers.Layer.Google("google relief", {
243				type : google.maps.MapTypeId.TERRAIN,
244				numZoomLevels : 16,
245				animationEnabled : true,
246				visibility : mapOpts.baselyr === "google relief"
247			}));
248			m.addLayer(new OpenLayers.Layer.Google("google sat", {
249				type : google.maps.MapTypeId.SATELLITE,
250				animationEnabled : true,
251				visibility : mapOpts.baselyr === "google sat"
252			}));
253			m.addLayer(new OpenLayers.Layer.Google("google hybrid", {
254				type : google.maps.MapTypeId.HYBRID,
255				animationEnabled : true,
256				visibility : mapOpts.baselyr === "google hybrid"
257			}));
258			m.addLayer(new OpenLayers.Layer.Google("google road", {
259				animationEnabled : true,
260				visibility : mapOpts.baselyr === "google road"
261			}));
262		} catch (ol_err1) {
263			Openlayers.Console.userError('Error loading Google maps' + ol_err1);
264		}
265	}
266
267	if (bEnable && bApiKey !== '') {
268		try {
269			/* add Bing tiles */
270			m.addLayer(new OpenLayers.Layer.Bing({
271				key : bApiKey,
272				type : "Road",
273				name : "bing road",
274				visibility : mapOpts.baselyr === "bing road",
275				wrapDateLine : true,
276				attributionTemplate : '<a target="_blank" href="http://www.bing.com/maps/">'
277						+ 'Bing™</a><img src="http://www.bing.com/favicon.ico" alt="Bing logo"/> ${copyrights}'
278						+ '<a target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'
279			}));
280			m.addLayer(new OpenLayers.Layer.Bing({
281				key : bApiKey,
282				type : "Aerial",
283				name : "bing sat",
284				visibility : mapOpts.baselyr === "bing sat",
285				wrapDateLine : true,
286				attributionTemplate : '<a target="_blank" href="http://www.bing.com/maps/">'
287						+ 'Bing™</a><img src="http://www.bing.com/favicon.ico" alt="Bing logo"/> ${copyrights}'
288						+ '<a target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'
289			}));
290			m.addLayer(new OpenLayers.Layer.Bing({
291				key : bApiKey,
292				type : "AerialWithLabels",
293				name : "bing hybrid",
294				visibility : mapOpts.baselyr === "bing hybrid",
295				wrapDateLine : true,
296				attributionTemplate : '<a target="_blank" href="http://www.bing.com/maps/">'
297						+ 'Bing™</a><img src="http://www.bing.com/favicon.ico" alt="Bing logo"/> ${copyrights}'
298						+ '<a target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'
299			}));
300		} catch (ol_errBing) {
301			Openlayers.Console.userError('Error loading Bing maps: ' + ol_errBing);
302		}
303	}
304
305	m.setCenter(new OpenLayers.LonLat(mapOpts.lon, mapOpts.lat).transform(m.displayProjection, m.projection),
306			mapOpts.zoom);
307	extent.extend(m.getExtent());
308
309	// change/set alternative baselyr
310	try {
311		m.setBaseLayer(((m.getLayersByName(mapOpts.baselyr))[0]));
312	} catch (ol_err4) {
313		m.setBaseLayer(m.layers[0]);
314	}
315
316	m.addControls([ new OpenLayers.Control.ScaleLine({
317		geodesic : true
318	}), new OpenLayers.Control.KeyboardDefaults({
319		observeElement : mapOpts.id
320	}), new OpenLayers.Control.Navigation() ]);
321
322	if (mapOpts.statusbar === 1) {
323		// statusbar control: mouse pos.
324		m.addControl(new OpenLayers.Control.MousePosition({
325			'div' : OpenLayers.Util.getElement(mapOpts.id + '-statusbar-mouseposition')
326		}));
327		// statusbar control: scale
328		m.addControl(new OpenLayers.Control.Scale(mapOpts.id + '-statusbar-scale'));
329		// statusbar control: attribution
330		m.addControl(new OpenLayers.Control.Attribution({
331			'div' : OpenLayers.Util.getElement(mapOpts.id + '-statusbar-text')
332		}));
333		// statusbar control: projection
334		OpenLayers.Util.getElement(mapOpts.id + '-statusbar-projection').innerHTML = m.displayProjection;
335	} else {
336		OpenLayers.Util.getElement(mapOpts.id + '-olStatusBar').display = 'none';
337	}
338
339	if (OLmapPOI.length > 0) {
340		var markers = new OpenLayers.Layer.Vector("POI", {
341			styleMap : new OpenLayers.StyleMap({
342				"default" : {
343					cursor : "help",
344					externalGraphic : "${img}",
345					graphicHeight : 16,
346					graphicWidth : 16,
347					// graphicXOffset : 0,
348					// graphicYOffset : -8,
349					graphicOpacity : "${opacity}",
350					rotation : "${angle}",
351					backgroundGraphic : DOKU_BASE + "lib/plugins/openlayersmap/icons/marker_shadow.png",
352					// backgroundXOffset : 0,
353					// backgroundYOffset : -4,
354					backgroundRotation : "${angle}",
355					pointRadius : 10,
356					labelXOffset : 8,
357					labelYOffset : 8,
358					labelAlign : "lb",
359					label : "${label}",
360					// fontColor : "",
361					fontFamily : "monospace",
362					fontSize : "12px",
363					fontWeight : "bold"
364				},
365				"select" : {
366					cursor : "help",
367					externalGraphic : DOKU_BASE + "lib/plugins/openlayersmap/icons/marker-red.png",
368					graphicHeight : 16,
369					graphicWidth : 16,
370					// graphicXOffset : 0,
371					// graphicYOffset : -8,
372					graphicOpacity : 1.0,
373					rotation : "${angle}"
374				}
375			}),
376			isBaseLayer : false,
377			rendererOptions : {
378				yOrdering : true
379			}
380		});
381		m.addLayer(markers);
382		var features = [];
383		for (var j = 0; j < OLmapPOI.length; j++) {
384			var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(OLmapPOI[j].lon, OLmapPOI[j].lat)
385					.transform(m.displayProjection, m.projection), {
386				angle : OLmapPOI[j].angle,
387				opacity : OLmapPOI[j].opacity,
388				img : DOKU_BASE + "lib/plugins/openlayersmap/icons/" + OLmapPOI[j].img,
389				label : OLmapPOI[j].rowId
390			});
391			feat.data = {
392				name : OLmapPOI[j].txt,
393				rowId : OLmapPOI[j].rowId
394			};
395			features.push(feat);
396		}
397		markers.addFeatures(features);
398		extent.extend(markers.getDataExtent());
399		m.zoomToExtent(extent);
400	}
401
402	/* GPX layer */
403	if (mapOpts.gpxfile.length > 0) {
404		var layerGPX = new OpenLayers.Layer.Vector("GPS route", {
405			protocol : new OpenLayers.Protocol.HTTP({
406				url : DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.gpxfile,
407				format : new OpenLayers.Format.GPX({
408					extractWaypoints : true,
409					extractTracks : true,
410					extractStyles : true,
411					extractAttributes : true,
412					handleHeight : true,
413					maxDepth : 3
414				})
415			}),
416			style : {
417				strokeColor : "#0000FF",
418				strokeWidth : 3,
419				strokeOpacity : 0.7,
420				pointRadius : 4,
421				fillColor : "#0099FF",
422				fillOpacity : 0.7
423			// , label:"${name}"
424			},
425			projection : new OpenLayers.Projection("EPSG:4326"),
426			strategies : [ new OpenLayers.Strategy.Fixed() ]
427		});
428		m.addLayer(layerGPX);
429		layerGPX.events.register('loadend', m, function() {
430			extent.extend(layerGPX.getDataExtent());
431			m.zoomToExtent(extent);
432		});
433	}
434
435	/* GeoJSON layer */
436	if (mapOpts.geojsonfile.length > 0) {
437		var layerGJS = new OpenLayers.Layer.Vector("json data", {
438			protocol : new OpenLayers.Protocol.HTTP({
439				url : DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.geojsonfile,
440				format : new OpenLayers.Format.GeoJSON({
441					ignoreExtraDims : true
442				})
443			}),
444			style : {
445				strokeColor : "#FF00FF",
446				strokeWidth : 3,
447				strokeOpacity : 0.7,
448				pointRadius : 4,
449				fillColor : "#FF99FF",
450				fillOpacity : 0.7
451			// , label:"${name}"
452			},
453			projection : new OpenLayers.Projection("EPSG:4326"),
454			strategies : [ new OpenLayers.Strategy.Fixed() ]
455		});
456		m.addLayer(layerGJS);
457		layerGJS.events.register('loadend', m, function() {
458			extent.extend(layerGJS.getDataExtent());
459			m.zoomToExtent(extent);
460		});
461	}
462
463	/* KML layer */
464	if (mapOpts.kmlfile.length > 0) {
465		var layerKML = new OpenLayers.Layer.Vector("KML file", {
466			protocol : new OpenLayers.Protocol.HTTP({
467				url : DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.kmlfile,
468				format : new OpenLayers.Format.KML({
469					extractStyles : true,
470					extractAttributes : true,
471					maxDepth : 3
472				})
473			}),
474			style : {
475				label : "${name}"
476			},
477			projection : new OpenLayers.Projection("EPSG:4326"),
478			strategies : [ new OpenLayers.Strategy.Fixed() ]
479		});
480		m.addLayer(layerKML);
481		layerKML.events.register('loadend', m, function() {
482			extent.extend(layerKML.getDataExtent());
483			m.zoomToExtent(extent);
484		});
485	}
486
487	// selectcontrol for layers
488	if ((m.getLayersByClass('OpenLayers.Layer.GML').length > 0)
489			|| m.getLayersByClass('OpenLayers.Layer.Vector').length > 0) {
490		selectControl = new OpenLayers.Control.SelectFeature((m.getLayersByClass('OpenLayers.Layer.Vector')).concat(m
491				.getLayersByClass('OpenLayers.Layer.GML')), {
492			hover : mapOpts.poihoverstyle,
493			onSelect : onFeatureSelect,
494			onUnselect : onFeatureUnselect
495		});
496		m.addControl(selectControl);
497		selectControl.activate();
498
499		// keyboard select control
500		var iControl = new OpenLayersMap.Control.KeyboardClick({
501			observeElement : mapOpts.id,
502			selectControl : selectControl
503		});
504		m.addControl(iControl);
505	}
506
507	if (mapOpts.controls === 1) {
508		/* add base controls to map */
509		m.addControls([ new OpenLayersMap.Control.LayerSwitcher(), new OpenLayers.Control.Graticule({
510			visible : false
511		}), new OpenLayersMap.Control.OverviewMap({
512			mapOptions : {
513				theme : null
514			}
515		}), new OpenLayersMap.Control.Zoom(),
516		new OpenLayersMap.Control.Fullscreen() ]);
517
518		// add hillshade, since this is off by default only add when we have a
519		// layerswitcher
520		m.addLayer(new OpenLayers.Layer.OSM("Hillshade", "http://toolserver.org/~cmarqu/hill/${z}/${x}/${y}.png", {
521			isBaseLayer : false,
522			transparent : true,
523			visibility : false,
524			displayOutsideMaxExtent : true,
525			attribution : '',
526			tileOptions : {
527				crossOriginKeyword : null
528			}
529		}));
530	}
531
532	return m;
533}
534
535/** init. */
536function olInit() {
537	if (olEnable) {
538		var _i = 0;
539		// create the maps in the page
540		for (_i = 0; _i < olMapData.length; _i++) {
541			olMaps[olMapData[_i].mapOpts.id] = createMap(olMapData[_i].mapOpts, olMapData[_i].poi);
542		}
543
544		// hide the table(s) with POI by giving it a print-only style
545		jQuery('.olPOItableSpan').addClass('olPrintOnly');
546		// hide the static map image(s) by giving it a print only style
547		jQuery('.olStaticMap').addClass('olPrintOnly');
548		// add help button with toggle.
549		jQuery('.olWebOnly > .olMap').prepend(
550				'<div class="olMapHelpButtonDiv">'
551					+ '<button onclick="jQuery(\'.olMapHelp\').toggle(500);" class="olMapHelpButton olHasTooltip"><span>'
552					+ OpenLayers.i18n("toggle_help")
553					+ '</span>?</button></div>');
554		// toggle to switch dynamic vs. static map
555		jQuery('.olMapHelp').before(
556				'<div class="a11y"><button onclick="jQuery(\'.olPrintOnly\').toggle();jQuery(\'.olWebOnly\').toggle();">'
557				+ OpenLayers.i18n("toggle_dynamic_map") + '</button></div>');
558	}
559}
560
561/**
562 * ol api flag.
563 *
564 * @type {Boolean}
565 */
566var olEnable = false,
567/**
568 * An array with data for each map in the page.
569 *
570 * @type {Array}
571 */
572olMapData = [],
573/**
574 * Holds a reference to all of the maps on this page with the map's id as key.
575 * Can be used as an extension point.
576 *
577 * @type {Object}
578 */
579olMaps = new Object(),
580/**
581 * MapQuest tiles flag.
582 *
583 * @type {Boolean}
584 */
585mqEnable = false,
586/**
587 * google map api flag.
588 *
589 * @type {Boolean}
590 */
591gEnable = false,
592/**
593 * Bing tiles flag.
594 *
595 * @type {Boolean}
596 */
597bEnable = false,
598/**
599 * Bing API key.
600 *
601 * @type {String}
602 */
603bApiKey = '',
604/**
605 * OSM tiles flag.
606 *
607 * @type {Boolean}
608 */
609osmEnable = true,
610/**
611 * CSS support flag.
612 *
613 * @type {Boolean}
614 */
615olCSSEnable = true;
616
617/* register olInit to run with onload event. */
618jQuery(olInit);
619