refactor: implement strict map layer stacking order for intersection and cross plan visualizations

This commit is contained in:
kdeterme 2026-09-09 23:08:36 +02:00
parent 1e7f8123de
commit 4424c2d9c4
3 changed files with 243 additions and 5 deletions

View file

@ -42,6 +42,104 @@ class CustomPlansControl {
} }
map.addControl(new CustomPlansControl(), 'top-right'); map.addControl(new CustomPlansControl(), 'top-right');
// ── Helper to determine where to place the orthophoto layer ──
function getOrthoBeforeLayer(m) {
if (!m || !m.getStyle) return undefined;
const style = m.getStyle();
const styleLayers = (style && style.layers) || [];
// 1. Bottom-most plan layer: first georef image layer
const imageLayer = styleLayers.find(l => l.id && l.id.startsWith('georef-image-overlay-layer-'));
if (imageLayer) return imageLayer.id;
// 2. Or first CAD layer
if (m.getLayer('dxf-layout-fills')) return 'dxf-layout-fills';
if (m.getLayer('dxf-layout-lines')) return 'dxf-layout-lines';
if (m.getLayer('dxf-layout-symbols')) return 'dxf-layout-symbols';
// 3. Or traffic light / marker layer
if (m.getLayer('traffic-light-outline')) return 'traffic-light-outline';
if (m.getLayer('traffic-light')) return 'traffic-light';
return undefined;
}
// ── Ensure strict layer stacking order ──
// 1. Base vector tiles
// 2. Orthophotos (layer-switcher-ortho)
// 3. Georeferenced image plans (georef-image-overlay-layer-*)
// 4. CAD / DXF layers (fills -> lines -> symbols)
// 5. Traffic light marker (traffic-light-outline -> traffic-light)
// 6. Assets (cables, poles, radars, cabinets, equipment)
function reorderIntersectionLayers() {
if (!map || typeof map.isStyleLoaded !== 'function' || !map.isStyleLoaded()) return;
const markerBottomId = map.getLayer('traffic-light-outline')
? 'traffic-light-outline'
: (map.getLayer('traffic-light') ? 'traffic-light' : undefined);
const style = map.getStyle();
const styleLayers = (style && style.layers) || [];
const imageLayerIds = styleLayers
.filter(l => l.id && l.id.startsWith('georef-image-overlay-layer-'))
.map(l => l.id);
const firstCadId = map.getLayer('dxf-layout-fills')
? 'dxf-layout-fills'
: (map.getLayer('dxf-layout-lines') ? 'dxf-layout-lines' : (map.getLayer('dxf-layout-symbols') ? 'dxf-layout-symbols' : undefined));
const firstPlanId = (imageLayerIds.length > 0 ? imageLayerIds[0] : null) || firstCadId;
// 1. Move orthophoto under any plan layer (or under marker)
const orthoBeforeId = firstPlanId || markerBottomId;
if (map.getLayer('layer-switcher-ortho') && orthoBeforeId && map.getLayer(orthoBeforeId)) {
try {
map.moveLayer('layer-switcher-ortho', orthoBeforeId);
} catch (e) {
console.warn("Could not move layer-switcher-ortho:", e);
}
}
// 2. Georeferenced image layers under CAD layers (or under marker)
const imageBeforeId = firstCadId || markerBottomId;
if (imageBeforeId && map.getLayer(imageBeforeId)) {
imageLayerIds.forEach(imgId => {
if (map.getLayer(imgId)) {
try {
map.moveLayer(imgId, imageBeforeId);
} catch (e) {
console.warn("Could not move image overlay layer:", e);
}
}
});
}
// 3. DXF CAD layers: fills -> lines -> symbols, before markerBottomId
if (markerBottomId && map.getLayer(markerBottomId)) {
['dxf-layout-fills', 'dxf-layout-lines', 'dxf-layout-symbols'].forEach(cadId => {
if (map.getLayer(cadId)) {
try {
map.moveLayer(cadId, markerBottomId);
} catch (e) {
console.warn("Could not move CAD layer:", e);
}
}
});
}
// 4. Ensure assets stay on top of the intersection marker and plans
const assetLayerIds = ['assets-cables', 'assets-poles', 'assets-radars', 'assets-cabinets', 'assets-equipment'];
assetLayerIds.forEach(assetId => {
if (map.getLayer(assetId)) {
try {
map.moveLayer(assetId);
} catch (e) {
// ignore
}
}
});
}
// --- Contrôle photo aérienne (LayerSwitcherControl) --- (second, below Plans button) // --- Contrôle photo aérienne (LayerSwitcherControl) --- (second, below Plans button)
if (typeof LayerSwitcherControl !== 'undefined') { if (typeof LayerSwitcherControl !== 'undefined') {
const layerSwitcher = new LayerSwitcherControl({ const layerSwitcher = new LayerSwitcherControl({
@ -51,7 +149,11 @@ if (typeof LayerSwitcherControl !== 'undefined') {
name: 'Orthophotos', name: 'Orthophotos',
icon: 'satellite', icon: 'satellite',
group: 'ortho', group: 'ortho',
beforeLayer: 'traffic-light', beforeLayer: getOrthoBeforeLayer,
onLayerAdded: () => reorderIntersectionLayers(),
onVisibilityChange: (layerId, isVisible) => {
if (isVisible) reorderIntersectionLayers();
},
layer: { layer: {
type: 'raster', type: 'raster',
paint: { 'raster-opacity': 1 } paint: { 'raster-opacity': 1 }
@ -440,11 +542,14 @@ map.on('load', () => {
}); });
}); });
reorderIntersectionLayers();
}).catch(e => { }).catch(e => {
console.error('Failed to load asset icons:', e); console.error('Failed to load asset icons:', e);
}); });
} }
reorderIntersectionLayers();
}); });
@ -704,7 +809,9 @@ function ensureDxfSourcesAndLayers() {
data: { type: "FeatureCollection", features: [] } data: { type: "FeatureCollection", features: [] }
}); });
const beforeId = map.getLayer('traffic-light') ? 'traffic-light' : undefined; const beforeId = map.getLayer('traffic-light-outline')
? 'traffic-light-outline'
: (map.getLayer('traffic-light') ? 'traffic-light' : undefined);
map.addLayer({ map.addLayer({
'id': 'dxf-layout-fills', 'id': 'dxf-layout-fills',
@ -762,6 +869,7 @@ function ensureDxfSourcesAndLayers() {
} }
}, beforeId); }, beforeId);
} }
reorderIntersectionLayers();
} }
// ── Georeferenced Plans & DXF Background Loader ── // ── Georeferenced Plans & DXF Background Loader ──
@ -792,6 +900,7 @@ function showGeorefImage(doc, active, opacity) {
if (map.getLayer(lyrId)) { if (map.getLayer(lyrId)) {
map.setLayoutProperty(lyrId, 'visibility', 'visible'); map.setLayoutProperty(lyrId, 'visibility', 'visible');
map.setPaintProperty(lyrId, 'raster-opacity', opacity); map.setPaintProperty(lyrId, 'raster-opacity', opacity);
reorderIntersectionLayers();
} }
} else { } else {
map.addSource(srcId, { map.addSource(srcId, {
@ -805,6 +914,8 @@ function showGeorefImage(doc, active, opacity) {
beforeId = 'dxf-layout-fills'; beforeId = 'dxf-layout-fills';
} else if (map.getLayer('dxf-layout-lines')) { } else if (map.getLayer('dxf-layout-lines')) {
beforeId = 'dxf-layout-lines'; beforeId = 'dxf-layout-lines';
} else if (map.getLayer('traffic-light-outline')) {
beforeId = 'traffic-light-outline';
} else if (map.getLayer('traffic-light')) { } else if (map.getLayer('traffic-light')) {
beforeId = 'traffic-light'; beforeId = 'traffic-light';
} }
@ -816,6 +927,8 @@ function showGeorefImage(doc, active, opacity) {
'layout': { 'visibility': 'visible' }, 'layout': { 'visibility': 'visible' },
'paint': { 'raster-opacity': opacity } 'paint': { 'raster-opacity': opacity }
}, beforeId); }, beforeId);
reorderIntersectionLayers();
} }
// Zoom/fitBounds to document // Zoom/fitBounds to document
@ -873,6 +986,7 @@ function applyDxfVectorFilters() {
map.setLayoutProperty(lId, 'visibility', 'visible'); map.setLayoutProperty(lId, 'visibility', 'visible');
} }
}); });
reorderIntersectionLayers();
} }
} }

View file

@ -1195,6 +1195,83 @@ document.addEventListener('DOMContentLoaded', () => {
} }
map.addControl(new CustomPlansControl(), 'top-right'); map.addControl(new CustomPlansControl(), 'top-right');
// ── Helper to determine where to place the orthophoto layer ──
function getOrthoBeforeLayer(m) {
if (!m || !m.getStyle) return undefined;
const style = m.getStyle();
const styleLayers = (style && style.layers) || [];
// 1. Bottom-most plan layer: first georef image layer
const imageLayer = styleLayers.find(l => l.id && l.id.startsWith('georef-image-overlay-layer-'));
if (imageLayer) return imageLayer.id;
// 2. Or first CAD layer
if (m.getLayer('dxf-layout-fills')) return 'dxf-layout-fills';
if (m.getLayer('dxf-layout-lines')) return 'dxf-layout-lines';
if (m.getLayer('dxf-layout-symbols')) return 'dxf-layout-symbols';
// 3. Or existing poles layer
if (m.getLayer('existing-poles-layer')) return 'existing-poles-layer';
return undefined;
}
// ── Ensure strict layer stacking order for cross plan import ──
function reorderCrossPlanLayers() {
if (!map || typeof map.isStyleLoaded !== 'function' || !map.isStyleLoaded()) return;
const markerBottomId = map.getLayer('existing-poles-layer') ? 'existing-poles-layer' : undefined;
const style = map.getStyle();
const styleLayers = (style && style.layers) || [];
const imageLayerIds = styleLayers
.filter(l => l.id && l.id.startsWith('georef-image-overlay-layer-'))
.map(l => l.id);
const firstCadId = map.getLayer('dxf-layout-fills')
? 'dxf-layout-fills'
: (map.getLayer('dxf-layout-lines') ? 'dxf-layout-lines' : (map.getLayer('dxf-layout-symbols') ? 'dxf-layout-symbols' : undefined));
const firstPlanId = (imageLayerIds.length > 0 ? imageLayerIds[0] : null) || firstCadId;
// 1. Move orthophoto under any plan layer (or under marker)
const orthoBeforeId = firstPlanId || markerBottomId;
if (map.getLayer('layer-switcher-ortho') && orthoBeforeId && map.getLayer(orthoBeforeId)) {
try {
map.moveLayer('layer-switcher-ortho', orthoBeforeId);
} catch (e) {
console.warn("Could not move layer-switcher-ortho:", e);
}
}
// 2. Georeferenced image layers under CAD layers (or under marker)
const imageBeforeId = firstCadId || markerBottomId;
if (imageBeforeId && map.getLayer(imageBeforeId)) {
imageLayerIds.forEach(imgId => {
if (map.getLayer(imgId)) {
try {
map.moveLayer(imgId, imageBeforeId);
} catch (e) {
console.warn("Could not move image overlay layer:", e);
}
}
});
}
// 3. DXF CAD layers: fills -> lines -> symbols, before markerBottomId
if (markerBottomId && map.getLayer(markerBottomId)) {
['dxf-layout-fills', 'dxf-layout-lines', 'dxf-layout-symbols'].forEach(cadId => {
if (map.getLayer(cadId)) {
try {
map.moveLayer(cadId, markerBottomId);
} catch (e) {
console.warn("Could not move CAD layer:", e);
}
}
});
}
}
// --- Contrôle photo aérienne (LayerSwitcherControl) --- (second, below Plans button) // --- Contrôle photo aérienne (LayerSwitcherControl) --- (second, below Plans button)
if (typeof LayerSwitcherControl !== 'undefined') { if (typeof LayerSwitcherControl !== 'undefined') {
const layerSwitcher = new LayerSwitcherControl({ const layerSwitcher = new LayerSwitcherControl({
@ -1204,7 +1281,11 @@ document.addEventListener('DOMContentLoaded', () => {
name: 'Orthophotos', name: 'Orthophotos',
icon: 'satellite', icon: 'satellite',
group: 'ortho', group: 'ortho',
beforeLayer: 'existing-poles-layer', beforeLayer: getOrthoBeforeLayer,
onLayerAdded: () => reorderCrossPlanLayers(),
onVisibilityChange: (layerId, isVisible) => {
if (isVisible) reorderCrossPlanLayers();
},
layer: { layer: {
type: 'raster', type: 'raster',
paint: { 'raster-opacity': 1 } paint: { 'raster-opacity': 1 }
@ -1783,6 +1864,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (map.getLayer(lyrId)) { if (map.getLayer(lyrId)) {
map.setLayoutProperty(lyrId, 'visibility', 'visible'); map.setLayoutProperty(lyrId, 'visibility', 'visible');
map.setPaintProperty(lyrId, 'raster-opacity', opacity); map.setPaintProperty(lyrId, 'raster-opacity', opacity);
reorderCrossPlanLayers();
} }
} else { } else {
map.addSource(srcId, { map.addSource(srcId, {
@ -1807,6 +1889,8 @@ document.addEventListener('DOMContentLoaded', () => {
'layout': { 'visibility': 'visible' }, 'layout': { 'visibility': 'visible' },
'paint': { 'raster-opacity': opacity } 'paint': { 'raster-opacity': opacity }
}, beforeId); }, beforeId);
reorderCrossPlanLayers();
} }
// Zoom/fitBounds to document // Zoom/fitBounds to document
@ -1864,6 +1948,7 @@ document.addEventListener('DOMContentLoaded', () => {
map.setLayoutProperty(lId, 'visibility', 'visible'); map.setLayoutProperty(lId, 'visibility', 'visible');
} }
}); });
reorderCrossPlanLayers();
} }
} }
@ -1932,6 +2017,7 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}, beforeId); }, beforeId);
} }
reorderCrossPlanLayers();
} }
function updateButtonActiveState() { function updateButtonActiveState() {

View file

@ -425,7 +425,12 @@
this.map.addSource(sourceId, sublayer.source); this.map.addSource(sourceId, sublayer.source);
// Determine where to insert the layer // Determine where to insert the layer
let beforeLayer = layerConfig.beforeLayer; let beforeLayer = typeof layerConfig.beforeLayer === 'function'
? layerConfig.beforeLayer(this.map)
: layerConfig.beforeLayer;
if (beforeLayer && !this.map.getLayer(beforeLayer)) {
beforeLayer = undefined;
}
if (!beforeLayer) { if (!beforeLayer) {
const layers = this.map.getStyle().layers; const layers = this.map.getStyle().layers;
for (const layer of layers) { for (const layer of layers) {
@ -447,6 +452,13 @@
}; };
this.map.addLayer(mapLayerConfig, beforeLayer); this.map.addLayer(mapLayerConfig, beforeLayer);
if (layerConfig.onLayerAdded && typeof layerConfig.onLayerAdded === 'function') {
layerConfig.onLayerAdded(mapLayerId, this.map);
}
if (this.map && typeof this.map.fire === 'function') {
this.map.fire('layerswitcher:layerAdded', { layerId: mapLayerId, config: layerConfig });
}
} }
} }
@ -501,8 +513,14 @@
} }
// Determine where to insert the layer // Determine where to insert the layer
let beforeLayer = layerConfig.beforeLayer; let beforeLayer = typeof layerConfig.beforeLayer === 'function'
? layerConfig.beforeLayer(this.map)
: layerConfig.beforeLayer;
if (beforeLayer && !this.map.getLayer(beforeLayer)) {
beforeLayer = undefined;
}
// If no beforeLayer specified, try to insert before draw layers // If no beforeLayer specified, try to insert before draw layers
if (!beforeLayer) { if (!beforeLayer) {
const layers = this.map.getStyle().layers; const layers = this.map.getStyle().layers;
@ -531,6 +549,13 @@
state.layerAdded = true; state.layerAdded = true;
state.visible = true; state.visible = true;
} }
if (layerConfig.onLayerAdded && typeof layerConfig.onLayerAdded === 'function') {
layerConfig.onLayerAdded(layerId, this.map);
}
if (this.map && typeof this.map.fire === 'function') {
this.map.fire('layerswitcher:layerAdded', { layerId: layerId, config: layerConfig });
}
} }
/** /**
@ -578,6 +603,13 @@
if (layerConfig.sublayers && layerConfig.sublayers.length > 0) { if (layerConfig.sublayers && layerConfig.sublayers.length > 0) {
this._hideSublayerSelector(layerId); this._hideSublayerSelector(layerId);
} }
if (layerConfig.onVisibilityChange && typeof layerConfig.onVisibilityChange === 'function') {
layerConfig.onVisibilityChange(mapLayerId, false, this.map);
}
if (this.map && typeof this.map.fire === 'function') {
this.map.fire('layerswitcher:visibilityChange', { layerId: mapLayerId, visible: false });
}
} else { } else {
// Handle exclusive layers // Handle exclusive layers
if (layerConfig.exclusive && layerConfig.group) { if (layerConfig.exclusive && layerConfig.group) {
@ -587,6 +619,12 @@
// Show layer // Show layer
if (this.map.getLayer(mapLayerId)) { if (this.map.getLayer(mapLayerId)) {
this.map.setLayoutProperty(mapLayerId, 'visibility', 'visible'); this.map.setLayoutProperty(mapLayerId, 'visibility', 'visible');
if (layerConfig.onVisibilityChange && typeof layerConfig.onVisibilityChange === 'function') {
layerConfig.onVisibilityChange(mapLayerId, true, this.map);
}
if (this.map && typeof this.map.fire === 'function') {
this.map.fire('layerswitcher:visibilityChange', { layerId: mapLayerId, visible: true });
}
} else { } else {
this._addLayer(layerConfig); this._addLayer(layerConfig);
} }