feat: add unified map assemblies and dynamic zoom scaling for mobile assets
This commit is contained in:
parent
3f75b511f2
commit
099043ea8b
1 changed files with 422 additions and 112 deletions
|
|
@ -341,7 +341,267 @@
|
||||||
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Marqueur spécifique poteau de signalisation (symbole desktop : socle circulaire + tige verticale) */
|
/* Assemblage unifié poteau + panneaux (ancre à offset [0, 0] sur la base au sol) */
|
||||||
|
.asset-map-assembly {
|
||||||
|
width: 28px;
|
||||||
|
height: 0px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: none;
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 5;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
/* Conteneur de mise à l'échelle dynamique des poteaux et panneaux selon le zoom */
|
||||||
|
.asset-map-assembly .assembly-scaler {
|
||||||
|
width: 28px;
|
||||||
|
height: 0px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
transform-origin: 50% 0;
|
||||||
|
transform: scale(var(--assembly-scale, 1));
|
||||||
|
pointer-events: none;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .pole-base {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #888888;
|
||||||
|
border: 1.5px solid #333333;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 2;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: transform 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .pole-base::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
bottom: -8px;
|
||||||
|
left: -8px;
|
||||||
|
right: -8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .pole-base:hover,
|
||||||
|
.asset-map-assembly .pole-base:active {
|
||||||
|
transform: translate(-50%, -50%) scale(1.3);
|
||||||
|
background-color: #ca8a04;
|
||||||
|
border-color: #854d0e;
|
||||||
|
box-shadow: 0 0 8px rgba(202, 138, 4, 0.6);
|
||||||
|
}
|
||||||
|
.asset-map-assembly.selected .pole-base,
|
||||||
|
.asset-map-assembly .pole-base.selected {
|
||||||
|
background-color: #fde047 !important;
|
||||||
|
border-color: #ca8a04 !important;
|
||||||
|
border-width: 2px !important;
|
||||||
|
transform: translate(-50%, -50%) scale(1.4);
|
||||||
|
box-shadow: 0 0 0 4px rgba(250, 204, 21, 0.45), 0 0 12px #eab308 !important;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .pole-stem {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 2.5px;
|
||||||
|
background-color: #555555;
|
||||||
|
border-left: 0.5px solid #222222;
|
||||||
|
border-right: 0.5px solid #222222;
|
||||||
|
border-top: 0.5px solid #222222;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: background-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
.asset-map-assembly.selected .pole-stem {
|
||||||
|
background-color: #eab308 !important;
|
||||||
|
border-color: #ca8a04 !important;
|
||||||
|
box-shadow: 0 0 8px rgba(234, 179, 8, 0.7) !important;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
bottom: -8px;
|
||||||
|
left: -8px;
|
||||||
|
right: -8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.45));
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item:hover img,
|
||||||
|
.asset-map-assembly .assembly-panel-item:active img {
|
||||||
|
transform: scale(1.25);
|
||||||
|
filter: drop-shadow(0 2px 6px rgba(0,0,0,0.6));
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item i {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1.5px solid #2563eb;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item:hover i,
|
||||||
|
.asset-map-assembly .assembly-panel-item:active i {
|
||||||
|
transform: scale(1.25);
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item.selected img {
|
||||||
|
transform: scale(1.3);
|
||||||
|
filter: drop-shadow(0 0 5px #eab308) drop-shadow(0 0 10px #fde047) drop-shadow(0 2px 5px rgba(0,0,0,0.5)) !important;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item.selected i {
|
||||||
|
border-color: #ca8a04 !important;
|
||||||
|
color: #854d0e !important;
|
||||||
|
transform: scale(1.3);
|
||||||
|
box-shadow: 0 0 8px rgba(234, 179, 8, 0.85);
|
||||||
|
}
|
||||||
|
.asset-map-assembly .assembly-panel-item.selected::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -3px;
|
||||||
|
left: -3px;
|
||||||
|
right: -3px;
|
||||||
|
bottom: -3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid #eab308;
|
||||||
|
box-shadow: 0 0 8px rgba(234, 179, 8, 0.85);
|
||||||
|
pointer-events: none;
|
||||||
|
animation: marker-selected-pulse 1.8s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
.asset-map-assembly .marker-count-badge {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(6px, -14px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Marqueur spécifique panneau compact à faible/moyen zoom (< 16.5) */
|
||||||
|
.asset-map-marker-compact {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
bottom: -4px;
|
||||||
|
left: -4px;
|
||||||
|
right: -4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.45));
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact:hover img, .asset-map-marker-compact:active img {
|
||||||
|
transform: scale(1.2);
|
||||||
|
filter: drop-shadow(0 2px 6px rgba(0,0,0,0.6));
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact i {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1.5px solid #2563eb;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact:hover i, .asset-map-marker-compact:active i {
|
||||||
|
transform: scale(1.2);
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact.selected {
|
||||||
|
z-index: 50 !important;
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact.selected img {
|
||||||
|
transform: scale(1.25);
|
||||||
|
filter: drop-shadow(0 0 5px #eab308) drop-shadow(0 0 10px #fde047) drop-shadow(0 2px 5px rgba(0,0,0,0.5)) !important;
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact.selected i {
|
||||||
|
border-color: #ca8a04;
|
||||||
|
color: #854d0e;
|
||||||
|
transform: scale(1.2);
|
||||||
|
box-shadow: 0 0 8px rgba(234, 179, 8, 0.85);
|
||||||
|
}
|
||||||
|
.asset-map-marker-compact.selected::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
left: -4px;
|
||||||
|
right: -4px;
|
||||||
|
bottom: -4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #eab308;
|
||||||
|
box-shadow: 0 0 8px rgba(234, 179, 8, 0.85);
|
||||||
|
pointer-events: none;
|
||||||
|
animation: marker-selected-pulse 1.8s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Marqueur spécifique poteau de signalisation seul (socle circulaire + tige verticale) */
|
||||||
.asset-map-marker-pole {
|
.asset-map-marker-pole {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
|
@ -1101,6 +1361,26 @@
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mise à l'échelle dynamique des poteaux et panneaux selon le niveau de zoom
|
||||||
|
function updateAssemblyScale() {
|
||||||
|
if (!map) return;
|
||||||
|
const z = map.getZoom();
|
||||||
|
let scale;
|
||||||
|
if (z >= 20.5) {
|
||||||
|
scale = 1.0;
|
||||||
|
} else if (z <= 12.5) {
|
||||||
|
scale = 0.25;
|
||||||
|
} else {
|
||||||
|
// La réduction commence plus tôt (dès zoom 20.5) et s'adapte régulièrement jusqu'à zoom 12.5
|
||||||
|
scale = 0.25 + (z - 12.5) * (0.75 / 8.0);
|
||||||
|
}
|
||||||
|
scale = Math.round(scale * 100) / 100;
|
||||||
|
const container = map.getContainer ? map.getContainer() : document.getElementById('assets-map');
|
||||||
|
if (container) {
|
||||||
|
container.style.setProperty('--assembly-scale', scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Initialisation de la carte MapLibre
|
// 1. Initialisation de la carte MapLibre
|
||||||
function initMap() {
|
function initMap() {
|
||||||
if (map) return;
|
if (map) return;
|
||||||
|
|
@ -1122,6 +1402,10 @@
|
||||||
|
|
||||||
map.addControl(new maplibregl.NavigationControl({ showCompass: true }), 'top-left');
|
map.addControl(new maplibregl.NavigationControl({ showCompass: true }), 'top-left');
|
||||||
|
|
||||||
|
// Écouter le zoom pour ajuster dynamiquement et immédiatement la taille des poteaux et panneaux
|
||||||
|
map.on('zoom', updateAssemblyScale);
|
||||||
|
updateAssemblyScale();
|
||||||
|
|
||||||
// Couches vectorielles pour les éléments surfaciques (polygones) et linéaires (lignes)
|
// Couches vectorielles pour les éléments surfaciques (polygones) et linéaires (lignes)
|
||||||
function setupMapLayers() {
|
function setupMapLayers() {
|
||||||
if (!map || !map.isStyleLoaded()) return;
|
if (!map || !map.isStyleLoaded()) return;
|
||||||
|
|
@ -1217,6 +1501,8 @@
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
map.on('load', () => {
|
map.on('load', () => {
|
||||||
layersConfigured = false;
|
layersConfigured = false;
|
||||||
setupMapLayers();
|
setupMapLayers();
|
||||||
|
|
@ -1546,11 +1832,11 @@
|
||||||
|
|
||||||
let isSameLocation = false;
|
let isSameLocation = false;
|
||||||
if (isSignRelated) {
|
if (isSignRelated) {
|
||||||
// Pour les panneaux et poteaux, regrouper uniquement si même poteau
|
// Pour les panneaux et poteaux, regrouper si même poteau
|
||||||
// ou si coordonnées strictement identiques (< 1m) pour panneaux sans pole_id
|
// ou si coordonnées strictement identiques (< 1.5m) pour éléments sans poteau attribué
|
||||||
if (samePole) {
|
if (samePole) {
|
||||||
isSameLocation = true;
|
isSameLocation = true;
|
||||||
} else if (!asset.pole_id && !other.pole_id && !isAssetPole && !isOtherPole && asset.model_key === 'signpanel' && other.model_key === 'signpanel') {
|
} else if (!asset.pole_id && !other.pole_id) {
|
||||||
const dLon = Math.abs(other.lon - asset.lon);
|
const dLon = Math.abs(other.lon - asset.lon);
|
||||||
const dLat = Math.abs(other.lat - asset.lat);
|
const dLat = Math.abs(other.lat - asset.lat);
|
||||||
isSameLocation = (dLon < 0.000015 && dLat < 0.000015);
|
isSameLocation = (dLon < 0.000015 && dLat < 0.000015);
|
||||||
|
|
@ -1584,61 +1870,52 @@
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Créer les marqueurs pour chaque emplacement avec offset vertical pour les panneaux
|
// 4. Créer les marqueurs pour chaque emplacement
|
||||||
|
updateAssemblyScale();
|
||||||
locationGroups.forEach(group => {
|
locationGroups.forEach(group => {
|
||||||
const signPanels = group.filter(a => a.model_key === 'signpanel');
|
const signPanels = group.filter(a => a.model_key === 'signpanel');
|
||||||
const otherAssets = group.filter(a => a.model_key !== 'signpanel');
|
const otherAssets = group.filter(a => a.model_key !== 'signpanel');
|
||||||
|
|
||||||
// CAS A : Emplacement avec un ou plusieurs panneaux de signalisation
|
// CAS A : Emplacement avec un ou plusieurs panneaux de signalisation
|
||||||
|
// Rendu systématique en assemblage vertical unifié sans agrégation avec compteur
|
||||||
if (signPanels.length > 0) {
|
if (signPanels.length > 0) {
|
||||||
// Trier pour identifier si un poteau ou support est présent
|
|
||||||
const poleAsset = otherAssets.find(a => a.model_key === 'signpole' || (a.model_key && a.model_key.includes('pole')) || (a.thematic === 'sign' && a.model_key && a.model_key.includes('pole')));
|
const poleAsset = otherAssets.find(a => a.model_key === 'signpole' || (a.model_key && a.model_key.includes('pole')) || (a.thematic === 'sign' && a.model_key && a.model_key.includes('pole')));
|
||||||
const baseAsset = poleAsset || (otherAssets.length > 0 ? otherAssets[0] : null);
|
const baseAsset = poleAsset || (otherAssets.length > 0 ? otherAssets[0] : null);
|
||||||
|
|
||||||
const hasBaseSupport = !!baseAsset;
|
const hasBaseSupport = !!baseAsset;
|
||||||
|
|
||||||
|
const refLon = baseAsset ? baseAsset.lon : signPanels[0].lon;
|
||||||
|
const refLat = baseAsset ? baseAsset.lat : signPanels[0].lat;
|
||||||
|
|
||||||
|
// vertical_order = 1 est le panneau le plus bas (au-dessus du dégagement du sol)
|
||||||
|
// Les ordres supérieurs s'empilent vers le haut le long du mât
|
||||||
|
signPanels.sort((a, b) => {
|
||||||
|
const orderA = a.vertical_order != null ? Number(a.vertical_order) : (a.panel_vertical_order != null ? Number(a.panel_vertical_order) : 1);
|
||||||
|
const orderB = b.vertical_order != null ? Number(b.vertical_order) : (b.panel_vertical_order != null ? Number(b.panel_vertical_order) : 1);
|
||||||
|
return (orderA - orderB) || (Number(a.id) - Number(b.id));
|
||||||
|
});
|
||||||
|
|
||||||
const N = signPanels.length;
|
const N = signPanels.length;
|
||||||
const highestPanelIndex = Math.max(0, N - 1);
|
const itemStep = 21; // Espacement vertical compact entre panneaux
|
||||||
|
const baseClearance = hasBaseSupport ? 16 : 0; // Dégagement harmonieux au-dessus du trottoir (tige visible ~12px)
|
||||||
// Dimensions compactes identiques à la vue desktop :
|
const stemHeight = hasBaseSupport ? (baseClearance + (N - 1) * itemStep + 16) : 0;
|
||||||
// Centre du panneau le plus bas à -14px au-dessus du sol/socle
|
|
||||||
// Incrément de 14px pour chaque panneau supplémentaire sur le mât
|
|
||||||
const stemHeight = hasBaseSupport ? (16 + (highestPanelIndex * 14)) : 0;
|
|
||||||
const topOffset = -(14 + (highestPanelIndex * 14));
|
|
||||||
|
|
||||||
// 1. S'il y a un poteau ou support à la base, le positionner à l'offset [0, 0]
|
|
||||||
if (baseAsset) {
|
|
||||||
const symbology = (typeof AssetSymbology !== 'undefined')
|
|
||||||
? AssetSymbology.getAssetSymbology(baseAsset)
|
|
||||||
: { color: THEMATIC_COLORS[baseAsset.thematic] || '#888888', iconClass: baseAsset.thematic_icon || 'bi-signpost-split' };
|
|
||||||
const color = symbology.color || THEMATIC_COLORS[baseAsset.thematic] || '#888888';
|
|
||||||
const iconHtml = (typeof AssetSymbology !== 'undefined')
|
|
||||||
? AssetSymbology.renderIconHtml(symbology)
|
|
||||||
: `<i class="bi ${symbology.iconClass || 'bi-signpost-split'}"></i>`;
|
|
||||||
|
|
||||||
const isSignPole = baseAsset.model_key === 'signpole' || baseAsset.model_key.includes('pole') || (baseAsset.thematic === 'sign' && baseAsset.model_key.includes('pole'));
|
|
||||||
|
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
if (isSignPole) {
|
el.className = 'asset-map-assembly';
|
||||||
el.className = 'asset-map-marker-pole';
|
|
||||||
el.innerHTML = `<div class="pole-stem" style="height: ${stemHeight}px;"></div><div class="pole-base"></div>`;
|
|
||||||
el.style.zIndex = '2';
|
|
||||||
} else {
|
|
||||||
el.className = 'asset-map-marker';
|
|
||||||
el.style.borderColor = color;
|
|
||||||
el.style.color = color;
|
|
||||||
el.style.zIndex = '5';
|
el.style.zIndex = '5';
|
||||||
el.innerHTML = iconHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherAssets.length > 1) {
|
const scaler = document.createElement('div');
|
||||||
const badge = document.createElement('span');
|
scaler.className = 'assembly-scaler';
|
||||||
badge.className = 'marker-count-badge';
|
el.appendChild(scaler);
|
||||||
badge.textContent = otherAssets.length > 99 ? '99+' : otherAssets.length;
|
|
||||||
badge.title = `${otherAssets.length} équipements à la base`;
|
|
||||||
el.appendChild(badge);
|
|
||||||
}
|
|
||||||
|
|
||||||
el.addEventListener('click', (e) => {
|
// 1. Socle / base circulaire du poteau (ancré exactement à Y=0 sur les coordonnées GPS)
|
||||||
|
if (hasBaseSupport) {
|
||||||
|
const baseDot = document.createElement('div');
|
||||||
|
baseDot.className = 'pole-base';
|
||||||
|
const baseFeatId = baseAsset.feature_id || (baseAsset.model_key && baseAsset.id ? `${baseAsset.model_key}_${baseAsset.id}` : null);
|
||||||
|
if (baseFeatId) baseDot.dataset.featureId = baseFeatId;
|
||||||
|
baseDot.title = baseAsset.name || baseAsset.code || 'Poteau';
|
||||||
|
|
||||||
|
baseDot.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (e.originalEvent) {
|
if (e.originalEvent) {
|
||||||
e.originalEvent._handledByAssetLayer = true;
|
e.originalEvent._handledByAssetLayer = true;
|
||||||
|
|
@ -1650,42 +1927,24 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const marker = new maplibregl.Marker({ element: el, offset: [0, 0] })
|
if (lastSelectedFeatureId && baseFeatId === lastSelectedFeatureId) {
|
||||||
.setLngLat([baseAsset.lon, baseAsset.lat])
|
baseDot.classList.add('selected');
|
||||||
.addTo(map);
|
|
||||||
|
|
||||||
marker._asset = baseAsset;
|
|
||||||
marker._group = group;
|
|
||||||
marker._element = el;
|
|
||||||
|
|
||||||
const isBaseSelected = otherAssets.some(a => {
|
|
||||||
const featId = a.feature_id || (a.model_key && a.id ? `${a.model_key}_${a.id}` : null);
|
|
||||||
return lastSelectedFeatureId && featId === lastSelectedFeatureId;
|
|
||||||
});
|
|
||||||
if (isBaseSelected) {
|
|
||||||
el.classList.add('selected');
|
|
||||||
el.style.zIndex = '50';
|
el.style.zIndex = '50';
|
||||||
selectedMarkerElement = el;
|
selectedMarkerElement = baseDot;
|
||||||
}
|
}
|
||||||
|
|
||||||
activeMarkers.push(marker);
|
scaler.appendChild(baseDot);
|
||||||
|
|
||||||
|
// Tige verticale montant vers le haut depuis la base
|
||||||
|
const stem = document.createElement('div');
|
||||||
|
stem.className = 'pole-stem';
|
||||||
|
stem.style.height = `${stemHeight}px`;
|
||||||
|
scaler.appendChild(stem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Calcul des offsets verticaux pour chaque panneau sur le poteau
|
// 2. Panneaux de signalisation empilés vers le haut
|
||||||
// vertical_order = 1 est le panneau principal placé au SOMMET du mât.
|
|
||||||
// Les panneaux secondaires / panonceaux (vertical_order > 1) se succèdent vers le bas le long du mât.
|
|
||||||
signPanels.sort((a, b) => {
|
|
||||||
const orderA = a.vertical_order != null ? Number(a.vertical_order) : (a.panel_vertical_order != null ? Number(a.panel_vertical_order) : 1);
|
|
||||||
const orderB = b.vertical_order != null ? Number(b.vertical_order) : (b.panel_vertical_order != null ? Number(b.panel_vertical_order) : 1);
|
|
||||||
return (orderA - orderB) || (Number(a.id) - Number(b.id));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Coordonnées de référence partagées pour l'alignement parfait sur la tige
|
|
||||||
const refLon = baseAsset ? baseAsset.lon : signPanels[0].lon;
|
|
||||||
const refLat = baseAsset ? baseAsset.lat : signPanels[0].lat;
|
|
||||||
|
|
||||||
signPanels.forEach((panel, index) => {
|
signPanels.forEach((panel, index) => {
|
||||||
const panelOffset = topOffset + (index * 14);
|
const panelBottom = baseClearance + (index * itemStep);
|
||||||
|
|
||||||
const symbology = (typeof AssetSymbology !== 'undefined')
|
const symbology = (typeof AssetSymbology !== 'undefined')
|
||||||
? AssetSymbology.getAssetSymbology(panel)
|
? AssetSymbology.getAssetSymbology(panel)
|
||||||
|
|
@ -1694,12 +1953,16 @@
|
||||||
? AssetSymbology.renderIconHtml(symbology)
|
? AssetSymbology.renderIconHtml(symbology)
|
||||||
: `<i class="bi ${symbology.iconClass || 'bi-sign-stop-fill'}"></i>`;
|
: `<i class="bi ${symbology.iconClass || 'bi-sign-stop-fill'}"></i>`;
|
||||||
|
|
||||||
const el = document.createElement('div');
|
const panelEl = document.createElement('div');
|
||||||
el.className = 'asset-map-marker-sign';
|
panelEl.className = 'assembly-panel-item';
|
||||||
el.innerHTML = iconHtml;
|
panelEl.style.bottom = `${panelBottom}px`;
|
||||||
el.style.zIndex = String(10 + (N - index));
|
panelEl.style.zIndex = String(10 + index);
|
||||||
|
panelEl.innerHTML = iconHtml;
|
||||||
|
|
||||||
el.addEventListener('click', (e) => {
|
const featId = panel.feature_id || (panel.model_key && panel.id ? `${panel.model_key}_${panel.id}` : null);
|
||||||
|
if (featId) panelEl.dataset.featureId = featId;
|
||||||
|
|
||||||
|
panelEl.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (e.originalEvent) {
|
if (e.originalEvent) {
|
||||||
e.originalEvent._handledByAssetLayer = true;
|
e.originalEvent._handledByAssetLayer = true;
|
||||||
|
|
@ -1707,25 +1970,24 @@
|
||||||
openBottomSheet(panel, false, group);
|
openBottomSheet(panel, false, group);
|
||||||
});
|
});
|
||||||
|
|
||||||
const marker = new maplibregl.Marker({ element: el, offset: [0, panelOffset] })
|
if (lastSelectedFeatureId && featId === lastSelectedFeatureId) {
|
||||||
|
panelEl.classList.add('selected');
|
||||||
|
el.style.zIndex = '50';
|
||||||
|
selectedMarkerElement = panelEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
scaler.appendChild(panelEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
const marker = new maplibregl.Marker({ element: el, offset: [0, 0] })
|
||||||
.setLngLat([refLon, refLat])
|
.setLngLat([refLon, refLat])
|
||||||
.addTo(map);
|
.addTo(map);
|
||||||
|
|
||||||
marker.setOffset([0, panelOffset]);
|
marker._asset = baseAsset || signPanels[0];
|
||||||
|
|
||||||
marker._asset = panel;
|
|
||||||
marker._group = group;
|
marker._group = group;
|
||||||
marker._element = el;
|
marker._element = el;
|
||||||
|
|
||||||
const featId = panel.feature_id || (panel.model_key && panel.id ? `${panel.model_key}_${panel.id}` : null);
|
|
||||||
if (lastSelectedFeatureId && featId === lastSelectedFeatureId) {
|
|
||||||
el.classList.add('selected');
|
|
||||||
el.style.zIndex = '50';
|
|
||||||
selectedMarkerElement = el;
|
|
||||||
}
|
|
||||||
|
|
||||||
activeMarkers.push(marker);
|
activeMarkers.push(marker);
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// CAS B : Emplacement sans panneau (arbres, bancs, corbeilles, poteau seul, etc.)
|
// CAS B : Emplacement sans panneau (arbres, bancs, corbeilles, poteau seul, etc.)
|
||||||
group.sort((a, b) => getDisplayPriority(b) - getDisplayPriority(a));
|
group.sort((a, b) => getDisplayPriority(b) - getDisplayPriority(a));
|
||||||
|
|
@ -1743,23 +2005,48 @@
|
||||||
|
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
if (isSignPole) {
|
if (isSignPole) {
|
||||||
el.className = 'asset-map-marker-pole';
|
el.className = 'asset-map-assembly';
|
||||||
el.innerHTML = '<div class="pole-stem" style="height: 16px;"></div><div class="pole-base"></div>';
|
|
||||||
el.style.zIndex = '5';
|
el.style.zIndex = '5';
|
||||||
|
|
||||||
|
const scaler = document.createElement('div');
|
||||||
|
scaler.className = 'assembly-scaler';
|
||||||
|
el.appendChild(scaler);
|
||||||
|
|
||||||
|
const baseDot = document.createElement('div');
|
||||||
|
baseDot.className = 'pole-base';
|
||||||
|
const baseFeatId = primaryAsset.feature_id || (primaryAsset.model_key && primaryAsset.id ? `${primaryAsset.model_key}_${primaryAsset.id}` : null);
|
||||||
|
if (baseFeatId) baseDot.dataset.featureId = baseFeatId;
|
||||||
|
baseDot.title = primaryAsset.name || primaryAsset.code || 'Poteau';
|
||||||
|
|
||||||
|
baseDot.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.originalEvent) {
|
||||||
|
e.originalEvent._handledByAssetLayer = true;
|
||||||
|
}
|
||||||
|
if (group.length > 1) {
|
||||||
|
openMultiBottomSheet(group);
|
||||||
|
} else {
|
||||||
|
openBottomSheet(primaryAsset, false, group);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lastSelectedFeatureId && baseFeatId === lastSelectedFeatureId) {
|
||||||
|
baseDot.classList.add('selected');
|
||||||
|
el.style.zIndex = '50';
|
||||||
|
selectedMarkerElement = baseDot;
|
||||||
|
}
|
||||||
|
|
||||||
|
scaler.appendChild(baseDot);
|
||||||
|
|
||||||
|
const stem = document.createElement('div');
|
||||||
|
stem.className = 'pole-stem';
|
||||||
|
stem.style.height = '18px';
|
||||||
|
scaler.appendChild(stem);
|
||||||
} else {
|
} else {
|
||||||
el.className = 'asset-map-marker';
|
el.className = 'asset-map-marker';
|
||||||
el.style.borderColor = color;
|
el.style.borderColor = color;
|
||||||
el.style.color = color;
|
el.style.color = color;
|
||||||
el.innerHTML = iconHtml;
|
el.innerHTML = iconHtml;
|
||||||
}
|
|
||||||
|
|
||||||
if (group.length > 1) {
|
|
||||||
const badge = document.createElement('span');
|
|
||||||
badge.className = 'marker-count-badge';
|
|
||||||
badge.textContent = group.length > 99 ? '99+' : group.length;
|
|
||||||
badge.title = `${group.length} assets à cet endroit`;
|
|
||||||
el.appendChild(badge);
|
|
||||||
}
|
|
||||||
|
|
||||||
el.addEventListener('click', (e) => {
|
el.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -1772,6 +2059,15 @@
|
||||||
openBottomSheet(primaryAsset, false, group);
|
openBottomSheet(primaryAsset, false, group);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSignPole && group.length > 1) {
|
||||||
|
const badge = document.createElement('span');
|
||||||
|
badge.className = 'marker-count-badge';
|
||||||
|
badge.textContent = group.length > 99 ? '99+' : group.length;
|
||||||
|
badge.title = `${group.length} assets à cet endroit`;
|
||||||
|
el.appendChild(badge);
|
||||||
|
}
|
||||||
|
|
||||||
const marker = new maplibregl.Marker({ element: el, offset: [0, 0] })
|
const marker = new maplibregl.Marker({ element: el, offset: [0, 0] })
|
||||||
.setLngLat([primaryAsset.lon, primaryAsset.lat])
|
.setLngLat([primaryAsset.lon, primaryAsset.lat])
|
||||||
|
|
@ -1955,6 +2251,8 @@
|
||||||
if (selectedMarkerElement) {
|
if (selectedMarkerElement) {
|
||||||
selectedMarkerElement.classList.remove('selected');
|
selectedMarkerElement.classList.remove('selected');
|
||||||
selectedMarkerElement.style.zIndex = '';
|
selectedMarkerElement.style.zIndex = '';
|
||||||
|
const parentAssembly = selectedMarkerElement.closest ? selectedMarkerElement.closest('.asset-map-assembly') : null;
|
||||||
|
if (parentAssembly) parentAssembly.style.zIndex = '';
|
||||||
selectedMarkerElement = null;
|
selectedMarkerElement = null;
|
||||||
}
|
}
|
||||||
const currentFeatId = asset.feature_id || (asset.model_key && asset.id ? `${asset.model_key}_${asset.id}` : null);
|
const currentFeatId = asset.feature_id || (asset.model_key && asset.id ? `${asset.model_key}_${asset.id}` : null);
|
||||||
|
|
@ -1970,11 +2268,19 @@
|
||||||
return m._group && m._group.some(g => String(g.feature_id) === String(currentFeatId) || (g.model_key === asset.model_key && String(g.id) === String(asset.id)));
|
return m._group && m._group.some(g => String(g.feature_id) === String(currentFeatId) || (g.model_key === asset.model_key && String(g.id) === String(asset.id)));
|
||||||
});
|
});
|
||||||
if (matchingMarker && matchingMarker._element) {
|
if (matchingMarker && matchingMarker._element) {
|
||||||
|
const subItem = matchingMarker._element.querySelector ? matchingMarker._element.querySelector(`[data-feature-id="${currentFeatId}"]`) : null;
|
||||||
|
if (subItem) {
|
||||||
|
selectedMarkerElement = subItem;
|
||||||
|
subItem.classList.add('selected');
|
||||||
|
subItem.style.zIndex = '50';
|
||||||
|
matchingMarker._element.style.zIndex = '50';
|
||||||
|
} else {
|
||||||
selectedMarkerElement = matchingMarker._element;
|
selectedMarkerElement = matchingMarker._element;
|
||||||
selectedMarkerElement.classList.add('selected');
|
selectedMarkerElement.classList.add('selected');
|
||||||
selectedMarkerElement.style.zIndex = '50';
|
selectedMarkerElement.style.zIndex = '50';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sheetCode.textContent = asset.code || '-';
|
sheetCode.textContent = asset.code || '-';
|
||||||
sheetName.textContent = asset.name || asset.element_type || '';
|
sheetName.textContent = asset.name || asset.element_type || '';
|
||||||
|
|
@ -2033,6 +2339,8 @@
|
||||||
if (selectedMarkerElement) {
|
if (selectedMarkerElement) {
|
||||||
selectedMarkerElement.classList.remove('selected');
|
selectedMarkerElement.classList.remove('selected');
|
||||||
selectedMarkerElement.style.zIndex = '';
|
selectedMarkerElement.style.zIndex = '';
|
||||||
|
const parentAssembly = selectedMarkerElement.closest ? selectedMarkerElement.closest('.asset-map-assembly') : null;
|
||||||
|
if (parentAssembly) parentAssembly.style.zIndex = '';
|
||||||
selectedMarkerElement = null;
|
selectedMarkerElement = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2066,6 +2374,8 @@
|
||||||
if (selectedMarkerElement) {
|
if (selectedMarkerElement) {
|
||||||
selectedMarkerElement.classList.remove('selected');
|
selectedMarkerElement.classList.remove('selected');
|
||||||
selectedMarkerElement.style.zIndex = '';
|
selectedMarkerElement.style.zIndex = '';
|
||||||
|
const parentAssembly = selectedMarkerElement.closest ? selectedMarkerElement.closest('.asset-map-assembly') : null;
|
||||||
|
if (parentAssembly) parentAssembly.style.zIndex = '';
|
||||||
selectedMarkerElement = null;
|
selectedMarkerElement = null;
|
||||||
}
|
}
|
||||||
if (multiContext && multiContext.length > 1) {
|
if (multiContext && multiContext.length > 1) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue