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);
|
||||
}
|
||||
|
||||
/* 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 {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
|
@ -1101,6 +1361,26 @@
|
|||
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
|
||||
function initMap() {
|
||||
if (map) return;
|
||||
|
|
@ -1122,6 +1402,10 @@
|
|||
|
||||
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)
|
||||
function setupMapLayers() {
|
||||
if (!map || !map.isStyleLoaded()) return;
|
||||
|
|
@ -1217,6 +1501,8 @@
|
|||
}, 200);
|
||||
});
|
||||
|
||||
|
||||
|
||||
map.on('load', () => {
|
||||
layersConfigured = false;
|
||||
setupMapLayers();
|
||||
|
|
@ -1546,11 +1832,11 @@
|
|||
|
||||
let isSameLocation = false;
|
||||
if (isSignRelated) {
|
||||
// Pour les panneaux et poteaux, regrouper uniquement si même poteau
|
||||
// ou si coordonnées strictement identiques (< 1m) pour panneaux sans pole_id
|
||||
// Pour les panneaux et poteaux, regrouper si même poteau
|
||||
// ou si coordonnées strictement identiques (< 1.5m) pour éléments sans poteau attribué
|
||||
if (samePole) {
|
||||
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 dLat = Math.abs(other.lat - asset.lat);
|
||||
isSameLocation = (dLon < 0.000015 && dLat < 0.000015);
|
||||
|
|
@ -1584,61 +1870,52 @@
|
|||
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 => {
|
||||
const signPanels = 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
|
||||
// Rendu systématique en assemblage vertical unifié sans agrégation avec compteur
|
||||
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 baseAsset = poleAsset || (otherAssets.length > 0 ? otherAssets[0] : null);
|
||||
|
||||
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 highestPanelIndex = Math.max(0, N - 1);
|
||||
|
||||
// Dimensions compactes identiques à la vue desktop :
|
||||
// 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 itemStep = 21; // Espacement vertical compact entre panneaux
|
||||
const baseClearance = hasBaseSupport ? 16 : 0; // Dégagement harmonieux au-dessus du trottoir (tige visible ~12px)
|
||||
const stemHeight = hasBaseSupport ? (baseClearance + (N - 1) * itemStep + 16) : 0;
|
||||
|
||||
const el = document.createElement('div');
|
||||
if (isSignPole) {
|
||||
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.className = 'asset-map-assembly';
|
||||
el.style.zIndex = '5';
|
||||
el.innerHTML = iconHtml;
|
||||
}
|
||||
|
||||
if (otherAssets.length > 1) {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'marker-count-badge';
|
||||
badge.textContent = otherAssets.length > 99 ? '99+' : otherAssets.length;
|
||||
badge.title = `${otherAssets.length} équipements à la base`;
|
||||
el.appendChild(badge);
|
||||
}
|
||||
const scaler = document.createElement('div');
|
||||
scaler.className = 'assembly-scaler';
|
||||
el.appendChild(scaler);
|
||||
|
||||
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();
|
||||
if (e.originalEvent) {
|
||||
e.originalEvent._handledByAssetLayer = true;
|
||||
|
|
@ -1650,42 +1927,24 @@
|
|||
}
|
||||
});
|
||||
|
||||
const marker = new maplibregl.Marker({ element: el, offset: [0, 0] })
|
||||
.setLngLat([baseAsset.lon, baseAsset.lat])
|
||||
.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');
|
||||
if (lastSelectedFeatureId && baseFeatId === lastSelectedFeatureId) {
|
||||
baseDot.classList.add('selected');
|
||||
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
|
||||
// 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;
|
||||
|
||||
// 2. Panneaux de signalisation empilés vers le haut
|
||||
signPanels.forEach((panel, index) => {
|
||||
const panelOffset = topOffset + (index * 14);
|
||||
const panelBottom = baseClearance + (index * itemStep);
|
||||
|
||||
const symbology = (typeof AssetSymbology !== 'undefined')
|
||||
? AssetSymbology.getAssetSymbology(panel)
|
||||
|
|
@ -1694,12 +1953,16 @@
|
|||
? AssetSymbology.renderIconHtml(symbology)
|
||||
: `<i class="bi ${symbology.iconClass || 'bi-sign-stop-fill'}"></i>`;
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = 'asset-map-marker-sign';
|
||||
el.innerHTML = iconHtml;
|
||||
el.style.zIndex = String(10 + (N - index));
|
||||
const panelEl = document.createElement('div');
|
||||
panelEl.className = 'assembly-panel-item';
|
||||
panelEl.style.bottom = `${panelBottom}px`;
|
||||
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();
|
||||
if (e.originalEvent) {
|
||||
e.originalEvent._handledByAssetLayer = true;
|
||||
|
|
@ -1707,25 +1970,24 @@
|
|||
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])
|
||||
.addTo(map);
|
||||
|
||||
marker.setOffset([0, panelOffset]);
|
||||
|
||||
marker._asset = panel;
|
||||
marker._asset = baseAsset || signPanels[0];
|
||||
marker._group = group;
|
||||
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);
|
||||
});
|
||||
} else {
|
||||
// CAS B : Emplacement sans panneau (arbres, bancs, corbeilles, poteau seul, etc.)
|
||||
group.sort((a, b) => getDisplayPriority(b) - getDisplayPriority(a));
|
||||
|
|
@ -1743,23 +2005,48 @@
|
|||
|
||||
const el = document.createElement('div');
|
||||
if (isSignPole) {
|
||||
el.className = 'asset-map-marker-pole';
|
||||
el.innerHTML = '<div class="pole-stem" style="height: 16px;"></div><div class="pole-base"></div>';
|
||||
el.className = 'asset-map-assembly';
|
||||
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 {
|
||||
el.className = 'asset-map-marker';
|
||||
el.style.borderColor = color;
|
||||
el.style.color = color;
|
||||
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) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -1772,6 +2059,15 @@
|
|||
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] })
|
||||
.setLngLat([primaryAsset.lon, primaryAsset.lat])
|
||||
|
|
@ -1955,6 +2251,8 @@
|
|||
if (selectedMarkerElement) {
|
||||
selectedMarkerElement.classList.remove('selected');
|
||||
selectedMarkerElement.style.zIndex = '';
|
||||
const parentAssembly = selectedMarkerElement.closest ? selectedMarkerElement.closest('.asset-map-assembly') : null;
|
||||
if (parentAssembly) parentAssembly.style.zIndex = '';
|
||||
selectedMarkerElement = 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)));
|
||||
});
|
||||
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.classList.add('selected');
|
||||
selectedMarkerElement.style.zIndex = '50';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sheetCode.textContent = asset.code || '-';
|
||||
sheetName.textContent = asset.name || asset.element_type || '';
|
||||
|
|
@ -2033,6 +2339,8 @@
|
|||
if (selectedMarkerElement) {
|
||||
selectedMarkerElement.classList.remove('selected');
|
||||
selectedMarkerElement.style.zIndex = '';
|
||||
const parentAssembly = selectedMarkerElement.closest ? selectedMarkerElement.closest('.asset-map-assembly') : null;
|
||||
if (parentAssembly) parentAssembly.style.zIndex = '';
|
||||
selectedMarkerElement = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -2066,6 +2374,8 @@
|
|||
if (selectedMarkerElement) {
|
||||
selectedMarkerElement.classList.remove('selected');
|
||||
selectedMarkerElement.style.zIndex = '';
|
||||
const parentAssembly = selectedMarkerElement.closest ? selectedMarkerElement.closest('.asset-map-assembly') : null;
|
||||
if (parentAssembly) parentAssembly.style.zIndex = '';
|
||||
selectedMarkerElement = null;
|
||||
}
|
||||
if (multiContext && multiContext.length > 1) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue