xref: /plugin/openlayersmap/script.js (revision ab2411d2721b7802bcebd11eb687ac97914d8d22)
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">' + '<div id="' + mapid
124			+ '" tabindex="-1" style="width:' + width + ';height:' + height + ';" class="olMap">'
125			+ '<a class="olAccesskey" href="#' + mapid + '" onclick="document.getElementById(&quot;' + mapid
126			+ '&quot;).focus(); return false;" title="' + OpenLayers.i18n("activate_map") + '">'
127			+ OpenLayers.i18n("activate_map") + '</a>' + '</div>' + '<div id="' + mapid + '-olStatusBar" style="width:'
128			+ width + ';"class="olStatusBarContainer">' + '<div id="' + mapid
129			+ '-statusbar-scale" class="olStatusBar olStatusBarScale">scale</div>' + '<div id="' + mapid
130			+ '-statusbar-mouseposition" class="olStatusBar olStatusBarMouseposition"></div>' + '<div id="' + mapid
131			+ '-statusbar-projection" class="olStatusBar olStatusBarProjection">proj</div>' + '<div id="' + mapid
132			+ '-statusbar-text" class="olStatusBar olStatusBarText">txt</div>' + '</div></div>',
133	// fragment
134	frag = document.createDocumentFragment(),
135	// temp node
136	temp = document.createElement('div');
137	temp.innerHTML = mEl;
138	while (temp.firstChild) {
139		frag.appendChild(temp.firstChild);
140	}
141	return frag;
142}
143
144/**
145 * Create the map based on the params given.
146 *
147 * @param {Object}
148 *            mapOpts MapOptions hash {id:'olmap', width:500px, height:500px,
149 *            lat:6710200, lon:506500, zoom:13, statusbar:1, controls:1,
150 *            poihoverstyle:1, baselyr:'', kmlfile:'', gpxfile:'', geojsonfile,
151 *            summary:''}
152 * @param {Array}
153 *            OLmapPOI array with POI's [ {lat:6710300,lon:506000,txt:'instap
154 *            punt',angle:180,opacity:.9,img:'', rowId:n},... ]);
155 *
156 * @return {OpenLayers.Map} the created map
157 */
158function createMap(mapOpts, OLmapPOI) {
159	if (!olEnable) {
160		return;
161	}
162	if (!olTestCSSsupport()) {
163		olEnable = false;
164		return;
165	}
166	OpenLayers.ImgPath = DOKU_BASE + 'lib/plugins/openlayersmap/lib/img/';
167	OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
168
169	// find map element location
170	var cleartag = document.getElementById(mapOpts.id + '-clearer');
171	if (cleartag === null) {
172		return;
173	}
174	// create map element and add to document
175	var fragment = olCreateMaptag(mapOpts.id, mapOpts.width, mapOpts.height);
176	cleartag.parentNode.insertBefore(fragment, cleartag);
177
178	/** dynamic map extent. */
179	var extent = new OpenLayers.Bounds(),
180
181	/** map. */
182	m = new OpenLayers.Map({
183		div : mapOpts.id,
184		projection : 'EPSG:900913',
185		displayProjection : new OpenLayers.Projection("EPSG:4326"),
186		numZoomLevels : 22,
187		controls : [],
188		theme : null
189	});
190
191	if (osmEnable) {
192		/* add OSM map layers */
193		m.addLayer(new OpenLayers.Layer.OSM());
194
195		m.addLayer(new OpenLayersMap.Layer.OCM());
196		/* open cycle map */
197		m.addLayer(new OpenLayersMap.Layer.OCM("transport", [
198				"http://a.tile2.opencyclemap.org/transport/${z}/${x}/${y}.png",
199				"http://b.tile2.opencyclemap.org/transport/${z}/${x}/${y}.png",
200				"http://c.tile2.opencyclemap.org/transport/${z}/${x}/${y}.png" ], {
201			visibility : mapOpts.baselyr === "transport"
202		}));
203		m.addLayer(new OpenLayersMap.Layer.OCM("landscape", [
204				"http://a.tile3.opencyclemap.org/landscape/${z}/${x}/${y}.png",
205				"http://b.tile3.opencyclemap.org/landscape/${z}/${x}/${y}.png",
206				"http://c.tile3.opencyclemap.org/landscape/${z}/${x}/${y}.png" ], {
207			visibility : mapOpts.baselyr === "landscape"
208		}));
209
210		m.addLayer(new OpenLayers.Layer.OSM(
211				"hike and bike map", "http://toolserver.org/tiles/hikebike/${z}/${x}/${y}.png", {
212					visibility : mapOpts.baselyr === "hike and bike map",
213					tileOptions : {
214						crossOriginKeyword : null
215					}
216				}));
217	}
218	/*
219	 * add MapQuest map layers, see:
220	 * http://developer.mapquest.com/web/products/open/map
221	 */
222	if (mqEnable) {
223		m.addLayer(new OpenLayersMap.Layer.MapQuest());
224		m.addLayer(new OpenLayersMap.Layer.MapQuest("mapquest sat", [
225				"//otile1-s.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
226				"//otile2-s.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
227				"//otile3-s.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
228				"//otile4-s.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg" ], {
229			// note that global coverage is provided at zoom levels 0-11. Zoom
230			// Levels 12+ are provided only in the United States (lower 48).
231			numZoomLevels : 12,
232			visibility : mapOpts.baselyr === "mapquest sat"
233		}));
234
235	}
236
237	if (gEnable) {
238		/* load google maps */
239		try {
240			m.addLayer(new OpenLayers.Layer.Google("google relief", {
241				type : google.maps.MapTypeId.TERRAIN,
242				numZoomLevels : 16,
243				animationEnabled : true,
244				visibility : mapOpts.baselyr === "google relief"
245			}));
246			m.addLayer(new OpenLayers.Layer.Google("google sat", {
247				type : google.maps.MapTypeId.SATELLITE,
248				animationEnabled : true,
249				visibility : mapOpts.baselyr === "google sat"
250			}));
251			m.addLayer(new OpenLayers.Layer.Google("google hybrid", {
252				type : google.maps.MapTypeId.HYBRID,
253				animationEnabled : true,
254				visibility : mapOpts.baselyr === "google hybrid"
255			}));
256			m.addLayer(new OpenLayers.Layer.Google("google road", {
257				animationEnabled : true,
258				visibility : mapOpts.baselyr === "google road"
259			}));
260		} catch (ol_err1) {
261			Openlayers.Console.userError('Error loading Google maps' + ol_err1);
262		}
263	}
264
265	if (bEnable && bApiKey !== '') {
266		try {
267			/* add Bing tiles */
268			m.addLayer(new OpenLayers.Layer.Bing({
269				key : bApiKey,
270				type : "Road",
271				name : "bing road",
272				visibility : mapOpts.baselyr === "bing road",
273				wrapDateLine : true,
274				attributionTemplate : '<a target="_blank" href="http://www.bing.com/maps/">'
275						+ 'Bing™</a><img src="http://www.bing.com/favicon.ico" alt="Bing logo"/> ${copyrights}'
276						+ '<a target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'
277			}));
278			m.addLayer(new OpenLayers.Layer.Bing({
279				key : bApiKey,
280				type : "Aerial",
281				name : "bing sat",
282				visibility : mapOpts.baselyr === "bing sat",
283				wrapDateLine : true,
284				attributionTemplate : '<a target="_blank" href="http://www.bing.com/maps/">'
285						+ 'Bing™</a><img src="http://www.bing.com/favicon.ico" alt="Bing logo"/> ${copyrights}'
286						+ '<a target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'
287			}));
288			m.addLayer(new OpenLayers.Layer.Bing({
289				key : bApiKey,
290				type : "AerialWithLabels",
291				name : "bing hybrid",
292				visibility : mapOpts.baselyr === "bing hybrid",
293				wrapDateLine : true,
294				attributionTemplate : '<a target="_blank" href="http://www.bing.com/maps/">'
295						+ 'Bing™</a><img src="http://www.bing.com/favicon.ico" alt="Bing logo"/> ${copyrights}'
296						+ '<a target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a>'
297			}));
298		} catch (ol_errBing) {
299			Openlayers.Console.userError('Error loading Bing maps: ' + ol_errBing);
300		}
301	}
302
303	m.setCenter(new OpenLayers.LonLat(mapOpts.lon, mapOpts.lat).transform(m.displayProjection, m.projection),
304			mapOpts.zoom);
305	extent.extend(m.getExtent());
306
307	// change/set alternative baselyr
308	try {
309		m.setBaseLayer(((m.getLayersByName(mapOpts.baselyr))[0]));
310	} catch (ol_err4) {
311		m.setBaseLayer(m.layers[0]);
312	}
313
314	m.addControls([ new OpenLayers.Control.ScaleLine({
315		geodesic : true
316	}), new OpenLayers.Control.KeyboardDefaults({
317		observeElement : mapOpts.id
318	}), new OpenLayers.Control.Navigation() ]);
319
320	if (mapOpts.statusbar === 1) {
321		// statusbar control: mouse pos.
322		m.addControl(new OpenLayers.Control.MousePosition({
323			'div' : OpenLayers.Util.getElement(mapOpts.id + '-statusbar-mouseposition')
324		}));
325		// statusbar control: scale
326		m.addControl(new OpenLayers.Control.Scale(mapOpts.id + '-statusbar-scale'));
327		// statusbar control: attribution
328		m.addControl(new OpenLayers.Control.Attribution({
329			'div' : OpenLayers.Util.getElement(mapOpts.id + '-statusbar-text')
330		}));
331		// statusbar control: projection
332		OpenLayers.Util.getElement(mapOpts.id + '-statusbar-projection').innerHTML = m.displayProjection;
333	} else {
334		OpenLayers.Util.getElement(mapOpts.id + '-olStatusBar').display = 'none';
335	}
336
337	if (OLmapPOI.length > 0) {
338		var markers = new OpenLayers.Layer.Vector("POI", {
339			styleMap : new OpenLayers.StyleMap({
340				"default" : {
341					cursor : "help",
342					externalGraphic : "${img}",
343					graphicHeight : 16,
344					graphicWidth : 16,
345					// graphicXOffset : 0,
346					// graphicYOffset : -8,
347					graphicOpacity : "${opacity}",
348					rotation : "${angle}",
349					backgroundGraphic : DOKU_BASE + "lib/plugins/openlayersmap/icons/marker_shadow.png",
350					// backgroundXOffset : 0,
351					// backgroundYOffset : -4,
352					backgroundRotation : "${angle}",
353					pointRadius : 10,
354					labelXOffset : 8,
355					labelYOffset : 8,
356					labelAlign : "lb",
357					label : "${label}",
358					// fontColor : "",
359					fontFamily : "monospace",
360					fontSize : "12px",
361					fontWeight : "bold"
362				},
363				"select" : {
364					cursor : "help",
365					externalGraphic : DOKU_BASE + "lib/plugins/openlayersmap/icons/marker-red.png",
366					graphicHeight : 16,
367					graphicWidth : 16,
368					// graphicXOffset : 0,
369					// graphicYOffset : -8,
370					graphicOpacity : 1.0,
371					rotation : "${angle}"
372				}
373			}),
374			isBaseLayer : false,
375			rendererOptions : {
376				yOrdering : true
377			}
378		});
379		m.addLayer(markers);
380		var features = [];
381		for (var j = 0; j < OLmapPOI.length; j++) {
382			var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(OLmapPOI[j].lon, OLmapPOI[j].lat)
383					.transform(m.displayProjection, m.projection), {
384				angle : OLmapPOI[j].angle,
385				opacity : OLmapPOI[j].opacity,
386				img : DOKU_BASE + "lib/plugins/openlayersmap/icons/" + OLmapPOI[j].img,
387				label : OLmapPOI[j].rowId
388			});
389			feat.data = {
390				name : OLmapPOI[j].txt,
391				rowId : OLmapPOI[j].rowId
392			};
393			features.push(feat);
394		}
395		markers.addFeatures(features);
396		extent.extend(markers.getDataExtent());
397		m.zoomToExtent(extent);
398	}
399
400	/* GPX layer */
401	if (mapOpts.gpxfile.length > 0) {
402		var layerGPX = new OpenLayers.Layer.Vector("GPS route", {
403			protocol : new OpenLayers.Protocol.HTTP({
404				url : DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.gpxfile,
405				format : new OpenLayers.Format.GPX({
406					extractWaypoints : true,
407					extractTracks : true,
408					extractStyles : true,
409					extractAttributes : true,
410					handleHeight : true,
411					maxDepth : 3
412				})
413			}),
414			style : {
415				strokeColor : "#0000FF",
416				strokeWidth : 3,
417				strokeOpacity : 0.7,
418				pointRadius : 4,
419				fillColor : "#0099FF",
420				fillOpacity : 0.7
421			// , label:"${name}"
422			},
423			projection : new OpenLayers.Projection("EPSG:4326"),
424			strategies : [ new OpenLayers.Strategy.Fixed() ]
425		});
426		m.addLayer(layerGPX);
427		layerGPX.events.register('loadend', m, function() {
428			extent.extend(layerGPX.getDataExtent());
429			m.zoomToExtent(extent);
430		});
431	}
432
433	/* GeoJSON layer */
434	if (mapOpts.geojsonfile.length > 0) {
435		var layerGJS = new OpenLayers.Layer.Vector("json data", {
436			protocol : new OpenLayers.Protocol.HTTP({
437				url : DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.geojsonfile,
438				format : new OpenLayers.Format.GeoJSON({
439					ignoreExtraDims : true
440				})
441			}),
442			style : {
443				strokeColor : "#FF00FF",
444				strokeWidth : 3,
445				strokeOpacity : 0.7,
446				pointRadius : 4,
447				fillColor : "#FF99FF",
448				fillOpacity : 0.7
449			// , label:"${name}"
450			},
451			projection : new OpenLayers.Projection("EPSG:4326"),
452			strategies : [ new OpenLayers.Strategy.Fixed() ]
453		});
454		m.addLayer(layerGJS);
455		layerGJS.events.register('loadend', m, function() {
456			extent.extend(layerGJS.getDataExtent());
457			m.zoomToExtent(extent);
458		});
459	}
460
461	/* KML layer */
462	if (mapOpts.kmlfile.length > 0) {
463		var layerKML = new OpenLayers.Layer.Vector("KML file", {
464			protocol : new OpenLayers.Protocol.HTTP({
465				url : DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.kmlfile,
466				format : new OpenLayers.Format.KML({
467					extractStyles : true,
468					extractAttributes : true,
469					maxDepth : 3
470				})
471			}),
472			style : {
473				label : "${name}"
474			},
475			projection : new OpenLayers.Projection("EPSG:4326"),
476			strategies : [ new OpenLayers.Strategy.Fixed() ]
477		});
478		m.addLayer(layerKML);
479		layerKML.events.register('loadend', m, function() {
480			extent.extend(layerKML.getDataExtent());
481			m.zoomToExtent(extent);
482		});
483	}
484
485	// selectcontrol for layers
486	if ((m.getLayersByClass('OpenLayers.Layer.GML').length > 0)
487			|| m.getLayersByClass('OpenLayers.Layer.Vector').length > 0) {
488		selectControl = new OpenLayers.Control.SelectFeature((m.getLayersByClass('OpenLayers.Layer.Vector')).concat(m
489				.getLayersByClass('OpenLayers.Layer.GML')), {
490			hover : mapOpts.poihoverstyle,
491			onSelect : onFeatureSelect,
492			onUnselect : onFeatureUnselect
493		});
494		m.addControl(selectControl);
495		selectControl.activate();
496
497		// keyboard select control
498		var iControl = new OpenLayersMap.Control.KeyboardClick({
499			observeElement : mapOpts.id,
500			selectControl : selectControl
501		});
502		m.addControl(iControl);
503	}
504
505	if (mapOpts.controls === 1) {
506		/* add base controls to map */
507		m.addControls([ new OpenLayersMap.Control.LayerSwitcher(), new OpenLayers.Control.Graticule({
508			visible : false
509		}), new OpenLayersMap.Control.OverviewMap({
510			mapOptions : {
511				theme : null
512			}
513		}), new OpenLayersMap.Control.Zoom() ]);
514
515		// add hillshade, since this is off by default only add when we have a
516		// layerswitcher
517		m.addLayer(new OpenLayers.Layer.OSM("Hillshade", "http://toolserver.org/~cmarqu/hill/${z}/${x}/${y}.png", {
518			isBaseLayer : false,
519			transparent : true,
520			visibility : false,
521			displayOutsideMaxExtent : true,
522			attribution : '',
523			tileOptions : {
524				crossOriginKeyword : null
525			}
526		}));
527	}
528
529	return m;
530}
531
532/** init. */
533function olInit() {
534	if (olEnable) {
535		var _i = 0;
536		// create the maps in the page
537		for (_i = 0; _i < olMapData.length; _i++) {
538			olMaps[olMapData[_i].mapOpts.id] = createMap(olMapData[_i].mapOpts, olMapData[_i].poi);
539		}
540		// hide the table(s) with POI by giving it a print-only style
541		var tbls = jQuery('.olPOItableSpan');
542		for (_i = 0; _i < tbls.length; _i++) {
543			tbls[_i].className += ' olPrintOnly';
544		}
545		// hide the static map image(s) by giving it a print only style
546		var statImgs = jQuery('.olStaticMap');
547		for (_i = 0; _i < statImgs.length; _i++) {
548			statImgs[_i].className += ' olPrintOnly';
549		}
550		// add help button with toggle.
551		jQuery('.olWebOnly').prepend(
552				'<div class="olMapHelpButtonDiv">'
553						+ '<button onclick="jQuery(\'.olMapHelp\').toggle(500);" class="olMapHelpButton olHasTooltip">'
554						+ '<span>' + OpenLayers.i18n("toggle_help") + '</span>?</button>' + '</div>');
555	}
556}
557
558/**
559 * ol api flag.
560 *
561 * @type {Boolean}
562 */
563var olEnable = false,
564/**
565 * An array with data for each map in the page.
566 *
567 * @type {Array}
568 */
569olMapData = [],
570/**
571 * Holds a reference to all of the maps on this page with the map's id as key.
572 * Can be used as an extension point.
573 *
574 * @type {Object}
575 */
576olMaps = new Object(),
577/**
578 * MapQuest tiles flag.
579 *
580 * @type {Boolean}
581 */
582mqEnable = false,
583/**
584 * google map api flag.
585 *
586 * @type {Boolean}
587 */
588gEnable = false,
589/**
590 * Bing tiles flag.
591 *
592 * @type {Boolean}
593 */
594bEnable = false,
595/**
596 * Bing API key.
597 *
598 * @type {String}
599 */
600bApiKey = '',
601/**
602 * OSM tiles flag.
603 *
604 * @type {Boolean}
605 */
606osmEnable = true,
607/**
608 * CSS support flag.
609 *
610 * @type {Boolean}
611 */
612olCSSEnable = true;
613
614/* register olInit to run with onload event. */
615jQuery(olInit);
616