Code Duplication    Length = 513-528 lines in 2 locations

script.js 1 location

@@ 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 &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
136
                                      + 'Tiles &copy;<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 &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
149
                                      + 'Tiles &copy;<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 &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
162
                                      + 'Tiles &copy;<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 &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
175
                                      + 'Tiles &copy;<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">&nbsp;</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)" />&nbsp;' +
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

ol6/script.js 1 location

@@ 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 &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
138
                                  + 'Tiles &copy;<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 &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
151
                                  + 'Tiles &copy;<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 &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
164
                                  + 'Tiles &copy;<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 &copy;ODbL <a href="https://openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>, '
177
                                  + 'Tiles &copy;<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">&nbsp;</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)" />&nbsp;' + '<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