xref: /plugin/openlayersmap/script.js (revision e299f0b739dd8395dd450353bd7e5c1cec99aa5c)
1/*
2 * Copyright (c) 2008-2026 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/**
19 * Test for css support in the browser by sniffing for a css class we added
20 * using JavaScript added by the action plugin; this is an edge case because
21 * browsers that support JavaScript generally support css as well.
22 *
23 * @returns {Boolean} true when the browser supports css (and implicitly
24 *          JavaScript)
25 */
26function 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 */
41function olCreateMaptag(mapid, width, height) {
42    const // fragment
43        frag = document.createDocumentFragment(),
44        // temp node
45        temp = document.createElement('div');
46    temp.innerHTML = '<div id="' + mapid + '-olContainer" class="olContainer olWebOnly">'
47        // map
48        + '<div id="' + mapid + '" tabindex="0" style="width:' + width + ';height:' + height + ';" class="olMap"></div>'
49        + '</div>';
50    while (temp.firstChild) {
51        frag.appendChild(temp.firstChild);
52    }
53    return frag;
54}
55
56/**
57 * Create the map based on the params given.
58 *
59 * @param mapOpts {Object}
60 *            mapOpts MapOptions hash {id:'olmap', width:500px, height:500px,
61 *            lat:6710200, lon:506500, zoom:13, controls:1,
62 *            baselyr:'', kmlfile:'', gpxfile:'', geojsonfile,
63 *            summary:''}
64 * @param poi {Array}
65 *            OLmapPOI array with POI's [ {lat:6710300,lon:506000,txt:'instap
66 *            punt',angle:180,opacity:.9,img:'', rowId:n},... ]);
67 *
68 * @return {ol.Map} the created map
69 */
70function createMap(mapOpts, poi) {
71
72    // const mapOpts = olMapData[0].mapOpts;
73    // const poi = olMapData[0].poi;
74    const autoZoom_options = {padding: [16, 16, 16, 16]};
75
76    if (!olEnable) {
77        return null;
78    }
79    if (!olTestCSSsupport()) {
80        olEnable = false;
81        return null;
82    }
83
84    // find map element location
85    const cleartag = document.getElementById(mapOpts.id + '-clearer');
86    if (cleartag === null) {
87        return null;
88    }
89    // create map element and add to document
90    const fragment = olCreateMaptag(mapOpts.id, mapOpts.width, mapOpts.height);
91    cleartag.parentNode.insertBefore(fragment, cleartag);
92
93    /** dynamic map extent. */
94    let extent = ol.extent.createEmpty();
95    let overlayGroup = new ol.layer.Group({title: 'Overlays', fold: 'open', layers: []});
96    const baseLyrGroup = new ol.layer.Group({'title': 'Base maps', layers: []});
97
98    const map = new ol.Map({
99        target: document.getElementById(mapOpts.id),
100        layers: [baseLyrGroup, overlayGroup],
101        view: new ol.View({
102            center: ol.proj.transform([mapOpts.lon, mapOpts.lat], 'EPSG:4326', 'EPSG:3857'),
103            zoom: mapOpts.zoom,
104            projection: 'EPSG:3857'
105        }),
106        controls: [
107            new ol.control.Attribution({
108                collapsible: true,
109                collapsed: true
110            })
111        ]
112    });
113
114    if (osmEnable) {
115        baseLyrGroup.getLayers().push(
116            new ol.layer.Tile({
117                visible: true,
118                title: 'OSM',
119                type: 'base',
120                source: new ol.source.OSM()
121            }));
122
123        baseLyrGroup.getLayers().push(
124            new ol.layer.Tile({
125                visible: mapOpts.baselyr === "opentopomap",
126                title: 'opentopomap',
127                type: 'base',
128                source: new ol.source.OSM({
129                    url: 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png',
130                    attributions: 'Data &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
131                        + '<a href="http://viewfinderpanoramas.org" target="_blank">SRTM</a>, '
132                        + 'style &copy;<a href="https://opentopomap.org/" target="_blank">OpenTopoMap</a>'
133                        + '(<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
134                })
135            }));
136
137        baseLyrGroup.getLayers().push(
138            new ol.layer.Tile({
139                visible: mapOpts.baselyr === "cycle map",
140                title: 'cycle map',
141                type: 'base',
142                source: new ol.source.OSM({
143                    url: 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=' + tfApiKey,
144                    attributions: 'Data &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
145                        + 'Tiles &copy;<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>'
146                        + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>'
147                })
148            }));
149
150        baseLyrGroup.getLayers().push(
151            new ol.layer.Tile({
152                visible: mapOpts.baselyr === "transport",
153                title: 'transport',
154                type: 'base',
155                source: new ol.source.OSM({
156                    url: 'https://{a-c}.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=' + tfApiKey,
157                    attributions: 'Data &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
158                        + 'Tiles &copy;<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>'
159                        + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>'
160                })
161            }));
162
163        baseLyrGroup.getLayers().push(
164            new ol.layer.Tile({
165                visible: mapOpts.baselyr === "landscape",
166                title: 'landscape',
167                type: 'base',
168                source: new ol.source.OSM({
169                    url: 'https://{a-c}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png?apikey=' + tfApiKey,
170                    attributions: 'Data &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
171                        + 'Tiles &copy;<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>'
172                        + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>'
173                })
174            }));
175
176        baseLyrGroup.getLayers().push(
177            new ol.layer.Tile({
178                visible: mapOpts.baselyr === "outdoors",
179                title: 'outdoors',
180                type: 'base',
181                source: new ol.source.OSM({
182                    url: 'https://{a-c}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey=' + tfApiKey,
183                    attributions: 'Data &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
184                        + 'Tiles &copy;<a href="https://www.thunderforest.com/" target="_blank">Thunderforest</a>'
185                        + '<img src="https://www.thunderforest.com/favicon.ico" alt="Thunderforest logo"/>'
186                })
187            }));
188    }
189
190    if (aEnable && aApiKey !== '') {
191        baseLyrGroup.getLayers().push(
192            new ol.layer.Tile({
193                visible: mapOpts.baselyr === "azure road",
194                title: 'Azure road',
195                type: 'base',
196                source: new ol.source.ImageTile({
197                    attributions: `© ${new Date().getFullYear()} TomTom, Microsoft`,
198                    url: `https://atlas.microsoft.com/map/tile?subscription-key=${aApiKey}&api-version=2.0&tilesetId=microsoft.base.road&zoom={z}&x={x}&y={y}&tileSize=256`,
199                    imagerySet: 'Road'
200                })
201            }));
202
203        baseLyrGroup.getLayers().push(
204            new ol.layer.Tile({
205                visible: mapOpts.baselyr === "azure sat",
206                title: 'Azure sat',
207                type: 'base',
208                source: new ol.source.ImageTile({
209                    attributions: `© ${new Date().getFullYear()} TomTom, Microsoft`,
210                    url: `https://atlas.microsoft.com/map/tile?subscription-key=${aApiKey}&api-version=2.0&tilesetId=microsoft.imagery&zoom={z}&x={x}&y={y}&tileSize=256`,
211                    imagerySet: 'Aerial'
212                })
213            }));
214    }
215
216    if (stadiaEnable) {
217        baseLyrGroup.getLayers().push(
218            new ol.layer.Tile({
219                visible: mapOpts.baselyr === "toner",
220                type: 'base',
221                title: 'toner',
222                // apiKey: 'OPTIONAL', (we suggest domain-based auth)
223                source: new ol.source.StadiaMaps({
224                    layer: 'stamen_toner',
225                    // missing CORS header
226                    // url:'https://tiles-eu.stadiamaps.com/tiles/'
227                })
228            })
229        );
230
231        baseLyrGroup.getLayers().push(
232            new ol.layer.Tile({
233                visible: mapOpts.baselyr === "terrain",
234                type: 'base',
235                title: 'terrain',
236                // apiKey: 'OPTIONAL', (we suggest domain-based auth)
237                source: new ol.source.StadiaMaps({
238                    layer: 'stamen_terrain',
239                    // missing CORS header
240                    // url:'https://tiles-eu.stadiamaps.com/tiles/'
241                })
242            })
243        );
244    }
245
246    extent = ol.extent.extend(extent, map.getView().calculateExtent());
247
248    const iconScale = window.devicePixelRatio ?? 1.0;
249    const vectorSource = new ol.source.Vector();
250    poi.forEach((p) => {
251        const f = new ol.Feature({
252            geometry: new ol.geom.Point(ol.proj.fromLonLat([p.lon, p.lat])),
253            description: p.txt,
254            img: p.img,
255            rowId: p.rowId,
256            lat: p.lat,
257            lon: p.lon,
258            angle: p.angle,
259            opacity: p.opacity,
260            alt: p.img.substring(0, p.img.lastIndexOf("."))
261        });
262        f.setId(p.rowId);
263        vectorSource.addFeature(f);
264    });
265
266    const vectorLayer = new ol.layer.Vector({
267        title: 'POI',
268        visible: true,
269        source: vectorSource,
270        style(feature, resolution) {
271            const img = feature.get('img');
272            const opacity = feature.get('opacity');
273            const angle = feature.get('angle');
274            const text = feature.get('rowId');
275
276            return new ol.style.Style({
277                image: new ol.style.Icon({
278                    src: `${DOKU_BASE}lib/plugins/openlayersmap/icons/${img}`,
279                    crossOrigin: '',
280                    opacity: opacity,
281                    scale: iconScale,
282                    rotation: angle * Math.PI / 180,
283                    // width/height were added in OpenLayers 7.2.2
284                    // see https://github.com/openlayers/openlayers/pull/14364
285                }),
286                text: new ol.style.Text({
287                    text: `${text}`,
288                    textAlign: 'center',
289                    textBaseline: 'middle',
290                    offsetX: (8 + 4 + 2) * iconScale,
291                    offsetY: -8 * iconScale,
292                    scale: iconScale,
293                    fill: new ol.style.Fill({color: 'rgb(0,0,0)'}),
294                    font: 'bold 16px monospace',
295                    stroke: new ol.style.Stroke({color: 'rgba(255,255,255,.4)', width: 5}),
296                })
297            });
298        }
299    });
300    overlayGroup.getLayers().push(vectorLayer);
301    if (mapOpts.autozoom) {
302        extent = ol.extent.extend(extent, vectorSource.getExtent());
303        map.getView().fit(extent, autoZoom_options);
304    }
305
306    if (mapOpts.controls === 1) {
307        map.addControl(new ol.control.Zoom());
308        map.addControl(new ol.control.ScaleLine({bar: true, text: true}));
309        map.addControl(new ol.control.MousePosition({
310            coordinateFormat: ol.coordinate.createStringXY(4), projection: 'EPSG:4326',
311        }));
312        map.addControl(new ol.control.FullScreen({
313            // Square Four Corners / U+26F6
314            label: '⛶',
315            labelActive: '▢'
316        }));
317        map.addControl(new ol.control.OverviewMap({
318            label: '+',
319            layers: [new ol.layer.Tile({
320                source: new ol.source.OSM()
321            })]
322        }));
323        map.addControl(new ol.control.LayerSwitcher({
324            activationMode: 'click',
325            label: '\u2630',
326            collapseLabel: '\u00BB',
327        }));
328    }
329
330    if (mapOpts.kmlfile.length > 0) {
331        try {
332            const kmlSource = new ol.source.Vector({
333                url: DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.kmlfile,
334                format: new ol.format.KML(),
335            });
336            overlayGroup.getLayers().push(new ol.layer.Vector({title: 'KML file', visible: true, source: kmlSource}));
337
338            if (mapOpts.autozoom) {
339                kmlSource.once('change', function () {
340                    extent = ol.extent.extend(extent, kmlSource.getExtent());
341                    map.getView().fit(extent, autoZoom_options);
342                });
343            }
344        } catch (e) {
345            console.error(e);
346        }
347    }
348
349    if (mapOpts.geojsonfile.length > 0) {
350        try {
351            // these are the same colour as in StaticMap#drawJSON()
352            const geoJsonStyle = {
353                'Point': new ol.style.Style({
354                    image: new ol.style.Circle({
355                        fill: new ol.style.Fill({
356                            color: 'rgba(255,0,255,0.4)',
357                        }),
358                        radius: 5,
359                        stroke: new ol.style.Stroke({
360                            color: 'rgba(255,0,255,0.9)',
361                            width: 1,
362                        }),
363                    }),
364                }),
365                'LineString': new ol.style.Style({
366                    stroke: new ol.style.Stroke({
367                        color: 'rgba(255,0,255,0.9)',
368                        width: 3,
369                    }),
370                }),
371                'MultiLineString': new ol.style.Style({
372                    stroke: new ol.style.Stroke({
373                        color: 'rgba(255,0,255,0.9)',
374                        width: 3,
375                    }),
376                }),
377                'Polygon': new ol.style.Style({
378                    stroke: new ol.style.Stroke({
379                        color: 'rgba(255,0,255,0.9)',
380                        width: 3,
381                    }),
382                    fill: new ol.style.Fill({
383                        color: 'rgba(255,0,255,0.4)',
384                    }),
385                }),
386                'MultiPolygon': new ol.style.Style({
387                    stroke: new ol.style.Stroke({
388                        color: 'rgba(255,0,255,0.9)',
389                        width: 3,
390                    }),
391                    fill: new ol.style.Fill({
392                        color: 'rgba(255,0,255,0.4)',
393                    }),
394                }),
395            };
396            const geoJsonSource = new ol.source.Vector({
397                url: DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.geojsonfile,
398                format: new ol.format.GeoJSON(),
399            });
400            overlayGroup.getLayers().push(new ol.layer.Vector({
401                title: 'GeoJSON file', visible: true, source: geoJsonSource,
402                style: function (feature) {
403                    return geoJsonStyle[feature.getGeometry().getType()];
404                },
405            }));
406
407            if (mapOpts.autozoom) {
408                geoJsonSource.once('change', function () {
409                    extent = ol.extent.extend(extent, geoJsonSource.getExtent());
410                    map.getView().fit(extent, autoZoom_options);
411                });
412            }
413        } catch (e) {
414            console.error(e);
415        }
416    }
417
418    if (mapOpts.gpxfile.length > 0) {
419        try {
420            // these are the same colour as in StaticMap#drawGPX()
421            const gpxJsonStyle = {
422                'Point': new ol.style.Style({
423                    image: new ol.style.Circle({
424                        fill: new ol.style.Fill({
425                            color: 'rgba(0,0,255,0.4)',
426                        }),
427                        radius: 5,
428                        stroke: new ol.style.Stroke({
429                            color: 'rgba(0,0,255,0.9)',
430                            width: 1,
431                        }),
432                    }),
433                }),
434                'LineString': new ol.style.Style({
435                    stroke: new ol.style.Stroke({
436                        color: 'rgba(0,0,255,0.9)',
437                        width: 3,
438                    }),
439                }),
440                'MultiLineString': new ol.style.Style({
441                    stroke: new ol.style.Stroke({
442                        color: 'rgba(0,0,255,0.9)',
443                        width: 3,
444                    }),
445                }),
446            };
447            const gpxSource = new ol.source.Vector({
448                url: DOKU_BASE + "lib/exe/fetch.php?media=" + mapOpts.gpxfile,
449                format: new ol.format.GPX(),
450            });
451            overlayGroup.getLayers().push(new ol.layer.Vector({
452                title: 'GPS track', visible: true, source: gpxSource,
453                style: function (feature) {
454                    return gpxJsonStyle[feature.getGeometry().getType()];
455                },
456            }));
457
458            if (mapOpts.autozoom) {
459                gpxSource.once('change', function () {
460                    extent = ol.extent.extend(extent, gpxSource.getExtent());
461                    map.getView().fit(extent, autoZoom_options);
462                });
463            }
464        } catch (e) {
465            console.error(e);
466        }
467    }
468
469    const container = document.getElementById('popup');
470    const content = document.getElementById('popup-content');
471    const closer = document.getElementById('popup-closer');
472
473    const overlay = new ol.Overlay({
474        element: container,
475        positioning: 'center-center',
476        stopEvent: true,
477        autoPan: {
478            animation: {
479                duration: 250,
480            }
481        },
482    });
483    map.addOverlay(overlay);
484
485    /**
486     * Add a click handler to hide the popup.
487     * @return {boolean} Don't follow the href.
488     */
489    closer.onclick = function () {
490        overlay.setPosition(undefined);
491        closer.blur();
492        return false;
493    };
494
495    // display popup on click
496    map.on('singleclick', function (evt) {
497        const selFeature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
498            return feature;
499        });
500        if (selFeature) {
501            overlay.setPosition(evt.coordinate);
502
503            let pContent = '<div class="spacer">&nbsp;</div>';
504            // let locDesc = '';
505
506            if (selFeature.get('rowId') !== undefined) {
507                pContent += '<span class="rowId">' + selFeature.get('rowId') + ': </span>';
508            }
509            if (selFeature.get('name') !== undefined) {
510                pContent += '<span class="txt">' + selFeature.get('name') + '</span>';
511                // locDesc = selFeature.get('name');
512                // TODO strip <p> tag from locDesc
513                // locDesc = selFeature.get('name').split(/\s+/).slice(0,2).join('+');
514            }
515            if (selFeature.get('ele') !== undefined) {
516                pContent += '<div class="ele">elevation: ' + selFeature.get('ele') + '</div>';
517            }
518            if (selFeature.get('type') !== undefined) {
519                pContent += '<div>' + selFeature.get('type') + '</div>';
520            }
521            if (selFeature.get('time') !== undefined) {
522                pContent += '<div class="time">time: ' + selFeature.get('time') + '</div>';
523            }
524            if (selFeature.get('description') !== undefined) {
525                pContent += '<div class="desc">' + selFeature.get('description') + '</div>';
526            }
527            if (selFeature.get('img') !== undefined) {
528                const _alt = selFeature.get('alt');
529                // Android Maps intent: https://developer.android.com/guide/components/intents-common#Maps
530                // geo uri scheme: https://en.wikipedia.org/wiki/Geo_URI_scheme
531                // geo uri reference: https://tools.ietf.org/html/rfc5870
532                // OSM wiki: https://wiki.openstreetmap.org/wiki/Geo_URI_scheme
533                pContent += '<div class="coord" title="lat;lon">' +
534                    '<img alt="' + _alt + '" src="' + DOKU_BASE + 'lib/plugins/openlayersmap/icons/' + selFeature.get('img') +
535                    '" width="16" height="16" ' + 'style="transform:rotate(' + selFeature.get('angle') + 'deg)" />&nbsp;' +
536                    '<a href="geo:' + selFeature.get('lat') + ','
537                    + selFeature.get('lon') +
538                    '?q=' + selFeature.get('lat') + ',' +
539                    selFeature.get('lon') +
540                    '&name=' + selFeature.get('alt') +
541                    ';z=16" title="Open in navigation app">' +
542                    ol.coordinate.format([selFeature.get('lon'), selFeature.get('lat')], '{x}º; {y}º', 4)
543                    + '</a>' +
544                    '</div>';
545            }
546            content.innerHTML = pContent;
547        } else {
548            // do nothing...
549        }
550    });
551
552    // change mouse cursor when over marker
553    map.on('pointermove', function (e) {
554        const pixel = map.getEventPixel(e.originalEvent);
555        const hit = map.hasFeatureAtPixel(pixel);
556        map.getTarget().style.cursor = hit ? 'pointer' : '';
557    });
558
559    return map;
560}
561
562/**
563 * add layers to the map based on the olMapOverlays object.
564 */
565function olovAddToMap() {
566    for (const key in olMapOverlays) {
567        const overlay = olMapOverlays[key];
568        const m = olMaps[overlay.id];
569
570        let targetGroup;
571        const isBaselayer = overlay.baselayer && (overlay.baselayer).toLowerCase() === 'true';
572        m.getLayers().forEach(function (layer) {
573            if (layer.get('title') === 'Base maps' && isBaselayer) {
574                targetGroup = layer;
575            }
576            if (layer.get('title') === 'Overlays' && !isBaselayer) {
577                targetGroup = layer;
578            }
579        });
580
581        switch (overlay.type) {
582            case 'osm':
583                targetGroup.getLayers().push(new ol.layer.Tile({
584                    title: overlay.name,
585                    visible: (overlay.visible).toLowerCase() === 'true',
586                    opacity: parseFloat(overlay.opacity),
587                    source: new ol.source.OSM({
588                        url: overlay.url,
589                        crossOrigin: 'Anonymous',
590                        attributions: overlay.attribution
591                    }),
592                    type: overlay.baselayer ? 'base' : 'overlay'
593                }));
594                break;
595            case 'wms':
596                targetGroup.getLayers().push(new ol.layer.Image({
597                    title: overlay.name,
598                    opacity: parseFloat(overlay.opacity),
599                    visible: (overlay.visible).toLowerCase() === 'true',
600                    source: new ol.source.ImageWMS({
601                        url: overlay.url,
602                        params: {
603                            'LAYERS': overlay.layers,
604                            'VERSION': overlay.version,
605                            'TRANSPARENT': overlay.transparent,
606                            'FORMAT': overlay.format
607                        },
608                        ratio: 1,
609                        crossOrigin: 'Anonymous',
610                        attributions: overlay.attribution
611                    }),
612                    type: overlay.baselayer ? 'base' : 'overlay'
613                }));
614                break;
615            case 'wmts':
616                const parser = new ol.format.WMTSCapabilities();
617                fetch(overlay.url).then(function (response) {
618                    return response.text();
619                }).then(function (text) {
620                    const wmtsCap = parser.read(text);
621                    const options = ol.source.WMTS.optionsFromCapabilities(wmtsCap, {
622                        layer: overlay.layer,
623                        matrixSet: overlay.matrixSet
624                    });
625                    options.crossOrigin = 'Anonymous';
626                    options.attributions = overlay.attribution;
627                    targetGroup.getLayers().push(new ol.layer.Tile({
628                        title: overlay.name,
629                        opacity: parseFloat(overlay.opacity),
630                        visible: (overlay.visible).toLowerCase() === 'true',
631                        source: new ol.source.WMTS(options),
632                        type: overlay.baselayer ? 'base' : 'overlay'
633                    }));
634                });
635                break;
636            case 'ags':
637                targetGroup.getLayers().push(new ol.layer.Image({
638                    title: overlay.name,
639                    opacity: parseFloat(overlay.opacity),
640                    visible: (overlay.visible).toLowerCase() === 'true',
641                    source: new ol.source.ImageArcGISRest({
642                        url: overlay.url,
643                        params: {
644                            'LAYERS': overlay.layers,
645                            'TRANSPARENT': overlay.transparent,
646                            'FORMAT': overlay.format
647                        },
648                        ratio: 1,
649                        crossOrigin: 'Anonymous',
650                        attributions: overlay.attribution,
651                        type: overlay.baselayer ? 'base' : 'overlay'
652                    })
653                }));
654                break;
655            // case 'mapillary':
656            //     var mUrl = 'http://api.mapillary.com/v1/im/search?';
657            //     if (overlay.skey !== '') {
658            //         mUrl = 'http://api.mapillary.com/v1/im/sequence?';
659            //     }
660            //     var mLyr = new OpenLayers.Layer.Vector(
661            //         "Mapillary", {
662            //             projection:  new OpenLayers.Projection("EPSG:4326"),
663            //             strategies:  [new OpenLayers.Strategy.BBOX({
664            //                 ratio:     1.1,
665            //                 resFactor: 1.5
666            //             }) /* ,new OpenLayers.Strategy.Cluster({}) */],
667            //             protocol:    new OpenLayers.Protocol.HTTP({
668            //                 url:            mUrl,
669            //                 format:         new OpenLayers.Format.GeoJSON(),
670            //                 params:         {
671            //                     // default to max. 250 locations
672            //                     'max-results': 250,
673            //                     'geojson':     true,
674            //                     'skey':        overlay.skey
675            //                 },
676            //                 filterToParams: function (filter, params) {
677            //                     if (filter.type === OpenLayers.Filter.Spatial.BBOX) {
678            //                         // override the bbox serialization of
679            //                         // the filter to give the Mapillary
680            //                         // specific bounds
681            //                         params['min-lat'] = filter.value.bottom;
682            //                         params['max-lat'] = filter.value.top;
683            //                         params['min-lon'] = filter.value.left;
684            //                         params['max-lon'] = filter.value.right;
685            //                         // if the width of our bbox width is
686            //                         // less than 0.15 degrees drop the max
687            //                         // results
688            //                         if (filter.value.top - filter.value.bottom < .15) {
689            //                             OpenLayers.Console.debug('dropping max-results parameter, width is: ',
690            //                                 filter.value.top - filter.value.bottom);
691            //                             params['max-results'] = null;
692            //                         }
693            //                     }
694            //                     return params;
695            //                 }
696            //             }),
697            //             styleMap:    new OpenLayers.StyleMap({
698            //                 'default': {
699            //                     cursor:          'help',
700            //                     rotation:        '${ca}',
701            //                     externalGraphic: DOKU_BASE + 'lib/plugins/openlayersmapoverlays/icons/arrow-up-20.png',
702            //                     graphicHeight:   20,
703            //                     graphicWidth:    20,
704            //                 },
705            //                 'select':  {
706            //                     externalGraphic: DOKU_BASE + 'lib/plugins/openlayersmapoverlays/icons/arrow-up-20-select.png',
707            //                     label:           '${location}',
708            //                     fontSize:        '1em',
709            //                     fontFamily:      'monospace',
710            //                     labelXOffset:    '0.5',
711            //                     labelYOffset:    '0.5',
712            //                     labelAlign:      'lb',
713            //                 }
714            //             }),
715            //             attribution: '<a href="http://www.mapillary.com/legal.html">' +
716            //                              '<img src="http://mapillary.com/favicon.ico" ' +
717            //                              'alt="Mapillary" height="16" width="16" />Mapillary (CC-BY-SA)',
718            //             visibility:  (overlay.visible).toLowerCase() == 'true',
719            //         });
720            //     m.addLayer(mLyr);
721            //     selectControl.addLayer(mLyr);
722            //     break;
723            // case 'search':
724            //     m.addLayer(new OpenLayers.Layer.Vector(
725            //         overlay.name,
726            //         overlay.url,
727            //         {
728            //             layers:      overlay.layers,
729            //             version:     overlay.version,
730            //             transparent: overlay.transparent,
731            //             format:      overlay.format
732            //         }, {
733            //             opacity:     parseFloat(overlay.opacity),
734            //             visibility:  (overlay.visible).toLowerCase() == 'true',
735            //             isBaseLayer: !1,
736            //             attribution: overlay.attribution
737            //         }
738            //     ));
739            //     break;
740        }
741    }
742}
743
744/** init. */
745function olInit() {
746    if (typeof olEnable !== 'undefined' && olEnable) {
747        // add info window to DOM
748        const frag = document.createDocumentFragment(),
749            temp = document.createElement('div');
750        temp.innerHTML = '<div id="popup" class="olPopup"><a href="#" id="popup-closer" class="olPopupCloseBox"></a><div id="popup-content"></div></div>';
751        while (temp.firstChild) {
752            frag.appendChild(temp.firstChild);
753        }
754        document.body.appendChild(frag);
755
756        let _i = 0;
757        // create the maps in the page
758        for (_i = 0; _i < olMapData.length; _i++) {
759            const _id = olMapData[_i].mapOpts.id;
760            olMaps[_id] = createMap(olMapData[_i].mapOpts, olMapData[_i].poi);
761
762            // set max-width on help pop-over
763            jQuery('#' + _id).parent().parent().find('.olMapHelp').css('max-width', olMapData[_i].mapOpts.width);
764
765            // shrink the map width to fit inside page container
766            const _w = jQuery('#' + _id + '-olContainer').parent().innerWidth();
767            if (parseInt(olMapData[_i].mapOpts.width) > _w) {
768                jQuery('#' + _id).width(_w);
769                jQuery('#' + _id).parent().parent().find('.olMapHelp').width(_w);
770                olMaps[_id].updateSize();
771            }
772        }
773
774        // add overlays
775        olovAddToMap();
776
777        let resizeTimer;
778        jQuery(window).on('resize', function (e) {
779            clearTimeout(resizeTimer);
780            resizeTimer = setTimeout(function () {
781                for (_i = 0; _i < olMapData.length; _i++) {
782                    const _id = olMapData[_i].mapOpts.id;
783                    const _w = jQuery('#' + _id + '-olContainer').parent().innerWidth();
784                    if (parseInt(olMapData[_i].mapOpts.width) > _w) {
785                        jQuery('#' + _id).width(_w);
786                        jQuery('#' + _id).parent().parent().find('.olMapHelp').width(_w);
787                        olMaps[_id].updateSize();
788                    }
789                }
790            }, 250);
791        });
792
793        // hide the table(s) with POI by giving it a print-only style
794        jQuery('.olPOItableSpan').addClass('olPrintOnly');
795        // hide the static map image(s) by giving it a print only style
796        jQuery('.olStaticMap').addClass('olPrintOnly');
797        // add help button with toggle.
798        jQuery('.olWebOnly > .olMap')
799            .prepend(
800                '<div class="olMapHelpButtonDiv">'
801                + '<button onclick="jQuery(\'.olMapHelp\').toggle(500);" class="olMapHelpButton olHasTooltip"><span>'
802                + 'Show or hide help</span>?</button></div>');
803        // toggle to switch dynamic vs. static map
804        jQuery('.olMapHelp').before(
805            '<div class="a11y"><button onclick="jQuery(\'.olPrintOnly\').toggle();jQuery(\'.olWebOnly\').toggle();">'
806            + 'Hide or show the dynamic map</button></div>');
807    }
808}
809
810/**
811 * CSS support flag.
812 *
813 * @type {Boolean}
814 */
815let olCSSEnable = true;
816
817/* register olInit to run with onload event. */
818jQuery(olInit);
819