Use GeoSN aerial imagery via own proxy instead of Esri for satellite mode
Esri World Imagery (Maxar) is not freely licensed for commercial projects. Satellite mode now uses the official GeoSN orthophotos (Saxony, 20 cm) through the cache proxy on maps.mummert.media, so visitors never connect to Esri or geodienste.sachsen.de. - raster layer below the first base symbol layer, labels and event markers stay on top; Esri road/label overlays removed - labels recoloured for the aerial view (white text, dark halo), POIs, address labels and oneway arrows hidden; originals restored when switching back - attribution "© GeoSN, dl-de/by-2-0" - bump map-module.js cache buster Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5aeeee0ee5
commit
ff8fa50360
2 files changed
+87
-76
No files matched your search
@@ -85,4 +85,4 @@
|
||||
></div>
|
||||
|
||||
<script type="application/json" id="{{ mapDataElementId|e('html_attr') }}">{{ mapItemsJson|raw }}</script>
|
||||
<script type="module" src="/bundles/eventmanager/assets/map-module.js?v=20260822a"></script>
|
||||
<script type="module" src="/bundles/eventmanager/assets/map-module.js?v=20261104a"></script>
|
||||
+86
-75
@@ -12,16 +12,26 @@ const DEFAULT_EVENT_COLOR = '#BC5067';
|
||||
const EVENT_FADE_DURATION_MS = 200;
|
||||
const EVENT_CLUSTER_LAYER_ID = 'eventmanager-events-clusters';
|
||||
const EVENT_UNCLUSTERED_LAYER_ID = 'eventmanager-events-unclustered';
|
||||
const SATELLITE_SOURCE_ID = 'eventmanager-satellite-source';
|
||||
const SATELLITE_LAYER_ID = 'eventmanager-satellite-layer';
|
||||
const SATELLITE_TILE_URL = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
|
||||
const SATELLITE_LABELS_SOURCE_ID = 'eventmanager-satellite-labels-source';
|
||||
const SATELLITE_LABELS_LAYER_ID = 'eventmanager-satellite-labels-layer';
|
||||
const SATELLITE_LABELS_TILE_URL = 'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}';
|
||||
const SATELLITE_ROADS_SOURCE_ID = 'eventmanager-satellite-roads-source';
|
||||
const SATELLITE_ROADS_LAYER_ID = 'eventmanager-satellite-roads-layer';
|
||||
const SATELLITE_ROADS_TILE_URL = 'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}';
|
||||
const SATELLITE_ATTRIBUTION = '© Esri, Maxar';
|
||||
// Luftbild: amtliche Digitale Orthophotos des GeoSN (20 cm) über den Cache-Proxy auf maps.mummert.media.
|
||||
// Besucher rufen nur maps.mummert.media ab, keine Verbindung zu geodienste.sachsen.de.
|
||||
const SATELLITE_SOURCE_ID = 'eventmanager-luftbild-sn';
|
||||
const SATELLITE_LAYER_ID = 'eventmanager-luftbild-sn';
|
||||
const SATELLITE_TILE_URL = 'https://maps.mummert.media/dienste/dop-sn/{z}/{x}/{y}.webp';
|
||||
const SATELLITE_TILE_SIZE = 512;
|
||||
const SATELLITE_MIN_ZOOM = 7; // darunter lehnt der Proxy ab
|
||||
const SATELLITE_MAX_ZOOM = 18; // native Auflösung, darüber skaliert MapLibre
|
||||
const SATELLITE_BOUNDS = [11.7889, 50.1506, 15.0869, 51.7209]; // Sachsen
|
||||
const SATELLITE_ATTRIBUTION = '© GeoSN, dl-de/by-2-0';
|
||||
// Beschriftung im Luftbild lesbar machen (weiße Schrift, dunkler Rand), Unruhe ausblenden
|
||||
const AERIAL_HIDE_LAYER_IDS = ['pois', 'address_label', 'roads_oneway'];
|
||||
const AERIAL_KEEP_LAYER_IDS = ['roads_shields'];
|
||||
const AERIAL_TEXT_PAINT = {
|
||||
'text-color': '#ffffff',
|
||||
'text-halo-color': 'rgba(0, 0, 0, .78)',
|
||||
'text-halo-width': 1.6,
|
||||
'text-halo-blur': .6,
|
||||
};
|
||||
const AERIAL_WATER_TEXT_PAINT = { ...AERIAL_TEXT_PAINT, 'text-color': '#cfe0ff' };
|
||||
const PROTOMAPS_SOURCE_ID = 'protomaps';
|
||||
const DEFAULT_BASE_STYLE_URL = 'https://maps.mummert.media/metadaten/world-light.json';
|
||||
const PROTOMAPS_PM_TILES_URL = 'pmtiles://https://maps.mummert.media/tiles/world.pmtiles';
|
||||
@@ -392,6 +402,9 @@ const applyDefaultProjection = (map) => {
|
||||
}
|
||||
};
|
||||
|
||||
const isBaseSymbolLayer = (layer) => 'symbol' === layer?.type
|
||||
&& layer.source === PROTOMAPS_SOURCE_ID;
|
||||
|
||||
const ensureSatelliteLayer = (map) => {
|
||||
if (!map || map.getSource(SATELLITE_SOURCE_ID)) {
|
||||
return;
|
||||
@@ -400,15 +413,17 @@ const ensureSatelliteLayer = (map) => {
|
||||
map.addSource(SATELLITE_SOURCE_ID, {
|
||||
type: 'raster',
|
||||
tiles: [SATELLITE_TILE_URL],
|
||||
tileSize: 256,
|
||||
tileSize: SATELLITE_TILE_SIZE,
|
||||
minzoom: SATELLITE_MIN_ZOOM,
|
||||
maxzoom: SATELLITE_MAX_ZOOM,
|
||||
bounds: SATELLITE_BOUNDS,
|
||||
attribution: SATELLITE_ATTRIBUTION,
|
||||
});
|
||||
|
||||
const markerLayerCandidates = [
|
||||
EVENT_CLUSTER_LAYER_ID,
|
||||
EVENT_UNCLUSTERED_LAYER_ID,
|
||||
];
|
||||
const beforeId = markerLayerCandidates.find((layerId) => map.getLayer(layerId));
|
||||
// Unter die erste Beschriftungsebene der Grundkarte: Orts- und Straßennamen
|
||||
// sowie die Veranstaltungs-Marker bleiben über dem Luftbild.
|
||||
const firstSymbolLayer = (map.getStyle()?.layers || []).find(isBaseSymbolLayer)
|
||||
|| (map.getStyle()?.layers || []).find((layer) => 'symbol' === layer.type);
|
||||
|
||||
map.addLayer({
|
||||
id: SATELLITE_LAYER_ID,
|
||||
@@ -418,51 +433,57 @@ const ensureSatelliteLayer = (map) => {
|
||||
visibility: 'none',
|
||||
},
|
||||
paint: {
|
||||
'raster-opacity': 1,
|
||||
'raster-brightness-min': 0,
|
||||
'raster-brightness-max': 1,
|
||||
'raster-contrast': 0,
|
||||
'raster-saturation': 0,
|
||||
'raster-opacity': 0,
|
||||
'raster-opacity-transition': { duration: EVENT_FADE_DURATION_MS, delay: 0 },
|
||||
},
|
||||
}, beforeId);
|
||||
}, firstSymbolLayer?.id);
|
||||
};
|
||||
|
||||
map.addSource(SATELLITE_ROADS_SOURCE_ID, {
|
||||
type: 'raster',
|
||||
tiles: [SATELLITE_ROADS_TILE_URL],
|
||||
tileSize: 256,
|
||||
attribution: SATELLITE_ATTRIBUTION,
|
||||
const aerialLabelBackups = new WeakMap();
|
||||
|
||||
const setAerialLabels = (map, enabled) => {
|
||||
const layers = (map.getStyle()?.layers || []).filter(isBaseSymbolLayer);
|
||||
|
||||
if (!aerialLabelBackups.has(map)) {
|
||||
aerialLabelBackups.set(map, new Map());
|
||||
}
|
||||
|
||||
const backups = aerialLabelBackups.get(map);
|
||||
|
||||
layers.forEach((layer) => {
|
||||
const layerId = layer.id;
|
||||
|
||||
if (!map.getLayer(layerId) || AERIAL_KEEP_LAYER_IDS.includes(layerId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!backups.has(layerId)) {
|
||||
const paint = {};
|
||||
|
||||
Object.keys(AERIAL_TEXT_PAINT).forEach((property) => {
|
||||
paint[property] = map.getPaintProperty(layerId, property);
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: SATELLITE_ROADS_LAYER_ID,
|
||||
type: 'raster',
|
||||
source: SATELLITE_ROADS_SOURCE_ID,
|
||||
layout: {
|
||||
visibility: 'none',
|
||||
},
|
||||
paint: {
|
||||
'raster-opacity': 1,
|
||||
},
|
||||
}, beforeId);
|
||||
|
||||
map.addSource(SATELLITE_LABELS_SOURCE_ID, {
|
||||
type: 'raster',
|
||||
tiles: [SATELLITE_LABELS_TILE_URL],
|
||||
tileSize: 256,
|
||||
attribution: SATELLITE_ATTRIBUTION,
|
||||
backups.set(layerId, {
|
||||
visibility: map.getLayoutProperty(layerId, 'visibility') || 'visible',
|
||||
paint,
|
||||
});
|
||||
}
|
||||
|
||||
map.addLayer({
|
||||
id: SATELLITE_LABELS_LAYER_ID,
|
||||
type: 'raster',
|
||||
source: SATELLITE_LABELS_SOURCE_ID,
|
||||
layout: {
|
||||
visibility: 'none',
|
||||
},
|
||||
paint: {
|
||||
'raster-opacity': 1,
|
||||
},
|
||||
}, beforeId);
|
||||
const backup = backups.get(layerId);
|
||||
|
||||
if (AERIAL_HIDE_LAYER_IDS.includes(layerId)) {
|
||||
map.setLayoutProperty(layerId, 'visibility', enabled ? 'none' : backup.visibility);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const aerialPaint = layerId.startsWith('water') ? AERIAL_WATER_TEXT_PAINT : AERIAL_TEXT_PAINT;
|
||||
|
||||
Object.keys(aerialPaint).forEach((property) => {
|
||||
map.setPaintProperty(layerId, property, enabled ? aerialPaint[property] : backup.paint[property]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const setSatelliteMode = (map, enabled) => {
|
||||
@@ -476,30 +497,20 @@ const setSatelliteMode = (map, enabled) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const hybridOverlayLayerIds = [SATELLITE_ROADS_LAYER_ID, SATELLITE_LABELS_LAYER_ID];
|
||||
|
||||
map.setLayoutProperty(SATELLITE_LAYER_ID, 'visibility', enabled ? 'visible' : 'none');
|
||||
hybridOverlayLayerIds.forEach((layerId) => {
|
||||
if (map.getLayer(layerId)) {
|
||||
map.setLayoutProperty(layerId, 'visibility', enabled ? 'visible' : 'none');
|
||||
map.setPaintProperty(layerId, 'raster-opacity-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||
map.setPaintProperty(layerId, 'raster-opacity', enabled ? 1 : 0);
|
||||
if (enabled) {
|
||||
map.setLayoutProperty(SATELLITE_LAYER_ID, 'visibility', 'visible');
|
||||
}
|
||||
});
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-opacity-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-brightness-min-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-brightness-max-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-contrast-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-saturation-transition', { duration: EVENT_FADE_DURATION_MS, delay: 0 });
|
||||
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-opacity', enabled ? 1 : 0);
|
||||
setAerialLabels(map, enabled);
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
window.setTimeout(() => {
|
||||
if (map.getLayer(SATELLITE_LAYER_ID) && 0 === map.getPaintProperty(SATELLITE_LAYER_ID, 'raster-opacity')) {
|
||||
map.setLayoutProperty(SATELLITE_LAYER_ID, 'visibility', 'none');
|
||||
}
|
||||
}, EVENT_FADE_DURATION_MS);
|
||||
}
|
||||
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-brightness-min', 0);
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-brightness-max', 1);
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-contrast', 0);
|
||||
map.setPaintProperty(SATELLITE_LAYER_ID, 'raster-saturation', 0);
|
||||
};
|
||||
|
||||
const normalizeTagIds = (value) => {
|
||||
|
||||
Reference in new issue
Block a user