@@ 26-553 (lines=528) @@ | ||
23 | * @returns {Boolean} true when the browser supports css (and implicitly |
|
24 | * javascript) |
|
25 | */ |
|
26 | function olTestCSSsupport() { |
|
27 | return (jQuery('.olCSSsupported').length > 0); |
|
28 | } |
|
29 | ||
30 | /** |
|
31 | * Creates a DocumentFragment to insert into the dom. |
|
32 | * |
|
33 | * @param mapid |
|
34 | * id for the map div |
|
35 | * @param width |
|
36 | * width for the map div |
|
37 | * @param height |
|
38 | * height for the map div |
|
39 | * @returns a {DocumentFragment} element that can be injected into the dom |
|
40 | */ |
|
41 | function olCreateMaptag(mapid, width, height) { |
|
42 | var mEl = '<div id="' + mapid + '-olContainer" class="olContainer olWebOnly">' |
|
43 | // map |
|
44 | + '<div id="' + mapid + '" tabindex="0" style="width:' + width + ';height:' + height + ';" class="olMap"></div>' |
|
45 | + '</div>', |
|
46 | // fragment |
|
47 | frag = document.createDocumentFragment(), |
|
48 | // temp node |
|
49 | temp = document.createElement('div'); |
|
50 | temp.innerHTML = mEl; |
|
51 | while (temp.firstChild) { |
|
52 | frag.appendChild(temp.firstChild); |
|
53 | } |
|
54 | return frag; |
|
55 | } |
|
56 | ||
57 | /** |
|
58 | * Create the map based on the params given. |
|
59 | * |
|
60 | * @param {Object} |
|
61 | * mapOpts MapOptions hash {id:'olmap', width:500px, height:500px, |
|
62 | * lat:6710200, lon:506500, zoom:13, statusbar:1, controls:1, |
|
63 | * poihoverstyle:1, baselyr:'', kmlfile:'', gpxfile:'', geojsonfile, |
|
64 | * summary:''} |
|
65 | * @param {Array} |
|
66 | * OLmapPOI array with POI's [ {lat:6710300,lon:506000,txt:'instap |
|
67 | * punt',angle:180,opacity:.9,img:'', rowId:n},... ]); |
|
68 | * |
|
69 | * @return {OpenLayers.Map} the created map |
|
70 | */ |
|
71 | function createMap(mapOpts, poi) { |
|
72 | ||
73 | // const mapOpts = olMapData[0].mapOpts; |
|
74 | // const poi = olMapData[0].poi; |
|
75 | ||
76 | if (!olEnable) { |
|
77 | return; |
|
78 | } |
|
79 | if (!olTestCSSsupport()) { |
|
80 | olEnable = false; |
|
81 | return; |
|
82 | } |
|
83 | ||
84 | // find map element location |
|
85 | var cleartag = document.getElementById(mapOpts.id + '-clearer'); |
|
86 | if (cleartag === null) { |
|
87 | return; |
|
88 | } |
|
89 | // create map element and add to document |
|
90 | var fragment = olCreateMaptag(mapOpts.id, mapOpts.width, mapOpts.height); |
|
91 | cleartag.parentNode.insertBefore(fragment, cleartag); |
|
92 | ||
93 | ||
94 | const overlayGroup = new ol.layer.Group({title: 'Overlays', fold: 'open', layers: []}); |
|
95 | const baseLyrGroup = new ol.layer.Group({'title': 'Base maps', layers: []}); |
|
96 | ||
97 | const map = new ol.Map({ |
|
98 | target: document.getElementById(mapOpts.id), |
|
99 | layers: [baseLyrGroup, overlayGroup], |
|
100 | view: new ol.View({ |
|
101 | center: ol.proj.transform([mapOpts.lon, mapOpts.lat], 'EPSG:4326', 'EPSG:3857'), |
|
102 | zoom: mapOpts.zoom, |
|
103 | projection: 'EPSG:3857' |
|
104 | }) |
|
105 | }); |
|
106 | ||
107 | let /** |
|
108 | * Thunderforest API key. |
|
109 | * |
|
110 | * @type {String} |
|
111 | */ |
|
112 | tfApiKey = ''; |
|
113 | let /** |
|
114 | * OSM tiles flag. |
|
115 | * |
|
116 | * @type {Boolean} |
|
117 | */ |
|
118 | osmEnable = true; |
|
119 | if (osmEnable) { |
|
120 | baseLyrGroup.getLayers().push( |
|
121 | new ol.layer.Tile({ |
|
122 | visible: true, |
|
123 | title: 'OSM', |
|
124 | type: 'base', |
|
125 | source: new ol.source.OSM() |
|
126 | })); |
|
127 | ||
128 | baseLyrGroup.getLayers().push( |
|
129 | new ol.layer.Tile({ |
|
130 | visible: mapOpts.baselyr === "cycle map", |
|
131 | title: 'cycle map', |
|
132 | type: 'base', |
|
133 | source: new ol.source.OSM({ |
|
134 | url: 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=' + tfApiKey, |
|
135 | attributions: 'Data ©ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, ' |
|
136 | + 'Tiles ©<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>' |
|
137 | + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>' |
|
138 | }) |
|
139 | })); |
|
140 | ||
141 | baseLyrGroup.getLayers().push( |
|
142 | new ol.layer.Tile({ |
|
143 | visible: mapOpts.baselyr === "transport", |
|
144 | title: 'transport', |
|
145 | type: 'base', |
|
146 | source: new ol.source.OSM({ |
|
147 | url: 'https://{a-c}.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=' + tfApiKey, |
|
148 | attributions: 'Data ©ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, ' |
|
149 | + 'Tiles ©<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>' |
|
150 | + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>' |
|
151 | }) |
|
152 | })); |
|
153 | ||
154 | baseLyrGroup.getLayers().push( |
|
155 | new ol.layer.Tile({ |
|
156 | visible: mapOpts.baselyr === "landscape", |
|
157 | title: 'landscape', |
|
158 | type: 'base', |
|
159 | source: new ol.source.OSM({ |
|
160 | url: 'https://{a-c}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png?apikey=' + tfApiKey, |
|
161 | attributions: 'Data ©ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, ' |
|
162 | + 'Tiles ©<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>' |
|
163 | + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>' |
|
164 | }) |
|
165 | })); |
|
166 | ||
167 | baseLyrGroup.getLayers().push( |
|
168 | new ol.layer.Tile({ |
|
169 | visible: mapOpts.baselyr === "outdoors", |
|
170 | title: 'outdoors', |
|
171 | type: 'base', |
|
172 | source: new ol.source.OSM({ |
|
173 | url: 'https://{a-c}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey=' + tfApiKey, |
|
174 | attributions: 'Data ©ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, ' |
|
175 | + 'Tiles ©<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>' |
|
176 | + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>' |
|
177 | }) |
|
178 | })); |
|
179 | } |
|
180 | ||
181 | let /** |
|
182 | * Bing tiles flag. |
|
183 | * |
|
184 | * @type {Boolean} |
|
185 | */ |
|
186 | bEnable = false; |
|
187 | let /** |
|
188 | * Bing API key. |
|
189 | * |
|
190 | * @type {String} |
|
191 | */ |
|
192 | bApiKey = ''; |
|
193 | if (bEnable && bApiKey !== '') { |
|
194 | baseLyrGroup.getLayers().push( |
|
195 | new ol.layer.Tile({ |
|
196 | visible: mapOpts.baselyr === "bing road", |
|
197 | title: 'bing road', |
|
198 | type: 'base', |
|
199 | source: new ol.source.BingMaps({ |
|
200 | key: bApiKey, |
|
201 | imagerySet: 'Road' |
|
202 | }) |
|
203 | })); |
|
204 | ||
205 | baseLyrGroup.getLayers().push( |
|
206 | new ol.layer.Tile({ |
|
207 | visible: mapOpts.baselyr === "bing sat", |
|
208 | title: 'bing sat', |
|
209 | type: 'base', |
|
210 | source: new ol.source.BingMaps({ |
|
211 | key: bApiKey, |
|
212 | imagerySet: 'Aerial' |
|
213 | }) |
|
214 | })); |
|
215 | ||
216 | baseLyrGroup.getLayers().push( |
|
217 | new ol.layer.Tile({ |
|
218 | visible: mapOpts.baselyr === "bing hybrid", |
|
219 | title: 'bing hybrid', |
|
220 | type: 'base', |
|
221 | source: new ol.source.BingMaps({ |
|
222 | key: bApiKey, |
|
223 | imagerySet: 'AerialWithLabels' |
|
224 | }) |
|
225 | })); |
|
226 | } |
|
227 | ||
228 | let /** |
|
229 | * Stamen tiles flag. |
|
230 | * |
|
231 | * @type {Boolean} |
|
232 | */ |
|
233 | stamenEnable = false; |
|
234 | if (stamenEnable) { |
|
235 | baseLyrGroup.getLayers().push( |
|
236 | new ol.layer.Tile({ |
|
237 | visible: false, |
|
238 | type: 'base', |
|
239 | title: 'toner', |
|
240 | source: new ol.source.Stamen({layer: 'toner'}) |
|
241 | }) |
|
242 | ); |
|
243 | baseLyrGroup.getLayers().push( |
|
244 | new ol.layer.Tile({ |
|
245 | visible: false, |
|
246 | type: 'base', |
|
247 | title: 'terrain', |
|
248 | source: new ol.source.Stamen({layer: 'terrain'}) |
|
249 | }) |
|
250 | ); |
|
251 | } |
|
252 | ||
253 | map.addControl(new ol.control.ScaleLine({bar: true, text: true})); |
|
254 | map.addControl(new ol.control.MousePosition({ |
|
255 | coordinateFormat: ol.coordinate.createStringXY(4), projection: 'EPSG:4326', |
|
256 | })); |
|
257 | map.addControl(new ol.control.FullScreen()); |
|
258 | map.addControl(new ol.control.OverviewMap()); |
|
259 | map.addControl(new ol.control.LayerSwitcher()); |
|
260 | ||
261 | const iconScale = 1.5; |
|
262 | const vectorSource = new ol.source.Vector(); |
|
263 | poi.forEach((p) => { |
|
264 | const f = new ol.Feature({ |
|
265 | geometry: new ol.geom.Point(ol.proj.fromLonLat([p.lon, p.lat])), |
|
266 | description: p.txt, |
|
267 | img: p.img, |
|
268 | rowId: p.rowId, |
|
269 | lat: p.lat, |
|
270 | lon: p.lon, |
|
271 | angle: p.angle, |
|
272 | alt: p.img.substring(0, p.img.lastIndexOf(".")) |
|
273 | }); |
|
274 | f.setId(p.rowId); |
|
275 | f.setStyle(new ol.style.Style({ |
|
276 | text: new ol.style.Text({ |
|
277 | text: "" + p.rowId, |
|
278 | textAlign: 'left', |
|
279 | textBaseline: 'bottom', |
|
280 | offsetX: 8, |
|
281 | offsetY: -8, |
|
282 | scale: iconScale, |
|
283 | fill: new ol.style.Fill({color: 'rgb(0,0,0)'}), |
|
284 | font: '12px monospace bold', |
|
285 | backgroundFill: new ol.style.Fill({color: 'rgba(255,255,255,.4)'}) |
|
286 | }), image: new ol.style.Icon({ |
|
287 | src: DOKU_BASE + "lib/plugins/openlayersmap/icons/" + p.img, |
|
288 | crossOrigin: '', |
|
289 | opacity: p.opacity, |
|
290 | scale: iconScale, |
|
291 | rotation: p.angle * Math.PI / 180, |
|
292 | }), |
|
293 | })); |
|
294 | vectorSource.addFeature(f); |
|
295 | }); |
|
296 | ||
297 | overlayGroup.getLayers().push(new ol.layer.Vector({title: 'POI', visible: true, source: vectorSource})); |
|
298 | ||
299 | if (mapOpts.kmlfile.length > 0) { |
|
300 | overlayGroup.getLayers().push(new ol.layer.Vector({ |
|
301 | title: 'KML file', visible: true, |
|
302 | source: new ol.source.VectorSource({ |
|
303 | url: mapOpts.kmlfile, |
|
304 | format: new ol.format.KML(), |
|
305 | }), |
|
306 | style: {label: "${name}"} |
|
307 | })); |
|
308 | } |
|
309 | ||
310 | if (mapOpts.geojsonfile.length > 0) { |
|
311 | overlayGroup.getLayers().push(new ol.layer.Vector({ |
|
312 | title: 'GeoJSON file', visible: true, |
|
313 | source: new ol.source.VectorSource({ |
|
314 | url: mapOpts.geojsonfile, |
|
315 | format: new ol.format.GeoJSON(), |
|
316 | }), |
|
317 | style: { |
|
318 | /* TODO */ |
|
319 | strokeColor: "#FF00FF", |
|
320 | strokeWidth: 3, |
|
321 | strokeOpacity: 0.7, |
|
322 | pointRadius: 4, |
|
323 | fillColor: "#FF99FF", |
|
324 | fillOpacity: 0.7 |
|
325 | } |
|
326 | })); |
|
327 | } |
|
328 | ||
329 | if (mapOpts.gpxfile.length > 0) { |
|
330 | overlayGroup.getLayers().push(new ol.layer.Vector({ |
|
331 | title: 'GPS track', visible: true, |
|
332 | source: new ol.source.VectorSource({ |
|
333 | url: mapOpts.gpxfile, |
|
334 | format: new ol.format.GPX(), |
|
335 | }), |
|
336 | style: { |
|
337 | /* TODO */ |
|
338 | strokeColor: "#0000FF", |
|
339 | strokeWidth: 3, |
|
340 | strokeOpacity: 0.7, |
|
341 | pointRadius: 4, |
|
342 | fillColor: "#0099FF", |
|
343 | fillOpacity: 0.7 |
|
344 | } |
|
345 | })); |
|
346 | } |
|
347 | ||
348 | const container = document.getElementById('popup'); |
|
349 | const content = document.getElementById('popup-content'); |
|
350 | const closer = document.getElementById('popup-closer'); |
|
351 | ||
352 | const overlay = new ol.Overlay({ |
|
353 | element: container, autoPan: true, autoPanAnimation: { |
|
354 | duration: 250, |
|
355 | }, //stopEvent: false, |
|
356 | }); |
|
357 | map.addOverlay(overlay); |
|
358 | ||
359 | /** |
|
360 | * Add a click handler to hide the popup. |
|
361 | * @return {boolean} Don't follow the href. |
|
362 | */ |
|
363 | closer.onclick = function () { |
|
364 | overlay.setPosition(undefined); |
|
365 | closer.blur(); |
|
366 | return false; |
|
367 | }; |
|
368 | ||
369 | // display popup on click |
|
370 | map.on('singleclick', function (evt) { |
|
371 | const selFeature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { |
|
372 | return feature; |
|
373 | }); |
|
374 | if (selFeature) { |
|
375 | overlay.setPosition(evt.coordinate); |
|
376 | ||
377 | let pContent = '<div class="spacer"> </div>'; |
|
378 | let locDesc = ''; |
|
379 | ||
380 | if (selFeature.get('rowId') !== undefined) { |
|
381 | pContent += '<span class="rowId">' + selFeature.get('rowId') + ': </span>'; |
|
382 | } |
|
383 | if (selFeature.get('name') !== undefined) { |
|
384 | pContent += '<span class="txt">' + selFeature.get('name') + '</span>'; |
|
385 | locDesc = selFeature.get('name'); |
|
386 | // TODO strip <p> tag from locDesc |
|
387 | // locDesc = selFeature.get('name').split(/\s+/).slice(0,2).join('+'); |
|
388 | } |
|
389 | if (selFeature.get('ele') !== undefined) { |
|
390 | pContent += '<div class="ele">elevation: ' + selFeature.get('ele') + '</div>'; |
|
391 | } |
|
392 | if (selFeature.get('type') !== undefined) { |
|
393 | pContent += '<div>' + selFeature.get('type') + '</div>'; |
|
394 | } |
|
395 | if (selFeature.get('time') !== undefined) { |
|
396 | pContent += '<div class="time">time: ' + selFeature.get('time') + '</div>'; |
|
397 | } |
|
398 | if (selFeature.get('description') !== undefined) { |
|
399 | pContent += '<div class="desc">' + selFeature.get('description') + '</div>'; |
|
400 | } |
|
401 | if (selFeature.get('img') !== undefined) { |
|
402 | pContent += '<div class="coord" title="lat;lon">' + |
|
403 | '<img src="' + DOKU_BASE + 'lib/plugins/openlayersmap/icons/' + selFeature.get('img') + |
|
404 | '" width="16" height="16" ' + 'style="transform:rotate(' + selFeature.get('angle') + 'deg)" /> ' + |
|
405 | '<a href="geo:' + selFeature.get('lat') + ',' + selFeature.get('lon') + '?q=' + selFeature.get('lat') + |
|
406 | ',' + selFeature.get('lon') + '(' + selFeature.get('alt') + ')" title="Open in navigation app">' + |
|
407 | ol.coordinate.format([selFeature.get('lon'), selFeature.get('lat')], '{x}º; {y}º', 4) + '</a></div>'; |
|
408 | } |
|
409 | content.innerHTML = pContent; |
|
410 | } else { |
|
411 | // do nothing?... |
|
412 | } |
|
413 | }); |
|
414 | ||
415 | // change mouse cursor when over marker |
|
416 | map.on('pointermove', function (e) { |
|
417 | const pixel = map.getEventPixel(e.originalEvent); |
|
418 | const hit = map.hasFeatureAtPixel(pixel); |
|
419 | map.getTarget().style.cursor = hit ? 'pointer' : ''; |
|
420 | }); |
|
421 | ||
422 | return map; |
|
423 | } |
|
424 | ||
425 | ||
426 | /** init. */ |
|
427 | function olInit() { |
|
428 | if (olEnable) { |
|
429 | // info window |
|
430 | var frag = document.createDocumentFragment(), |
|
431 | temp = document.createElement('div'); |
|
432 | temp.innerHTML = '<div id="popup" class="olPopup"><a href="#" id="popup-closer" class="olPopupCloseBox"></a><div id="popup-content"></div></div>'; |
|
433 | while (temp.firstChild) { |
|
434 | frag.appendChild(temp.firstChild); |
|
435 | } |
|
436 | console.debug('adding popup', frag); |
|
437 | const b = document.body.appendChild(frag); |
|
438 | ||
439 | let _i = 0; |
|
440 | // create the maps in the page |
|
441 | for (_i = 0; _i < olMapData.length; _i++) { |
|
442 | var _id = olMapData[_i].mapOpts.id; |
|
443 | console.debug('creating map for', olMapData[_i].mapOpts); |
|
444 | olMaps[_id] = createMap(olMapData[_i].mapOpts, olMapData[_i].poi); |
|
445 | ||
446 | // set max-width on help pop-over |
|
447 | jQuery('#' + _id).parent().parent().find('.olMapHelp').css('max-width', olMapData[_i].mapOpts.width); |
|
448 | ||
449 | // shrink the map width to fit inside page container |
|
450 | var _w = jQuery('#' + _id + '-olContainer').parent().innerWidth(); |
|
451 | if (parseInt(olMapData[_i].mapOpts.width) > _w) { |
|
452 | jQuery('#' + _id).width(_w); |
|
453 | jQuery('#' + _id + '-olStatusBar').width(_w); |
|
454 | jQuery('#' + _id).parent().parent().find('.olMapHelp').width(_w); |
|
455 | olMaps[_id].updateSize(); |
|
456 | } |
|
457 | } |
|
458 | // TODO |
|
459 | // let resizeTimer; |
|
460 | // jQuery(window).on('resize', function (e) { |
|
461 | // clearTimeout(resizeTimer); |
|
462 | // resizeTimer = setTimeout(function () { |
|
463 | // for (_i = 0; _i < olMapData.length; _i++) { |
|
464 | // var _id = olMapData[_i].mapOpts.id; |
|
465 | // var _w = jQuery('#' + _id + '-olContainer').parent().innerWidth(); |
|
466 | // if (parseInt(olMapData[_i].mapOpts.width) > _w) { |
|
467 | // jQuery('#' + _id).width(_w); |
|
468 | // jQuery('#' + _id + '-olStatusBar').width(_w); |
|
469 | // jQuery('#' + _id).parent().parent().find('.olMapHelp').width(_w); |
|
470 | // olMaps[_id].updateSize(); |
|
471 | // } |
|
472 | // } |
|
473 | // }, 250); |
|
474 | // }); |
|
475 | ||
476 | // hide the table(s) with POI by giving it a print-only style |
|
477 | jQuery('.olPOItableSpan').addClass('olPrintOnly'); |
|
478 | // hide the static map image(s) by giving it a print only style |
|
479 | jQuery('.olStaticMap').addClass('olPrintOnly'); |
|
480 | // add help button with toggle. |
|
481 | jQuery('.olWebOnly > .olMap') |
|
482 | .prepend( |
|
483 | '<div class="olMapHelpButtonDiv">' |
|
484 | + '<button onclick="jQuery(\'.olMapHelp\').toggle(500);" class="olMapHelpButton olHasTooltip"><span>' |
|
485 | + 'Show or hide help</span>?</button></div>'); |
|
486 | // toggle to switch dynamic vs. static map |
|
487 | jQuery('.olMapHelp').before( |
|
488 | '<div class="a11y"><button onclick="jQuery(\'.olPrintOnly\').toggle();jQuery(\'.olWebOnly\').toggle();">' |
|
489 | + 'Hide or show the dynamic map</button></div>'); |
|
490 | } |
|
491 | } |
|
492 | ||
493 | ||
494 | /** |
|
495 | * ol api flag. |
|
496 | * |
|
497 | * @type {Boolean} |
|
498 | */ |
|
499 | let olEnable = false; |
|
500 | /** |
|
501 | * An array with data for each map in the page. |
|
502 | * |
|
503 | * @type {Array} |
|
504 | */ |
|
505 | const olMapData = []; |
|
506 | /** |
|
507 | * Holds a reference to all of the maps on this page with the map's id as key. |
|
508 | * Can be used as an extension point. |
|
509 | * |
|
510 | * @type {Object} |
|
511 | */ |
|
512 | let olMaps = {}; |
|
513 | /** |
|
514 | * Stamen tiles flag. |
|
515 | * |
|
516 | * @type {Boolean} |
|
517 | */ |
|
518 | let stamenEnable = false; |
|
519 | ||
520 | /** |
|
521 | * Bing tiles flag. |
|
522 | * |
|
523 | * @type {Boolean} |
|
524 | */ |
|
525 | let bEnable = false; |
|
526 | /** |
|
527 | * Bing API key. |
|
528 | * |
|
529 | * @type {String} |
|
530 | */ |
|
531 | let bApiKey = ''; |
|
532 | ||
533 | /** |
|
534 | * Thunderforest API key. |
|
535 | * |
|
536 | * @type {String} |
|
537 | */ |
|
538 | let tfApiKey = '' |
|
539 | /** |
|
540 | * OSM tiles flag. |
|
541 | * |
|
542 | * @type {Boolean} |
|
543 | */ |
|
544 | let osmEnable = true; |
|
545 | /** |
|
546 | * CSS support flag. |
|
547 | * |
|
548 | * @type {Boolean} |
|
549 | */ |
|
550 | let olCSSEnable = true; |
|
551 | ||
552 | /* register olInit to run with onload event. */ |
|
553 | jQuery(olInit); |
|
554 |
@@ 27-539 (lines=513) @@ | ||
24 | * @returns {Boolean} true when the browser supports css (and implicitly |
|
25 | * javascript) |
|
26 | */ |
|
27 | function olTestCSSsupport() { |
|
28 | return (jQuery('.olCSSsupported').length > 0); |
|
29 | } |
|
30 | ||
31 | /** |
|
32 | * Creates a DocumentFragment to insert into the dom. |
|
33 | * |
|
34 | * @param mapid |
|
35 | * id for the map div |
|
36 | * @param width |
|
37 | * width for the map div |
|
38 | * @param height |
|
39 | * height for the map div |
|
40 | * @returns a {DocumentFragment} element that can be injected into the dom |
|
41 | */ |
|
42 | function olCreateMaptag(mapid, width, height) { |
|
43 | const mEl = '<div id="' + mapid + '-olContainer" class="olContainer olWebOnly">' |
|
44 | // map |
|
45 | + '<div id="' + mapid + '" tabindex="0" style="width:' + width + ';height:' + height + ';" class="olMap"></div>' |
|
46 | + '</div>', |
|
47 | // fragment |
|
48 | frag = document.createDocumentFragment(), |
|
49 | // temp node |
|
50 | temp = document.createElement('div'); |
|
51 | temp.innerHTML = mEl; |
|
52 | while (temp.firstChild) { |
|
53 | frag.appendChild(temp.firstChild); |
|
54 | } |
|
55 | return frag; |
|
56 | } |
|
57 | ||
58 | /** |
|
59 | * Create the map based on the params given. |
|
60 | * |
|
61 | * @param {Object} |
|
62 | * mapOpts MapOptions hash {id:'olmap', width:500px, height:500px, |
|
63 | * lat:6710200, lon:506500, zoom:13, statusbar:1, controls:1, |
|
64 | * poihoverstyle:1, baselyr:'', kmlfile:'', gpxfile:'', geojsonfile, |
|
65 | * summary:''} |
|
66 | * @param {Array} |
|
67 | * OLmapPOI array with POI's [ {lat:6710300,lon:506000,txt:'instap |
|
68 | * punt',angle:180,opacity:.9,img:'', rowId:n},... ]); |
|
69 | * |
|
70 | * @return {OpenLayers.Map} the created map |
|
71 | */ |
|
72 | function createMap(mapOpts, poi) { |
|
73 | ||
74 | // const mapOpts = olMapData[0].mapOpts; |
|
75 | // const poi = olMapData[0].poi; |
|
76 | ||
77 | if (!olEnable) { |
|
78 | return; |
|
79 | } |
|
80 | if (!olTestCSSsupport()) { |
|
81 | olEnable = false; |
|
82 | return; |
|
83 | } |
|
84 | ||
85 | // find map element location |
|
86 | var cleartag = document.getElementById(mapOpts.id + '-clearer'); |
|
87 | if (cleartag === null) { |
|
88 | return; |
|
89 | } |
|
90 | // create map element and add to document |
|
91 | var fragment = olCreateMaptag(mapOpts.id, mapOpts.width, mapOpts.height); |
|
92 | cleartag.parentNode.insertBefore(fragment, cleartag); |
|
93 | ||
94 | ||
95 | ||
96 | const overlayGroup = new ol.layer.Group({title: 'Overlays', fold: 'open', layers: []}); |
|
97 | const baseLyrGroup = new ol.layer.Group({'title': 'Base maps', layers: []}); |
|
98 | ||
99 | const map = new ol.Map({ |
|
100 | target: document.getElementById('map'), |
|
101 | layers: [baseLyrGroup, overlayGroup], |
|
102 | view: new ol.View({ |
|
103 | center: ol.proj.transform([mapOpts.lon, mapOpts.lat], 'EPSG:4326', 'EPSG:3857'), |
|
104 | zoom: mapOpts.zoom, |
|
105 | projection: 'EPSG:3857' |
|
106 | }) |
|
107 | }); |
|
108 | ||
109 | let /** |
|
110 | * Thunderforest API key. |
|
111 | * |
|
112 | * @type {String} |
|
113 | */ |
|
114 | tfApiKey = ''; |
|
115 | let /** |
|
116 | * OSM tiles flag. |
|
117 | * |
|
118 | * @type {Boolean} |
|
119 | */ |
|
120 | osmEnable = true; |
|
121 | if (osmEnable) { |
|
122 | baseLyrGroup.getLayers().push( |
|
123 | new ol.layer.Tile({ |
|
124 | visible: true, |
|
125 | title: 'OSM', |
|
126 | type: 'base', |
|
127 | source: new ol.source.OSM() |
|
128 | })); |
|
129 | ||
130 | baseLyrGroup.getLayers().push( |
|
131 | new ol.layer.Tile({ |
|
132 | visible: mapOpts.baselyr === "cycle map", |
|
133 | title: 'cycle map', |
|
134 | type: 'base', |
|
135 | source: new ol.source.OSM({ |
|
136 | url: 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=' + tfApiKey, |
|
137 | attributions: 'Data ©ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, ' |
|
138 | + 'Tiles ©<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>' |
|
139 | + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>' |
|
140 | }) |
|
141 | })); |
|
142 | ||
143 | baseLyrGroup.getLayers().push( |
|
144 | new ol.layer.Tile({ |
|
145 | visible: mapOpts.baselyr === "transport", |
|
146 | title: 'transport', |
|
147 | type: 'base', |
|
148 | source: new ol.source.OSM({ |
|
149 | url: 'https://{a-c}.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=' + tfApiKey, |
|
150 | attributions: 'Data ©ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, ' |
|
151 | + 'Tiles ©<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>' |
|
152 | + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>' |
|
153 | }) |
|
154 | })); |
|
155 | ||
156 | baseLyrGroup.getLayers().push( |
|
157 | new ol.layer.Tile({ |
|
158 | visible: mapOpts.baselyr === "landscape", |
|
159 | title: 'landscape', |
|
160 | type: 'base', |
|
161 | source: new ol.source.OSM({ |
|
162 | url: 'https://{a-c}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png?apikey=' + tfApiKey, |
|
163 | attributions: 'Data ©ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, ' |
|
164 | + 'Tiles ©<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>' |
|
165 | + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>' |
|
166 | }) |
|
167 | })); |
|
168 | ||
169 | baseLyrGroup.getLayers().push( |
|
170 | new ol.layer.Tile({ |
|
171 | visible: mapOpts.baselyr === "outdoors", |
|
172 | title: 'outdoors', |
|
173 | type: 'base', |
|
174 | source: new ol.source.OSM({ |
|
175 | url: 'https://{a-c}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey=' + tfApiKey, |
|
176 | attributions: 'Data ©ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, ' |
|
177 | + 'Tiles ©<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>' |
|
178 | + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>' |
|
179 | }) |
|
180 | })); |
|
181 | } |
|
182 | ||
183 | let /** |
|
184 | * Bing tiles flag. |
|
185 | * |
|
186 | * @type {Boolean} |
|
187 | */ |
|
188 | bEnable = false; |
|
189 | let /** |
|
190 | * Bing API key. |
|
191 | * |
|
192 | * @type {String} |
|
193 | */ |
|
194 | bApiKey = ''; |
|
195 | if (bEnable && bApiKey !== '') { |
|
196 | baseLyrGroup.getLayers().push( |
|
197 | new ol.layer.Tile({ |
|
198 | visible: mapOpts.baselyr === "bing road", |
|
199 | title: 'bing road', |
|
200 | type: 'base', |
|
201 | source: new ol.source.BingMaps({ |
|
202 | key: bApiKey, |
|
203 | imagerySet: 'Road' |
|
204 | }) |
|
205 | })); |
|
206 | ||
207 | baseLyrGroup.getLayers().push( |
|
208 | new ol.layer.Tile({ |
|
209 | visible: mapOpts.baselyr === "bing sat", |
|
210 | title: 'bing sat', |
|
211 | type: 'base', |
|
212 | source: new ol.source.BingMaps({ |
|
213 | key: bApiKey, |
|
214 | imagerySet: 'Aerial' |
|
215 | }) |
|
216 | })); |
|
217 | ||
218 | baseLyrGroup.getLayers().push( |
|
219 | new ol.layer.Tile({ |
|
220 | visible: mapOpts.baselyr === "bing hybrid", |
|
221 | title: 'bing hybrid', |
|
222 | type: 'base', |
|
223 | source: new ol.source.BingMaps({ |
|
224 | key: bApiKey, |
|
225 | imagerySet: 'AerialWithLabels' |
|
226 | }) |
|
227 | })); |
|
228 | } |
|
229 | ||
230 | let /** |
|
231 | * Stamen tiles flag. |
|
232 | * |
|
233 | * @type {Boolean} |
|
234 | */ |
|
235 | stamenEnable = false; |
|
236 | if (stamenEnable) { |
|
237 | baseLyrGroup.getLayers().push( |
|
238 | new ol.layer.Tile({ |
|
239 | visible: false, |
|
240 | type: 'base', |
|
241 | title: 'toner', |
|
242 | source: new ol.source.Stamen({layer: 'toner'}) |
|
243 | }) |
|
244 | ); |
|
245 | baseLyrGroup.getLayers().push( |
|
246 | new ol.layer.Tile({ |
|
247 | visible: false, |
|
248 | type: 'base', |
|
249 | title: 'terrain', |
|
250 | source: new ol.source.Stamen({layer: 'terrain'}) |
|
251 | }) |
|
252 | ); |
|
253 | } |
|
254 | ||
255 | map.addControl(new ol.control.ScaleLine({bar: true, text: true})); |
|
256 | map.addControl(new ol.control.MousePosition({ |
|
257 | coordinateFormat: ol.coordinate.createStringXY(4), projection: 'EPSG:4326', |
|
258 | })); |
|
259 | map.addControl(new ol.control.FullScreen()); |
|
260 | map.addControl(new ol.control.OverviewMap()); |
|
261 | map.addControl(new ol.control.LayerSwitcher()); |
|
262 | ||
263 | const iconScale = 1.5; |
|
264 | const vectorSource = new ol.source.Vector(); |
|
265 | poi.forEach((p) => { |
|
266 | const f = new ol.Feature({ |
|
267 | geometry: new ol.geom.Point(ol.proj.fromLonLat([p.lon, p.lat])), |
|
268 | description: p.txt, |
|
269 | img: p.img, |
|
270 | rowId: p.rowId, |
|
271 | lat: p.lat, |
|
272 | lon: p.lon, |
|
273 | angle: p.angle, |
|
274 | alt: p.img.substring(0, p.img.lastIndexOf(".")) |
|
275 | }); |
|
276 | f.setId(p.rowId); |
|
277 | f.setStyle(new ol.style.Style({ |
|
278 | text: new ol.style.Text({ |
|
279 | text: "" + p.rowId, |
|
280 | textAlign: 'left', |
|
281 | textBaseline: 'bottom', |
|
282 | offsetX: 8, |
|
283 | offsetY: -8, |
|
284 | scale: iconScale, |
|
285 | fill: new ol.style.Fill({color: 'rgb(0,0,0)'}), |
|
286 | font: '12px monospace bold', |
|
287 | backgroundFill: new ol.style.Fill({color: 'rgba(255,255,255,.4)'}) |
|
288 | }), image: new ol.style.Icon({ |
|
289 | src: "./" + p.img, crossOrigin: '', opacity: p.opacity, scale: iconScale, rotation: p.angle * Math.PI / 180, |
|
290 | }), |
|
291 | })); |
|
292 | vectorSource.addFeature(f); |
|
293 | }); |
|
294 | ||
295 | overlayGroup.getLayers().push(new ol.layer.Vector({title: 'POI', visible: true, source: vectorSource})); |
|
296 | ||
297 | if (mapOpts.kmlfile.length > 0) { |
|
298 | overlayGroup.getLayers().push(new ol.layer.Vector({ |
|
299 | title: 'KML file', visible: true, |
|
300 | source: new ol.source.VectorSource({ |
|
301 | url: mapOpts.kmlfile, |
|
302 | format: new ol.format.KML(), |
|
303 | }), |
|
304 | style: {label: "${name}"} |
|
305 | })); |
|
306 | } |
|
307 | ||
308 | if (mapOpts.geojsonfile.length > 0) { |
|
309 | overlayGroup.getLayers().push(new ol.layer.Vector({ |
|
310 | title: 'GeoJSON file', visible: true, |
|
311 | source: new ol.source.VectorSource({ |
|
312 | url: mapOpts.geojsonfile, |
|
313 | format: new ol.format.GeoJSON(), |
|
314 | }), |
|
315 | style: { |
|
316 | /* TODO */ |
|
317 | strokeColor: "#FF00FF", |
|
318 | strokeWidth: 3, |
|
319 | strokeOpacity: 0.7, |
|
320 | pointRadius: 4, |
|
321 | fillColor: "#FF99FF", |
|
322 | fillOpacity: 0.7 |
|
323 | } |
|
324 | })); |
|
325 | } |
|
326 | ||
327 | if (mapOpts.gpxfile.length > 0) { |
|
328 | overlayGroup.getLayers().push(new ol.layer.Vector({ |
|
329 | title: 'GPS track', visible: true, |
|
330 | source: new ol.source.VectorSource({ |
|
331 | url: mapOpts.gpxfile, |
|
332 | format: new ol.format.GPX(), |
|
333 | }), |
|
334 | style: { |
|
335 | /* TODO */ |
|
336 | strokeColor: "#0000FF", |
|
337 | strokeWidth: 3, |
|
338 | strokeOpacity: 0.7, |
|
339 | pointRadius: 4, |
|
340 | fillColor: "#0099FF", |
|
341 | fillOpacity: 0.7 |
|
342 | } |
|
343 | })); |
|
344 | } |
|
345 | ||
346 | const container = document.getElementById('popup'); |
|
347 | const content = document.getElementById('popup-content'); |
|
348 | const closer = document.getElementById('popup-closer'); |
|
349 | ||
350 | const overlay = new ol.Overlay({ |
|
351 | element: container, autoPan: true, autoPanAnimation: { |
|
352 | duration: 250, |
|
353 | }, //stopEvent: false, |
|
354 | }); |
|
355 | map.addOverlay(overlay); |
|
356 | ||
357 | /** |
|
358 | * Add a click handler to hide the popup. |
|
359 | * @return {boolean} Don't follow the href. |
|
360 | */ |
|
361 | closer.onclick = function () { |
|
362 | overlay.setPosition(undefined); |
|
363 | closer.blur(); |
|
364 | return false; |
|
365 | }; |
|
366 | ||
367 | // display popup on click |
|
368 | map.on('singleclick', function (evt) { |
|
369 | const selFeature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { |
|
370 | return feature; |
|
371 | }); |
|
372 | if (selFeature) { |
|
373 | overlay.setPosition(evt.coordinate); |
|
374 | ||
375 | let pContent = '<div class="spacer"> </div>'; |
|
376 | let locDesc = ''; |
|
377 | ||
378 | if (selFeature.get('rowId') !== undefined) { |
|
379 | pContent += '<span class="rowId">' + selFeature.get('rowId') + ': </span>'; |
|
380 | } |
|
381 | if (selFeature.get('name') !== undefined) { |
|
382 | pContent += '<span class="txt">' + selFeature.get('name') + '</span>'; |
|
383 | locDesc = selFeature.get('name'); |
|
384 | // TODO strip <p> tag from locDesc |
|
385 | // locDesc = selFeature.get('name').split(/\s+/).slice(0,2).join('+'); |
|
386 | } |
|
387 | if (selFeature.get('ele') !== undefined) { |
|
388 | pContent += '<div class="ele">elevation: ' + selFeature.get('ele') + '</div>'; |
|
389 | } |
|
390 | if (selFeature.get('type') !== undefined) { |
|
391 | pContent += '<div>' + selFeature.get('type') + '</div>'; |
|
392 | } |
|
393 | if (selFeature.get('time') !== undefined) { |
|
394 | pContent += '<div class="time">time: ' + selFeature.get('time') + '</div>'; |
|
395 | } |
|
396 | if (selFeature.get('description') !== undefined) { |
|
397 | pContent += '<div class="desc">' + selFeature.get('description') + '</div>'; |
|
398 | } |
|
399 | if (selFeature.get('img') !== undefined) { |
|
400 | pContent += '<div class="coord" title="lat;lon">' + '<img src="' + selFeature.get('img') + '" width="16" height="16" ' + 'style="transform:rotate(' + selFeature.get('angle') + 'deg)" /> ' + '<a href="geo:' + selFeature.get('lat') + ',' + selFeature.get('lon') + '?q=' + selFeature.get('lat') + ',' + selFeature.get('lon') + '(' + selFeature.get('alt') + ')" title="Open in navigation app">' + ol.coordinate.format([selFeature.get('lon'), selFeature.get('lat')], '{x}º; {y}º', 4) + '</a></div>'; |
|
401 | } |
|
402 | content.innerHTML = pContent; |
|
403 | } else { |
|
404 | // do nothing?... |
|
405 | } |
|
406 | }); |
|
407 | ||
408 | // change mouse cursor when over marker |
|
409 | map.on('pointermove', function (e) { |
|
410 | const pixel = map.getEventPixel(e.originalEvent); |
|
411 | const hit = map.hasFeatureAtPixel(pixel); |
|
412 | map.getTarget().style.cursor = hit ? 'pointer' : ''; |
|
413 | }); |
|
414 | ||
415 | return map; |
|
416 | } |
|
417 | ||
418 | ||
419 | /** init. */ |
|
420 | function olInit() { |
|
421 | if (olEnable) { |
|
422 | ||
423 | ||
424 | ||
425 | ||
426 | var _i = 0; |
|
427 | // create the maps in the page |
|
428 | for (_i = 0; _i < olMapData.length; _i++) { |
|
429 | var _id = olMapData[_i].mapOpts.id; |
|
430 | olMaps[_id] = createMap(olMapData[_i].mapOpts, olMapData[_i].poi); |
|
431 | ||
432 | // set max-width on help pop-over |
|
433 | jQuery('#' + _id).parent().parent().find('.olMapHelp').css('max-width', olMapData[_i].mapOpts.width); |
|
434 | ||
435 | // shrink the map width to fit inside page container |
|
436 | const _w = jQuery('#' + _id + '-olContainer').parent().innerWidth(); |
|
437 | if (parseInt(olMapData[_i].mapOpts.width) > _w) { |
|
438 | jQuery('#' + _id).width(_w); |
|
439 | jQuery('#' + _id + '-olStatusBar').width(_w); |
|
440 | jQuery('#' + _id).parent().parent().find('.olMapHelp').width(_w); |
|
441 | olMaps[_id].updateSize(); |
|
442 | } |
|
443 | } |
|
444 | ||
445 | var resizeTimer; |
|
446 | jQuery(window).on('resize', function (e) { |
|
447 | clearTimeout(resizeTimer); |
|
448 | resizeTimer = setTimeout(function () { |
|
449 | for (_i = 0; _i < olMapData.length; _i++) { |
|
450 | var _id = olMapData[_i].mapOpts.id; |
|
451 | var _w = jQuery('#' + _id + '-olContainer').parent().innerWidth(); |
|
452 | if (parseInt(olMapData[_i].mapOpts.width) > _w) { |
|
453 | jQuery('#' + _id).width(_w); |
|
454 | jQuery('#' + _id + '-olStatusBar').width(_w); |
|
455 | jQuery('#' + _id).parent().parent().find('.olMapHelp').width(_w); |
|
456 | olMaps[_id].updateSize(); |
|
457 | } |
|
458 | } |
|
459 | }, 250); |
|
460 | }); |
|
461 | ||
462 | // hide the table(s) with POI by giving it a print-only style |
|
463 | jQuery('.olPOItableSpan').addClass('olPrintOnly'); |
|
464 | // hide the static map image(s) by giving it a print only style |
|
465 | jQuery('.olStaticMap').addClass('olPrintOnly'); |
|
466 | // add help button with toggle. |
|
467 | jQuery('.olWebOnly > .olMap') |
|
468 | .prepend( |
|
469 | '<div class="olMapHelpButtonDiv">' |
|
470 | + '<button onclick="jQuery(\'.olMapHelp\').toggle(500);" class="olMapHelpButton olHasTooltip"><span>' |
|
471 | + OpenLayers.i18n("toggle_help") + '</span>?</button></div>'); |
|
472 | // toggle to switch dynamic vs. static map |
|
473 | jQuery('.olMapHelp').before( |
|
474 | '<div class="a11y"><button onclick="jQuery(\'.olPrintOnly\').toggle();jQuery(\'.olWebOnly\').toggle();">' |
|
475 | + OpenLayers.i18n("toggle_dynamic_map") + '</button></div>'); |
|
476 | } |
|
477 | } |
|
478 | ||
479 | ||
480 | /** |
|
481 | * ol api flag. |
|
482 | * |
|
483 | * @type {Boolean} |
|
484 | */ |
|
485 | let olEnable = false; |
|
486 | /** |
|
487 | * An array with data for each map in the page. |
|
488 | * |
|
489 | * @type {Array} |
|
490 | */ |
|
491 | const olMapData = []; |
|
492 | /** |
|
493 | * Holds a reference to all of the maps on this page with the map's id as key. |
|
494 | * Can be used as an extension point. |
|
495 | * |
|
496 | * @type {Object} |
|
497 | */ |
|
498 | let olMaps = {}; |
|
499 | /** |
|
500 | * Stamen tiles flag. |
|
501 | * |
|
502 | * @type {Boolean} |
|
503 | */ |
|
504 | let stamenEnable = false; |
|
505 | ||
506 | /** |
|
507 | * Bing tiles flag. |
|
508 | * |
|
509 | * @type {Boolean} |
|
510 | */ |
|
511 | let bEnable = false; |
|
512 | /** |
|
513 | * Bing API key. |
|
514 | * |
|
515 | * @type {String} |
|
516 | */ |
|
517 | let bApiKey = ''; |
|
518 | ||
519 | /** |
|
520 | * Thunderforest API key. |
|
521 | * |
|
522 | * @type {String} |
|
523 | */ |
|
524 | let tfApiKey = '' |
|
525 | /** |
|
526 | * OSM tiles flag. |
|
527 | * |
|
528 | * @type {Boolean} |
|
529 | */ |
|
530 | let osmEnable = true; |
|
531 | /** |
|
532 | * CSS support flag. |
|
533 | * |
|
534 | * @type {Boolean} |
|
535 | */ |
|
536 | let olCSSEnable = true; |
|
537 | ||
538 | /* register olInit to run with onload event. */ |
|
539 | jQuery(olInit); |
|
540 |