(function () { var mapNumber = 0, currentProtocol = document.location.protocol, errorLayers = []; if (currentProtocol !== 'http:' && currentProtocol !== 'https:') { currentProtocol = 'https:'; } function getBaseLayers(tiles) { var layers = [], factories = { openstreetmap: function () { return new ol.layer.Tile({ source: new ol.source.OSM(), title: 'OpenStreetMap' }); }, bing_road: function () { return new ol.layer.Tile({ source: new ol.source.BingMaps({ key: jqueryTiki.bingMapsAPIKey, imagerySet: 'Road' }), title: 'BingRoad' }); }, bing_road_on_demand: function () { return new ol.layer.Tile({ source: new ol.source.BingMaps({ key: jqueryTiki.bingMapsAPIKey, imagerySet: 'RoadOnDemand' }), title: 'BingRoadOnDemand' }); }, bing_aerial: function () { return new ol.layer.Tile({ source: new ol.source.BingMaps({ key: jqueryTiki.bingMapsAPIKey, imagerySet: 'Aerial' }), title: 'BingAerial' }); }, bing_aerial_with_labels: function () { return new ol.layer.Tile({ source: new ol.source.BingMaps({ key: jqueryTiki.bingMapsAPIKey, imagerySet: 'AerialWithLabels' }), title: 'BingAerialWithLabels' }); }, bing_ordnance_survey: function () { return new ol.layer.Tile({ source: new ol.source.BingMaps({ key: jqueryTiki.bingMapsAPIKey, imagerySet: 'ordnanceSurvey' }), title: 'BingOrdnanceSurvey' }); }, bing_collins_bart: function () { // doesn't seem to work? return new ol.layer.Tile({ source: new ol.source.BingMaps({ key: jqueryTiki.bingMapsAPIKey, imagerySet: 'collinsBart' }), title: 'BingCollinsBart' }); }, nextzen: function () { var nextzenRoadStyleCache = {}, nextzenStyle = function (feature, resolution) { switch (feature.get('layer')) { case 'water': return new ol.style.Style({ fill: new ol.style.Fill({ color: '#9db9e8' }) }); case 'buildings': return (resolution < 10) ? new ol.style.Style({ fill: new ol.style.Fill({ color: '#bbb', opacity: 0.4 }), stroke: new ol.style.Stroke({ color: '#999', width: 1 }) }) : null; case 'roads': var kind = feature.get('kind'); var railway = feature.get('railway'); var sort_key = feature.get('sort_key'); var styleKey = kind + '/' + railway + '/' + sort_key; var style = nextzenRoadStyleCache[styleKey]; if (!style) { var color, width = 1; if (railway || kind === "rail") { color = "#7a5"; } else if (kind === "major_road"){ color = "#aaa"; } else if (kind === "minor_road"){ color = "#ccb"; } else if (kind === "path"){ color = "#ddd"; } else if (kind === "highway"){ color = "#f39"; width = 2; } else if (kind === "ferry"){ color = "#448cff"; width = 2; } else if (kind === "aeroway"){ color = "#999"; width = 3; } else { color = "#aaa"; console.log("Unknown road kind: " + kind); } style = new ol.style.Style({ stroke: new ol.style.Stroke({ color: color, width: width }), zIndex: sort_key }); nextzenRoadStyleCache[styleKey] = style; } return style; default: return null; } }; return new ol.layer.VectorTile({ source: new ol.source.VectorTile({ attributions: '© OpenStreetMap contributors, Who’s On First, ' + 'Natural Earth, and openstreetmapdata.com', format: new ol.format.MVT({ layertitle: 'layer', layers: ['water', 'roads', 'buildings'] }), maxZoom: 19, url: 'https://tile.nextzen.org/tilezen/vector/v1/all/{z}/{x}/{y}.mvt?api_key=' + jqueryTiki.nextzenAPIKey }), style: function (feature, resolution) { return typeof window.nextzenStyle === "function" ? window.nextzenStyle(feature, resolution) : nextzenStyle(feature, resolution); } }) } }; if (tiles.length === 0) { tiles.push('openstreetmap'); } var visible = true; $.each(tiles, function (k, name) { var getLayer = function (name) { var f = factories[name], layer; if (f) { layer = f(); } else { if (name.match(/stamen_/)) { var flavor = name.substring(7); layer = new ol.layer.Tile({ source: new ol.source.Stamen({ layer: flavor }), title: 'Stamen' + flavor }); } } return layer; }; if (typeof name === "object") { var sublayers = [], names = []; for (i = 0; i < name.length; i++) { sublayers.push(getLayer(name[i])); names.push(name[i]); } layers.push(new ol.layer.Group({ title: k, combine: true, visible: visible, type: "base", layers: sublayers }) ); } else { var lyr = getLayer(name); if (lyr) { lyr.set("visible", visible); lyr.set("type", "base"); lyr.set("title", k); layers.push(lyr); } else { errorLayers.push(name); console.log(tr("Cannot create map layer: " + name)); } } visible = false; }); return layers; } function parseCoordinates(value) { var matching = value.match(/^(-?[0-9]*(\.[0-9]+)?),(-?[0-9]*(\.[0-9]+)?)(,(.*))?$/); if (matching) { var lat = parseFloat(matching[3]); var lon = parseFloat(matching[1]); var zoom = matching[6] ? parseInt(matching[6], 10) : 0; return {lat: lat, lon: lon, zoom: zoom}; } return null; } function writeCoordinates(lonlat, map, fixProjection) { var original = lonlat; if (fixProjection) { lonlat = lonlat.transform( map.getView().getProjection(), new ol.proj.Projection({ code: "EPSG:4326" }) ); if (! lonlat) { lonlat = original; } } let coords = lonlat.getCoordinates(); return formatLocation(coords[0], coords[1], map.getView().getZoom()); } function formatLocation (lat, lon, zoom) { // Convert , decimal points - where those are used var strLon = '' + lon; strLon.replace(',', '.'); var strLat = '' + lat; strLat.replace(',', '.'); return strLon + ',' + strLat + ',' + zoom; } $.fn.createMap = function () { this.each(function () { var id = $(this).attr('id'), container = this, desiredControls; $(container).css('background', 'white'); container.getLayer = function (name) { var vectors; if (name) { if (! container.layers[name]) { // basic feature clustering if ($(container).data("cluster")) { var distance = $(container).data("cluster"), clusterSource = new ol.source.Cluster({ distance: distance, source: new ol.source.Vector({wrapX: false}) }), clusterFillColor = $(container).data("clusterfillcolor"), clusterTextColor = $(container).data("clustertextcolor"); if (clusterFillColor) { clusterFillColor = clusterFillColor.split(","); } else { clusterFillColor = [86, 134, 200]; } if (clusterTextColor) { clusterTextColor = clusterTextColor.split(","); } else { clusterTextColor = [255, 255, 255]; } var styleCache = {}, maxFeatureCount, /** * Calculate size of cluster depending on zoom extent * based on https://openlayers.org/en/latest/examples/earthquake-clusters.html * @param resolution */ calculateClusterInfo = function (resolution) { maxFeatureCount = 0; var features = vectors.getSource().getFeatures(); var feature, radius; for (var i = features.length - 1; i >= 0; --i) { feature = features[i]; var originalFeatures = feature.get('features'); var extent = ol.extent.createEmpty(); var j = (void 0), jj = (void 0); for (j = 0, jj = originalFeatures.length; j < jj; ++j) { ol.extent.extend(extent, originalFeatures[j].getGeometry().getExtent()); } maxFeatureCount = Math.max(maxFeatureCount, jj); radius = .4 * (ol.extent.getWidth(extent) + ol.extent.getHeight(extent)) / resolution; feature.set('radius', radius); } }, createMarkerStyle = function (feature) { if (feature.get("intent") === "marker") { return new ol.style.Style({ geometry: feature.getGeometry(), image: new ol.style.Icon({ anchor: [feature.get("offsetx"), feature.get("offsety")], anchorXUnits: "pixels", anchorYUnits: "pixels", src: feature.get("url") }) }); } }, invisibleFill = new ol.style.Fill({ color: 'rgba(255, 255, 255, 0.01)' }), selectClusterFeaturesStyle = function (feature) { var styles = [], features = feature.get('features'); if (! features && feature) { return createMarkerStyle(feature); } if (features.length > 1) { styles.push(new ol.style.Style({ image: new ol.style.Circle({ radius: feature.get('radius'), fill: invisibleFill }) }) ); } for (var i = features.length - 1; i >= 0; --i) { styles.push(createMarkerStyle(features[i])); } return styles; }; vectors = container.layers[name] = new ol.layer.Vector({ source: clusterSource, title: name, style: function (feature, resolution) { var features = feature.get("features"); if (features && features.length > 1) { calculateClusterInfo(resolution); var size = features.length, style = styleCache[size + " " + maxFeatureCount]; if (!style) { style = new ol.style.Style({ image: new ol.style.Circle({ radius: Math.max(feature.get('radius'), 20), stroke: new ol.style.Stroke({ color: clusterTextColor }), fill: new ol.style.Fill({ color: [ clusterFillColor[0], clusterFillColor[1], clusterFillColor[2], Math.min(0.8, 0.4 + (size / maxFeatureCount)) ] }) }), text: new ol.style.Text({ text: features.length.toString(), font: "14px sans-serif", fill: new ol.style.Fill({ color: clusterTextColor }) }) }); styleCache[size + " " + maxFeatureCount] = style; } } else if (features) { feature = features[0]; if (feature.get("intent") === "marker") { style = createMarkerStyle(feature); } } return style; } }); if ($(container).data("clusterhover") === "features") { container.map.interactions = container.map.getInteractions().extend( [new ol.interaction.Select({ condition: function (evt) { return evt.type === 'pointermove' || evt.type === 'singleclick'; }, style: selectClusterFeaturesStyle, layers: [vectors] })]); } if ($(container).data("popup-style")) { var selectionInteraction = new ol.interaction.Select({ style: createMarkerStyle, layers: [vectors] }); container.map.addInteraction(selectionInteraction); // use select to make popup selectionInteraction.on('select', container.showPopup); } } else { vectors = container.layers[name] = new ol.layer.Vector({ source: new ol.source.Vector({wrapX: false}), title: name //styleMap: container.defaultStyleMap, //rendererOptions: {zIndexing: true} }); } container.overlays.getLayers().push(vectors); } return container.layers[name]; } return container.vectors; }; container.clearLayer = function (name) { var vectors = container.getLayer(name).getSource(); vectors.getFeatures().forEach(f => { if ((f && f.get("itemId")) || (f && f.get("type") && f.get("object"))) { vectors.removeFeature(f); } }); }; desiredControls = $(this).data('map-controls'); if (desiredControls === undefined) { desiredControls = 'controls,layers,search_location,current_location,streetview,navigation'; } desiredControls = desiredControls.split(','); var setupHeight = function () { var height = $(container).height(); if (0 === height) { height = $(container).width() / 4.0 * 3.0; } $(container).closest('.height-size').each(function () { height = $(this).data('available-height'); $(this).css('padding', 0); $(this).css('margin', 0); }); $(container).height(height); }; setupHeight(); $(window).resize(setupHeight); if (! id) { ++mapNumber; id = 'openlayers' + mapNumber; $(this).attr('id', id); } setTimeout(function () { ol.ImgPath = "lib/openlayers/theme/dark/"; var controls = ol.control.defaults({ // zoom control is added by default, so remove it if not needed zoom: $.inArray('controls', desiredControls) !== -1 }); if ($.inArray('coordinates', desiredControls) !== -1) { controls.push(new ol.control.MousePosition({ projection: "EPSG:4326", coordinateFormat: function (coordinate) { return ol.coordinate.format(coordinate, '{y}, {x}', 4); } }) ); } if ($.inArray('scale', desiredControls) !== -1) { controls.push(new ol.control.ScaleLine()); } if ($.inArray('levels', desiredControls) !== -1) { controls.push(new ol.control.ZoomSlider()); } if (-1 !== $.inArray('layers', desiredControls)) { controls.push(new ol.control.LayerSwitcher()); } /* no navbar, pan or layer switcher anymore? if (layers.length > 0 && -1 !== $.inArray('navigation', desiredControls)) { defaultMode.controls.push(new ol.control.NavToolbar()); } */ // Set up initial layers container.layers = {}; var tilesets = {}, key = "", pos = -1, ts = $(container).data("tilesets") || jqueryTiki.mapTileSets; if (ts) { if (typeof ts === "string") { ts = ts.replace(/\s*/g, "").split(","); } if (typeof ts === "string") { ts = [ts]; } } for (var i = 0; i < ts.length; i++) { pos = ts[i].indexOf("="); if (pos !== -1) { key = ts[i].substr(0, pos); ts[i] = ts[i].substr(pos + 1); } else { var m = ts[i].match(/\W/); if (m) { key = ts[i].substr(0, ts[i].indexOf(m[0])); } else { key = ts[i]; } } if (ts[i].indexOf("~") !== -1) { tilesets[key] = ts[i].split("~"); } else { tilesets[key] = ts[i]; } } errorLayers = []; var layers = [ new ol.layer.Group({ title: tr("Base Maps"), layers: getBaseLayers(tilesets) }), new ol.layer.Group({ title: tr("Overlays") }) ]; var map = container.map = new ol.Map({ target: id, controls: controls, view: new ol.View({ center: [0, 0], zoom: 2 }), layers: layers }); if (errorLayers.length) { $("#tikifeedback").showError(tr("Cannot create map layer/s: " + errorLayers.join(", "))); errorLayers = []; } map.getLayerGroup().getLayers().forEach(function (layer) { if (layer.get("title") === tr("Overlays")) { container.overlays = layer; } }); container.vectors = container.getLayer(tr("Editable")); container.uniqueMarkers = {}; container.resetPosition = function (center) { center = center || [0, 0, 3]; var view = map.getView(); view.setCenter(ol.proj.fromLonLat([center.lon, center.lat])); view.setZoom(center.zoom); }; container.resetPosition(); container.modeManager = { modes: [], activeMode: null, addMode: function (options) { var mode = $.extend({ title: tr('Default'), icon: null, events: { activate: [], deactivate: [] }, controls: [], layers: [] }, options); $.each(mode.layers, function (k, layer) { layer.displayInLayerSwitcher = false; layer.setVisibility(false); map.addLayer(layer); }); $.each(mode.controls, function (k, control) { control.autoActivate = false; map.addControl(control); }); this.modes.push(mode); this.register('activate', mode.name, mode.activate); this.register('deactivate', mode.name, mode.deactivate); if (! this.activeMode) { this.activate(mode); } $(container).trigger('modechanged'); return mode; }, switchTo: function (modeName) { var manager = this; $.each(this.modes, function (k, mode) { if (mode.name === modeName) { manager.activate(mode); } }); }, register: function (eventName, modeName, callback) { $.each(this.modes, function (k, mode) { if (mode.name === modeName && callback) { mode.events[eventName].push(callback); } }); }, activate: function (mode) { if (this.activeMode) { this.deactivate(); } this.activeMode = mode; $.each(mode.controls, function (k, control) { control.activate(); }); $.each(mode.layers, function (k, layer) { layer.setVisibility(true); }); $.each(mode.events.activate, function (k, f) { f.apply([], container); }); $(container).trigger('modechanged'); }, deactivate: function () { if (! this.activeMode) { return; } $.each(this.activeMode.controls, function (k, control) { control.deactivate(); }); $.each(this.activeMode.layers, function (k, layer) { layer.setVisibility(false); }); $.each(this.activeMode.events.deactivate, function (k, f) { f.apply([], container); }); this.activeMode = null; } }; var defaultMode = { controls: [] }; var $mapBootstrapDummy = $("
") .attr("id", "map-tooltip") .css("position", "absolute") .appendTo($(".ol-viewport", container)); /* tooltips */ if ($(container).data("tooltips")) { // based on https://gis.stackexchange.com/a/166745/25953 container.displayFeatureInfo = function (pixel, evt) { $mapBootstrapDummy.tooltip("hide"); var feature, layer, both = map.forEachFeatureAtPixel(pixel, function (feature, layer) { return [feature, layer]; }); if (! both) { return; } feature = both[0]; layer = both[1]; if (! feature) { return; } if (layer && layer instanceof ol.layer.VectorTile) { return; } $mapBootstrapDummy.css({ left: pixel[0] + "px", top: (pixel[1] - 15) + "px" }); var clusterFeatures = feature.get("features"); if (clusterFeatures) { if ($(container).data("clusterhover") === "none") { feature.set("content", ""); for (var i = 0; i < clusterFeatures.length; i++) { feature.set("content", feature.get("content") + clusterFeatures[i].get("content") + "
"); } } else if (clusterFeatures.length === 1) { feature = clusterFeatures[0]; } else if (layer) { var f = layer.getSource().getSource().getClosestFeatureToCoordinate(evt.coordinate); if (f) { feature = f; } } } if (feature && feature.get("content")) { $mapBootstrapDummy .tooltip("dispose") .tooltip({ animation: false, trigger: "manual", html: true, title: feature.get("content"), container: "body" }) .tooltip("show"); } }; map.on('pointermove', function (evt) { if (evt.dragging) { $mapBootstrapDummy.tooltip('hide'); return true; } container.displayFeatureInfo(map.getEventPixel(evt.originalEvent), evt); return true; }); } if ($(container).data("popup-style")) { container.showPopup = function (e) { var pixel, feature, features = e.target.getFeatures(); if (features.getLength()) { feature = features.getArray()[0]; var clusterFeatures = feature.get("features"); if (clusterFeatures) { if (clusterFeatures.length === 1) { feature = clusterFeatures[0]; } else { selectionInteraction.getFeatures().clear(); var extent = ol.extent.createEmpty(); for (var i = 0; i < clusterFeatures.length; ++i) { ol.extent.extend(extent, clusterFeatures[i].getGeometry().getExtent()); } container.map.getView().fit( extent, { duration: 2000, padding: [10, 5, 10, 5] } ); return; } } else { feature = features.getArray()[0]; } } else { return; } pixel = map.getEventPixel(e.mapBrowserEvent.originalEvent); $mapBootstrapDummy.tooltip("dispose").css({ left: pixel[0] + "px", top: pixel[1] + "px" }); let type = feature.get("type"), object = feature.get("object"); if (! type && ! object) { type = "trackeritem", object = feature.get("itemId"); } switch ($(container).data('popup-style')) { case 'dialog': var $modal = $('.modal.fade:not(.show):first') .modal({}) .one('shown.bs.modal', function (event) { if (type && object) { $(container).loadInfoboxPopup({ type: type, object: object, feature: feature, event: event, element: this, callback: function (event, $html) { var title = $html.find("h1").remove().text() || feature.get( "content"), $header = $(".modal-header", $modal); $html.find("a.service-dialog").clickModal({}); if ($header.length === 0) { $(".modal-content", $modal).append( $("