Make default layers customizable

This commit is contained in:
Norbert Renner 2019-03-22 19:43:01 +01:00
parent e02be4ca15
commit 944ccd6cfa
6 changed files with 319 additions and 210 deletions

239
js/LayersConfig.js Normal file
View file

@ -0,0 +1,239 @@
BR.LayersConfig = L.Class.extend({
defaultBaseLayers: [
'standard',
'osm-mapnik-german_style',
'OpenTopoMap',
'Stamen.Terrain',
'Esri.WorldImagery'
],
defaultOverlays: [
'HikeBike.HillShading',
'Waymarked_Trails-Cycling',
'Waymarked_Trails-Hiking'
],
initialize: function (map) {
this._map = map;
this._addLeafletProvidersLayers();
this._customizeLayers();
},
_addLeafletProvidersLayers: function () {
var includeList = [
'Stamen.Terrain',
'MtbMap',
'OpenStreetMap.CH',
'HikeBike.HillShading',
'Esri.WorldImagery'
];
for (var i = 0; i < includeList.length; i++) {
var id = includeList[i];
var obj = {
geometry: null,
properties: {
id: id,
name: id.replace('.', ' '),
dataSource: 'leaflet-providers'
},
type: "Feature"
};
BR.layerIndex[id] = obj;
}
},
_customizeLayers: function () {
// add Thunderforest API key variable
BR.layerIndex['opencylemap'].properties.url = 'https://{switch:a,b,c}.tile.thunderforest.com/cycle/{zoom}/{x}/{y}.png?apikey={keys_thunderforest}';
BR.layerIndex['1061'].properties.url = 'http://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey={keys_thunderforest}';
BR.layerIndex['HikeBike.HillShading'].properties.overlay = true;
function setProperty(layerId, key, value) {
var layer = BR.layerIndex[layerId];
if (layer) {
layer.properties[key] = value;
} else {
console.error('Layer not found: ' + layerId);
}
}
function setMapUrl(layerId, url) {
setProperty(layerId, 'mapUrl', url);
}
function setName(layerId, url) {
setProperty(layerId, 'name', url);
}
// Layer attribution here only as short link to original site,
// to keep current position use placeholders: {zoom}/{lat}/{lon}
// Copyright attribution in index.html #credits
setMapUrl('standard', '<a target="_blank" href="https://www.openstreetmap.org/#map={zoom}/{lat}/{lon}">OpenStreetMap</a>');
setMapUrl('osm-mapnik-german_style', '<a target="_blank" href="https://www.openstreetmap.de/karte.html?zoom={zoom}&lat={lat}&lon={lon}&layers=B000TF">OpenStreetMap.de</a>');
setMapUrl('OpenTopoMap', '<a target="_blank" href="https://opentopomap.org/#map={zoom}/{lat}/{lon}">OpenTopoMap</a>');
setMapUrl('Stamen.Terrain', '<a target="_blank" href="http://maps.stamen.com/#terrain/{zoom}/{lat}/{lon}">' + i18next.t('map.layer.stamen-terrain') + '</a>');
setMapUrl('opencylemap', '<a target="_blank" href="https://www.opencyclemap.org/?zoom={zoom}&lat={lat}&lon={lon}&layers=B0000">OpenCycleMap</a>');
setMapUrl('1061', '<a target="_blank" href="https://www.opencyclemap.org/?zoom={zoom}&lat={lat}&lon={lon}&layers=000B0">Outdoors</a>');
setMapUrl('Esri.WorldImagery', '<a target="_blank" href="http://www.arcgis.com/home/item.html?id=10df2279f9684e4a9f6a7f08febac2a9">' + i18next.t('credits.esri-tiles') + '</a>');
setMapUrl('HikeBike.HillShading', '<a target="_blank" href="http://hikebikemap.org/?zoom={zoom}&lat={lat}&lon={lon}&layer=HikeBikeMap">' + i18next.t('map.hikebike-hillshading') + '</a>');
setMapUrl('Waymarked_Trails-Cycling', '<a target="_blank" href="http://cycling.waymarkedtrails.org/#?map={zoom}!{lat}!{lon}">' + i18next.t('map.cycling') + '</a>');
setMapUrl('Waymarked_Trails-Hiking', '<a target="_blank" href="http://hiking.waymarkedtrails.org/#?map={zoom}!{lat}!{lon}">' + i18next.t('map.hiking') + '</a>');
setName('standard', i18next.t('map.layer.osm'));
setName('osm-mapnik-german_style', i18next.t('map.layer.osmde'));
setName('OpenTopoMap', i18next.t('map.layer.topo'));
setName('Stamen.Terrain', i18next.t('map.layer.stamen-terrain'));
setName('opencylemap', i18next.t('map.layer.cycle'));
setName('1061', i18next.t('map.layer.outdoors'));
setName('Esri.WorldImagery', i18next.t('map.layer.esri'));
setName('HikeBike.HillShading', i18next.t('map.layer.hikebike-hillshading'));
setName('Waymarked_Trails-Cycling', i18next.t('map.layer.cycling'));
setName('Waymarked_Trails-Hiking', i18next.t('map.layer.hiking'));
},
isDefaultLayer: function(id, overlay) {
var result = false;
if (overlay) {
result = this.defaultOverlays.indexOf(id) > -1;
} else {
result = this.defaultBaseLayers.indexOf(id) > -1;
}
return result;
},
getBaseLayers: function() {
return this._getLayers(this.defaultBaseLayers);
},
getOverlays: function() {
return this._getLayers(this.defaultOverlays);
},
_getLayers: function(ids) {
var layers = {};
for (var i = 0; i < ids.length; i++) {
var layerId = ids[i];
var layerData = BR.layerIndex[layerId];
if (layerData) {
layers[layerData.properties.name] = this.createLayer(layerData);
} else {
console.error('Layer not found: ' + layerId);
}
}
return layers;
},
// own convention: key placeholder with prefix
// e.g. ?api_key={keys_openrouteservice}
getKeyName: function (url) {
var result = null;
// L.Util.template only matches [\w_-]
var prefix = 'keys_';
var regex = new RegExp('{' + prefix + '([^}]*)}');
var found, name;
if (!url) return result;
found = url.match(regex);
if (found) {
name = found[1];
result = {
name: name,
urlVar: prefix + name
};
}
return result;
},
createLayer: function (layerData) {
var props = layerData.properties;
var url = props.url;
var layer;
// JOSM: https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png
// Leaflet: https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
function convertUrlJosm(url) {
var rxSwitch = /{switch:[^}]*}/;
var rxZoom = /{zoom}/g;
var result = url.replace(rxSwitch, '{s}');
result = result.replace(rxZoom, '{z}');
return result;
}
// JOSM: https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png
// Leaflet: ['a','b','c']
function getSubdomains(url) {
var result = 'abc';
var regex = /{switch:([^}]*)}/;
var found = url.match(regex);
if (found) {
result = found[1].split(',');
}
return result;
}
var options = {
maxZoom: this._map.getMaxZoom(),
mapUrl: props.mapUrl
};
var keyObj = this.getKeyName(url);
if (keyObj && BR.keys[keyObj.name]) {
options[keyObj.urlVar] = BR.keys[keyObj.name];
}
if (props.dataSource === 'leaflet-providers') {
layer = L.tileLayer.provider(props.id);
var layerOptions = L.Util.extend(options, {
maxNativeZoom: layer.options.maxZoom,
});
L.setOptions(layer, layerOptions);
} else if (props.dataSource === 'LayersCollection') {
layer = L.tileLayer(url, L.Util.extend(options, {
minZoom: props.minZoom,
maxNativeZoom: props.maxZoom,
}));
if (props.subdomains) {
layer.subdomains = props.subdomains;
}
} else {
// JOSM
var url = convertUrlJosm(url);
var josmOptions = L.Util.extend(options, {
minZoom: props.min_zoom,
maxNativeZoom: props.max_zoom,
subdomains: getSubdomains(url),
});
if (props.type && props.type === 'wms') {
layer = L.tileLayer.wms(url, L.Util.extend(josmOptions, {
layers: props.layers,
format: props.format
}));
} else {
layer = L.tileLayer(url, josmOptions);
}
}
var getAttribution = function () {
return this.options.mapUrl;
}
layer.getAttribution = getAttribution;
return layer;
}
});
BR.layersConfig = function (map) {
return new BR.LayersConfig(map);
};

View file

@ -8,59 +8,6 @@ BR.Map = {
var maxZoom = 19;
// Layer attribution here only as short link to original site,
// to keep current position use placeholders: {zoom}/{lat}/{lon}
// Copyright attribution in index.html #credits
var osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: maxZoom,
attribution: '<a target="_blank" href="https://www.openstreetmap.org/#map={zoom}/{lat}/{lon}">OpenStreetMap</a>'
});
var osmde = L.tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', {
maxNativeZoom: 19,
maxZoom: maxZoom,
attribution: '<a target="_blank" href="https://www.openstreetmap.de/karte.html?zoom={zoom}&lat={lat}&lon={lon}&layers=B000TF">OpenStreetMap.de</a>'
});
var topo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxNativeZoom: 17,
maxZoom: maxZoom,
attribution: '<a target="_blank" href="https://opentopomap.org/#map={zoom}/{lat}/{lon}">OpenTopoMap</a>'
});
var thunderforestAuth = BR.keys.thunderforest ? '?apikey=' + BR.keys.thunderforest : '';
var cycle = L.tileLayer('https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png' + thunderforestAuth, {
maxNativeZoom: 18,
maxZoom: maxZoom,
attribution: '<a target="_blank" href="https://www.opencyclemap.org/?zoom={zoom}&lat={lat}&lon={lon}&layers=B0000">OpenCycleMap</a>'
});
var outdoors = L.tileLayer('https://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png' + thunderforestAuth, {
maxNativeZoom: 18,
maxZoom: maxZoom,
attribution: '<a target="_blank" href="https://www.opencyclemap.org/?zoom={zoom}&lat={lat}&lon={lon}&layers=000B0">Outdoors</a>'
});
var esri = L.tileLayer('https://{s}.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
maxNativeZoom: 19,
maxZoom: maxZoom,
subdomains: ['server', 'services'],
attribution: '<a target="_blank" href="http://www.arcgis.com/home/item.html?id=10df2279f9684e4a9f6a7f08febac2a9">' + i18next.t('credits.esri-tiles') + '</a>'
});
var cycling = L.tileLayer('https://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png', {
maxNativeZoom: 18,
opacity: 0.7,
maxZoom: maxZoom,
attribution: '<a target="_blank" href="http://cycling.waymarkedtrails.org/#?map={zoom}!{lat}!{lon}">' + i18next.t('map.cycling') + '</a>'
});
var hiking = L.tileLayer('https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png', {
maxNativeZoom: 18,
opacity: 0.7,
maxZoom: maxZoom,
attribution: '<a target="_blank" href="http://hiking.waymarkedtrails.org/#?map={zoom}!{lat}!{lon}">' + i18next.t('map.hiking') + '</a>'
});
map = new L.Map('map', {
zoomControl: false, // add it manually so that we can translate it
worldCopyJump: true,
@ -85,16 +32,9 @@ BR.Map = {
new L.Control.PermalinkAttribution().addTo(map);
map.attributionControl.setPrefix(false);
var baseLayers = {}
baseLayers[i18next.t('map.layer.osm')] = osm;
baseLayers[i18next.t('map.layer.osmde')] = osmde;
baseLayers[i18next.t('map.layer.topo')] = topo;
baseLayers[i18next.t('map.layer.cycle')] = cycle;
baseLayers[i18next.t('map.layer.outdoors')] = outdoors;
baseLayers[i18next.t('map.layer.esri')] = esri;
var overlays = {}
overlays[i18next.t('map.layer.cycling')] = cycling;
overlays[i18next.t('map.layer.hiking')] = hiking;
var layersConfig = BR.layersConfig(map);
var baseLayers = layersConfig.getBaseLayers();
var overlays = layersConfig.getOverlays();
if (BR.keys.bing) {
baseLayers[i18next.t('map.layer.bing')] = new BR.BingLayer(BR.keys.bing);
@ -129,7 +69,7 @@ BR.Map = {
map.addLayer(defaultLayer);
}
layersControl = BR.layersTab(baseLayers, overlays).addTo(map);
layersControl = BR.layersTab(layersConfig, baseLayers, overlays).addTo(map);
var secureContext = 'isSecureContext' in window ? isSecureContext : location.protocol === 'https:';
if (secureContext) {

View file

@ -2,6 +2,12 @@ BR.LayersTab = L.Control.Layers.extend({
previewLayer: null,
saveLayers: [],
initialize: function (layersConfig, baseLayers, overlays, options) {
L.Control.Layers.prototype.initialize.call(this, baseLayers, overlays, options);
this.layersConfig = layersConfig;
},
addTo: function (map) {
this._map = map;
this.onAdd(map);
@ -12,33 +18,32 @@ BR.LayersTab = L.Control.Layers.extend({
this.initButtons();
this.addLeafletProvidersLayers();
var structure = {
'Base layers': {
'Worldwide international': [
'standard',
'OpenTopoMap',
'Stamen.Terrain',
'HDM_HOT',
'Esri.WorldImagery',
'wikimedia-map',
'HDM_HOT',
'1010', // OpenStreetMap.se (Hydda.Full)
'opencylemap',
"1061", // Thunderforest Outdoors
"1065", // Hike & Bike Map
"1016", // 4UMaps,
"openmapsurfer"
'1061', // Thunderforest Outdoors
'1065', // Hike & Bike Map
'1016', // 4UMaps,
'openmapsurfer'
],
'Worldwide monolingual': [
'osm-mapnik-german_style',
'osmfr',
"1023", // Osmapa.pl - Mapa OpenStreetMap Polska
"1021", // kosmosnimki.ru
"1017", // sputnik.ru
"1010" // OpenStreetMap.se (Hydda.Full)
'1023', // Osmapa.pl - Mapa OpenStreetMap Polska
'1021', // kosmosnimki.ru
'1017' // sputnik.ru
],
'Europe': [
'MtbMap',
"1069", // MRI (maps.refuges.info)
'1069' // MRI (maps.refuges.info)
],
'Country': [
'topplus-open',
@ -76,7 +81,7 @@ BR.LayersTab = L.Control.Layers.extend({
'mapaszlakow-bike',
'mapaszlakow-hike',
'mapaszlakow-mtb',
'mapaszlakow-incline',
'mapaszlakow-incline'
]
}
]
@ -119,13 +124,14 @@ BR.LayersTab = L.Control.Layers.extend({
var obj = this._getLayerObjByName(data.node.text);
if (!obj) return;
this.removeLayer(obj.layer);
if (this._map.hasLayer(obj.layer)) {
this._map.removeLayer(obj.layer);
if (!obj.overlay) {
this.addFirstLayer();
}
}
this.removeLayer(obj.layer);
};
$('#optional-layers-tree')
@ -188,21 +194,45 @@ BR.LayersTab = L.Control.Layers.extend({
var data = [];
var self = this;
function createRootNode(name) {
var children = [];
var rootNode = {
'text': name,
'state': {
'disabled': true
},
'children': children
};
return rootNode;
}
function createNode(id, layerData) {
var props = layerData.properties;
var url = props.url;
var keyObj = self.layersConfig.getKeyName(url);
var childNode = null;
// when key required only add if configured
if (!keyObj || keyObj && BR.keys[keyObj.name]) {
childNode = {
'id': id,
'text': props.name,
'state': {
'checked': self.layersConfig.isDefaultLayer(id, props.overlay)
}
};
}
return childNode;
}
function walkTree(inTree, outTree) {
function walkObject(obj) {
for (name in obj) {
var value = obj[name];
var children = [];
var rootNode = {
'text': name,
'state': {
'disabled': true
},
'children': children
};
outTree.push(rootNode);
var rootNode = createRootNode(name)
walkTree(value, children);
outTree.push(rootNode);
walkTree(value, rootNode.children);
}
}
@ -215,16 +245,8 @@ BR.LayersTab = L.Control.Layers.extend({
var layer = BR.layerIndex[entry];
if (layer) {
var props = layer.properties;
var url = props.url;
var keyName = self.getKeyName(url);
// when key required only add if configured
if (!keyName || keyName && BR.keys[keyName]) {
var childNode = {
'id': entry,
'text': props.name
};
var childNode = createNode(entry, layer);
if (childNode) {
outTree.push(childNode);
}
} else {
@ -241,51 +263,13 @@ BR.LayersTab = L.Control.Layers.extend({
return data;
},
addLeafletProvidersLayers: function () {
var includeList = [
'Stamen.Terrain',
'MtbMap',
'OpenStreetMap.CH',
'HikeBike.HillShading'
];
for (var i = 0; i < includeList.length; i++) {
var id = includeList[i];
var obj = {
geometry: null,
properties: {
id: id,
name: id.replace('.', ' '),
dataSource: 'leaflet-providers'
},
type: "Feature"
};
BR.layerIndex[id] = obj;
}
BR.layerIndex['HikeBike.HillShading'].properties.overlay = true;
},
// own convention: key placeholder prefixed with 'key_'
// e.g. ?api_key={keys_openrouteservice}
getKeyName: function (url) {
var name = null;
var regex = /{keys_([^}]*)}/;
var found;
if (!url) return name;
found = url.match(regex);
if (found) {
name = found[1];
}
return name;
},
addFirstLayer() {
if (this._layers.length > 0) {
this._map.addLayer(this._layers[0].layer);
addFirstLayer: function () {
for (var i = 0; i < this._layers.length; i++) {
var obj = this._layers[i];
if (!obj.overlay) {
this._map.addLayer(obj.layer);
break;
}
}
},
@ -298,73 +282,9 @@ BR.LayersTab = L.Control.Layers.extend({
},
createLayer: function (layerData) {
var props = layerData.properties;
var url = props.url;
var layer;
// JOSM: https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png
// Leaflet: https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
function convertUrlJosm(url) {
var rxSwitch = /{switch:[^}]*}/;
var rxZoom = /{zoom}/g;
var result = url.replace(rxSwitch, '{s}');
result = result.replace(rxZoom, '{z}');
return result;
}
// JOSM: https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png
// Leaflet: ['a','b','c']
function getSubdomains(url) {
var result = 'abc';
var regex = /{switch:([^}]*)}/;
var found = url.match(regex);
if (found) {
result = found[1].split(',');
}
return result;
}
var options = {
maxZoom: this._map.getMaxZoom(),
zIndex: this._lastZIndex + 1
};
var keyName = this.getKeyName(url);
if (keyName && BR.keys[keyName]) {
options['keys_' + keyName] = BR.keys[keyName];
}
if (props.dataSource === 'leaflet-providers') {
layer = L.tileLayer.provider(props.id);
} else if (props.dataSource === 'LayersCollection') {
layer = L.tileLayer(url, L.Util.extend(options, {
minZoom: props.minZoom,
maxNativeZoom: props.maxZoom,
}));
if (props.subdomains) {
layer.subdomains = props.subdomains;
}
} else {
// JOSM
var url = convertUrlJosm(url);
var josmOptions = L.Util.extend(options, {
minZoom: props.min_zoom,
maxNativeZoom: props.max_zoom,
subdomains: getSubdomains(url),
});
if (props.type && props.type === 'wms') {
layer = L.tileLayer.wms(url, L.Util.extend(josmOptions, {
layers: props.layers,
format: props.format
}));
} else {
layer = L.tileLayer(url, josmOptions);
}
}
return layer
var layer = this.layersConfig.createLayer(layerData);
layer.options.zIndex = this._lastZIndex + 1;
return layer;
},
removeSelectedLayers: function () {