diff --git a/loko/mobile/templates/mobile/assets/assets_index_mobile.html b/loko/mobile/templates/mobile/assets/assets_index_mobile.html index 2ed375e..36afb5a 100644 --- a/loko/mobile/templates/mobile/assets/assets_index_mobile.html +++ b/loko/mobile/templates/mobile/assets/assets_index_mobile.html @@ -239,7 +239,7 @@ box-shadow: 0 0 10px rgba(13, 110, 253, 0.7); } - /* Marqueur asset sur la carte */ + /* Marqueur asset ponctuel standard */ .asset-map-marker { width: 32px; height: 32px; @@ -255,6 +255,7 @@ transition: none !important; padding: 3px; box-sizing: border-box; + position: relative; } .maplibregl-marker { transition: none !important; @@ -282,6 +283,59 @@ box-shadow: 0 4px 10px rgba(0,0,0,0.4); } + /* Marqueur spécifique panneau de signalisation (sans cercle bleu/blanc) */ + .asset-map-marker-sign { + width: 32px; + height: 32px; + 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-sign img { + width: 30px; + height: 30px; + object-fit: contain; + display: block; + pointer-events: none; + filter: drop-shadow(0 2px 4px rgba(0,0,0,0.45)); + transition: transform 0.15s ease; + } + .asset-map-marker-sign:hover img, .asset-map-marker-sign:active img { + transform: scale(1.2); + filter: drop-shadow(0 3px 8px rgba(0,0,0,0.6)); + } + + /* Badge compteur pour assets multiples au même endroit */ + .marker-count-badge { + position: absolute; + top: -5px; + right: -5px; + min-width: 18px; + height: 18px; + padding: 0 3px; + border-radius: 9px; + background: #2563eb; + color: #ffffff; + font-size: 0.65rem; + font-weight: 700; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 1px 4px rgba(0,0,0,0.45); + border: 1.5px solid #ffffff; + pointer-events: none; + z-index: 20; + } + /* Marqueur ponctuel sélectionné */ .asset-map-marker.selected { z-index: 50 !important; @@ -304,6 +358,26 @@ pointer-events: none; animation: marker-selected-pulse 1.8s infinite ease-in-out; } + .asset-map-marker-sign.selected { + z-index: 50 !important; + } + .asset-map-marker-sign.selected img { + transform: scale(1.25); + filter: drop-shadow(0 0 5px #eab308) drop-shadow(0 0 10px #fde047) drop-shadow(0 3px 6px rgba(0,0,0,0.5)) !important; + } + .asset-map-marker-sign.selected::after { + content: ''; + position: absolute; + top: -6px; + left: -6px; + right: -6px; + bottom: -6px; + border-radius: 50%; + border: 2.5px solid #eab308; + box-shadow: 0 0 10px rgba(234, 179, 8, 0.85); + pointer-events: none; + animation: marker-selected-pulse 1.8s infinite ease-in-out; + } @keyframes marker-selected-pulse { 0%, 100% { transform: scale(1); @@ -1306,55 +1380,254 @@ activeMarkers = []; selectedMarkerElement = null; - (results || []).forEach(asset => { - if (asset.lon === null || asset.lat === null) return; + // 1. Filtrer les assets ponctuels valides + const punctualAssets = (results || []).filter(asset => { + if (asset.lon === null || asset.lat === null) return false; + return !asset.geom_type || asset.geom_type === 'Point' || asset.geom_type === 'MultiPoint'; + }); - // N'afficher un marqueur DOM HTML que pour les géométries ponctuelles - const isPunctual = !asset.geom_type || asset.geom_type === 'Point' || asset.geom_type === 'MultiPoint'; - if (!isPunctual) return; + // 2. Regrouper les assets superposés aux mêmes coordonnées exactes ou sur le même poteau + const locationGroups = []; + const processedSet = new Set(); - const symbology = (typeof AssetSymbology !== 'undefined') - ? AssetSymbology.getAssetSymbology(asset) - : { color: THEMATIC_COLORS[asset.thematic] || '#2f3e8b', iconClass: asset.thematic_icon || 'bi-pin-map' }; - const color = symbology.color || THEMATIC_COLORS[asset.thematic] || '#2f3e8b'; - const iconHtml = (typeof AssetSymbology !== 'undefined') - ? AssetSymbology.renderIconHtml(symbology) - : ``; + punctualAssets.forEach(asset => { + const key = String(asset.feature_id || `${asset.model_key}_${asset.id}`); + if (processedSet.has(key)) return; - const el = document.createElement('div'); - el.className = 'asset-map-marker'; - el.style.borderColor = color; - el.style.color = color; - el.innerHTML = iconHtml; + const group = [asset]; + processedSet.add(key); - el.addEventListener('click', (e) => { - e.stopPropagation(); - if (e.originalEvent) { - e.originalEvent._handledByAssetLayer = true; - } - const matchingAssets = findAssetsAtMarker(asset); - if (matchingAssets.length > 1) { - openMultiBottomSheet(matchingAssets); - } else { - openBottomSheet(asset); + punctualAssets.forEach(other => { + const otherKey = String(other.feature_id || `${other.model_key}_${other.id}`); + if (processedSet.has(otherKey)) return; + + const samePole = (other.pole_id && asset.pole_id && String(other.pole_id) === String(asset.pole_id)) + || (asset.model_key && asset.model_key.includes('pole') && String(other.pole_id) === String(asset.id)) + || (other.model_key && other.model_key.includes('pole') && String(asset.pole_id) === String(other.id)); + + const dLon = Math.abs(other.lon - asset.lon); + const dLat = Math.abs(other.lat - asset.lat); + const isSameLocation = samePole || (dLon < 0.00003 && dLat < 0.00003); + + if (isSameLocation) { + group.push(other); + processedSet.add(otherKey); } }); - const marker = new maplibregl.Marker({ element: el }) - .setLngLat([asset.lon, asset.lat]) - .addTo(map); + locationGroups.push(group); + }); - marker._asset = asset; - marker._element = el; + // 3. Fonction pour déterminer l'asset prioritaire visuellement + function getDisplayPriority(a) { + // 1. Panneau avec visuel SVG/PNG : priorité maximale (100) + if (a.model_key === 'signpanel' && (a.svg_url || a.icon_url || a.panel_type)) return 100; + // 2. Autre panneau de signalisation : 90 + if (a.model_key === 'signpanel') return 90; + // 3. Lanterne tricolore / luminaire : 80 + if (a.model_key === 'trafficlightlantern' || a.model_key === 'publiclightinglightfixture') return 80; + // 4. Autre équipement non-poteau : 70 + if (!a.model_key.includes('pole')) return 70; + // 5. Poteau / mât structural : priorité minimale (10) + return 10; + } - const featId = asset.feature_id || (asset.model_key && asset.id ? `${asset.model_key}_${asset.id}` : null); - if (lastSelectedFeatureId && featId === lastSelectedFeatureId) { - el.classList.add('selected'); - el.style.zIndex = '50'; - selectedMarkerElement = el; + // 4. Créer les marqueurs pour chaque emplacement avec offset vertical pour les panneaux + 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 + if (signPanels.length > 0) { + // 1. S'il y a un poteau ou support à la base, le positionner à l'offset [0, 0] + if (otherAssets.length > 0) { + otherAssets.sort((a, b) => getDisplayPriority(b) - getDisplayPriority(a)); + const baseAsset = otherAssets[0]; + + 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) + : ``; + + const el = document.createElement('div'); + el.className = 'asset-map-marker'; + el.style.borderColor = color; + el.style.color = color; + 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); + } + + el.addEventListener('click', (e) => { + e.stopPropagation(); + if (e.originalEvent) { + e.originalEvent._handledByAssetLayer = true; + } + if (otherAssets.length > 1) { + openMultiBottomSheet(otherAssets); + } else { + openBottomSheet(baseAsset, false, group); + } + }); + + 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'); + el.style.zIndex = '50'; + selectedMarkerElement = el; + } + + activeMarkers.push(marker); + } + + // 2. Calcul de l'offset vertical pour chaque panneau sur le poteau (identique à la version desktop) + 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 hasBaseSupport = otherAssets.length > 0; + const MIN_PANEL_BASE_UP_OFFSET_PX = 36; + const DEFAULT_PANEL_HEIGHT_PX = 32; + const PANEL_STACK_GAP_PX = 2; + + let cumulativeOffset = -MIN_PANEL_BASE_UP_OFFSET_PX; + let previousHeight = DEFAULT_PANEL_HEIGHT_PX; + + signPanels.forEach((panel, index) => { + const renderHeight = DEFAULT_PANEL_HEIGHT_PX; + + if (index === 0) { + if (hasBaseSupport) { + cumulativeOffset = -MIN_PANEL_BASE_UP_OFFSET_PX - Math.max(0, (renderHeight - DEFAULT_PANEL_HEIGHT_PX) / 2); + } else if (signPanels.length > 1) { + cumulativeOffset = 0 - Math.max(0, (renderHeight - DEFAULT_PANEL_HEIGHT_PX) / 2); + } else { + cumulativeOffset = 0; + } + } else { + cumulativeOffset -= (previousHeight + renderHeight) / 2 + PANEL_STACK_GAP_PX; + } + + previousHeight = renderHeight; + + const symbology = (typeof AssetSymbology !== 'undefined') + ? AssetSymbology.getAssetSymbology(panel) + : { color: '#0022ff', iconClass: 'bi-sign-stop-fill' }; + const iconHtml = (typeof AssetSymbology !== 'undefined') + ? AssetSymbology.renderIconHtml(symbology) + : ``; + + const el = document.createElement('div'); + el.className = 'asset-map-marker-sign'; + el.innerHTML = iconHtml; + el.style.zIndex = String(10 + index); + + el.addEventListener('click', (e) => { + e.stopPropagation(); + if (e.originalEvent) { + e.originalEvent._handledByAssetLayer = true; + } + openBottomSheet(panel, false, group); + }); + + const marker = new maplibregl.Marker({ element: el, offset: [0, cumulativeOffset] }) + .setLngLat([panel.lon, panel.lat]) + .addTo(map); + + marker._asset = panel; + 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, etc.) + group.sort((a, b) => getDisplayPriority(b) - getDisplayPriority(a)); + const primaryAsset = group[0]; + + const symbology = (typeof AssetSymbology !== 'undefined') + ? AssetSymbology.getAssetSymbology(primaryAsset) + : { color: THEMATIC_COLORS[primaryAsset.thematic] || '#2f3e8b', iconClass: primaryAsset.thematic_icon || 'bi-pin-map' }; + const color = symbology.color || THEMATIC_COLORS[primaryAsset.thematic] || '#2f3e8b'; + const iconHtml = (typeof AssetSymbology !== 'undefined') + ? AssetSymbology.renderIconHtml(symbology) + : ``; + + const el = document.createElement('div'); + 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(); + if (e.originalEvent) { + e.originalEvent._handledByAssetLayer = true; + } + if (group.length > 1) { + openMultiBottomSheet(group); + } else { + openBottomSheet(primaryAsset, false, group); + } + }); + + const marker = new maplibregl.Marker({ element: el, offset: [0, 0] }) + .setLngLat([primaryAsset.lon, primaryAsset.lat]) + .addTo(map); + + marker._asset = primaryAsset; + marker._group = group; + marker._element = el; + + const isGroupSelected = group.some(a => { + const featId = a.feature_id || (a.model_key && a.id ? `${a.model_key}_${a.id}` : null); + return lastSelectedFeatureId && featId === lastSelectedFeatureId; + }); + if (isGroupSelected) { + el.classList.add('selected'); + el.style.zIndex = '50'; + selectedMarkerElement = el; + } + + activeMarkers.push(marker); } - - activeMarkers.push(marker); }); // Ne jamais modifier la visualisation (zoom ou pan) de manière automatique lors d'une action de filtre ou de recherche @@ -1475,7 +1748,7 @@ } // 5b. Volet d'aperçu individuel (Bottom Sheet) - function openBottomSheet(asset, fromMulti = false) { + function openBottomSheet(asset, fromMulti = false, assetGroup = null) { if (!asset) return; // Fermer le volet multi-assets @@ -1483,12 +1756,14 @@ multiAssetBottomSheet.classList.remove('active'); } - // Bouton retour vers la liste multi si ouvert depuis une sélection multiple + // Bouton retour vers la liste multi si ouvert depuis une sélection multiple ou s'il y a d'autres assets sur le même support + const siblingGroup = (assetGroup && assetGroup.length > 1) ? assetGroup : ((lastMultiAssets && lastMultiAssets.length > 1) ? lastMultiAssets : null); if (btnBackToMultiSheet) { - if (fromMulti && lastMultiAssets && lastMultiAssets.length > 1) { + if (siblingGroup && siblingGroup.length > 1) { + lastMultiAssets = siblingGroup; btnBackToMultiSheet.classList.remove('d-none'); if (btnBackToMultiSheetText) { - btnBackToMultiSheetText.textContent = `{% translate "Retour aux" %} ${lastMultiAssets.length} {% translate "assets à cet endroit" %}`; + btnBackToMultiSheetText.textContent = `{% translate "Voir les" %} ${siblingGroup.length} {% translate "assets à cet endroit" %}`; } } else { btnBackToMultiSheet.classList.add('d-none'); @@ -1519,10 +1794,16 @@ } const currentFeatId = asset.feature_id || (asset.model_key && asset.id ? `${asset.model_key}_${asset.id}` : null); if (currentFeatId) { - const matchingMarker = activeMarkers.find(m => m._asset && ( - String(m._asset.feature_id) === String(currentFeatId) || - (m._asset.model_key === asset.model_key && String(m._asset.id) === String(asset.id)) - )); + const matchingMarker = activeMarkers.find(m => { + if (!m._element) return false; + return m._asset && ( + String(m._asset.feature_id) === String(currentFeatId) || + (m._asset.model_key === asset.model_key && String(m._asset.id) === String(asset.id)) + ); + }) || activeMarkers.find(m => { + if (!m._element) return false; + 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) { selectedMarkerElement = matchingMarker._element; selectedMarkerElement.classList.add('selected'); diff --git a/loko/mobile/tests_asset_mobile.py b/loko/mobile/tests_asset_mobile.py index 1945312..4311528 100644 --- a/loko/mobile/tests_asset_mobile.py +++ b/loko/mobile/tests_asset_mobile.py @@ -533,6 +533,7 @@ class MobileAssetDetailTests(TestCase): street=street, pole=pole, signpanel_type=panel_type, + vertical_order=1, status="active", ) @@ -549,6 +550,9 @@ class MobileAssetDetailTests(TestCase): self.assertEqual(panel_item["panel_type"], "B1") self.assertEqual(panel_item["svg_url"], "/static/assets/road_signs/2025/B1.svg") self.assertEqual(panel_item["icon_url"], "/static/assets/road_signs/2025/B1.svg") + self.assertEqual(panel_item["pole_id"], pole.id) + self.assertEqual(panel_item["vertical_order"], 1) + self.assertEqual(panel_item["panel_vertical_order"], 1) self.assertAlmostEqual(panel_item["lat"], 50.8466, places=4) self.assertAlmostEqual(panel_item["lon"], 4.3528, places=4) self.assertIn("B1", panel_item["name"]) @@ -558,6 +562,9 @@ class MobileAssetDetailTests(TestCase): self.assertEqual(feature["properties"]["panel_type"], "B1") self.assertEqual(feature["properties"]["svg_url"], "/static/assets/road_signs/2025/B1.svg") self.assertEqual(feature["properties"]["icon_url"], "/static/assets/road_signs/2025/B1.svg") + self.assertEqual(feature["properties"]["pole_id"], pole.id) + self.assertEqual(feature["properties"]["vertical_order"], 1) + self.assertEqual(feature["properties"]["panel_vertical_order"], 1) self.assertAlmostEqual(feature["geometry"]["coordinates"][1], 50.8466, places=4) self.assertAlmostEqual(feature["geometry"]["coordinates"][0], 4.3528, places=4) @@ -568,4 +575,63 @@ class MobileAssetDetailTests(TestCase): self.assertEqual(data["count"], 1) self.assertEqual(data["results"][0]["id"], panel.id) + def test_search_multiple_signpanels_on_same_pole_vertical_order(self): + """Multiple panels on the same pole return distinct vertical_order and shared pole_id.""" + user = User.objects.create_user(username="multi_panel_user", password="password") + user_config = UserConfig.objects.create(user=user, is_intern=True) + user_config.roles.add(self.admin_role) + UserThematics.objects.create( + user_config=user_config, + thematic=self.sign_thematic, + can_view_assets=True + ) + + from assets.models import SignPanel, SignPole, SignStreet, SignPanelType + from common.models import Municipality + mun, _ = Municipality.objects.get_or_create(name_fr="Bruxelles", defaults={"name_nl": "Brussel", "niscode": "21004"}) + street = SignStreet.objects.create(name_fr="Rue Royale", municipality=mun) + pole = SignPole.objects.create( + code="POT-MULTI-01", + street=street, + lat=50.8500, + lon=4.3600, + status="active", + ) + type_a, _ = SignPanelType.objects.get_or_create(code="A1a", defaults={"name_fr": "Virage dangereux à droite"}) + type_c, _ = SignPanelType.objects.get_or_create(code="C1", defaults={"name_fr": "Sens interdit"}) + + panel1 = SignPanel.objects.create( + code="PAN-A1a-001", + street=street, + pole=pole, + signpanel_type=type_a, + vertical_order=1, + status="active", + ) + panel2 = SignPanel.objects.create( + code="PAN-C1-002", + street=street, + pole=pole, + signpanel_type=type_c, + vertical_order=2, + status="active", + ) + + self.client.login(username="multi_panel_user", password="password") + search_url = reverse("mobile:mobile_assets_search") + + response = self.client.get(search_url, {"thematic": "sign"}) + self.assertEqual(response.status_code, 200) + data = response.json() + + results_by_id = {r["id"]: r for r in data["results"] if r["model_key"] == "signpanel"} + self.assertIn(panel1.id, results_by_id) + self.assertIn(panel2.id, results_by_id) + + self.assertEqual(results_by_id[panel1.id]["pole_id"], pole.id) + self.assertEqual(results_by_id[panel1.id]["vertical_order"], 1) + self.assertEqual(results_by_id[panel2.id]["pole_id"], pole.id) + self.assertEqual(results_by_id[panel2.id]["vertical_order"], 2) + + diff --git a/loko/mobile/views.py b/loko/mobile/views.py index 6631ef1..7f0f5ab 100644 --- a/loko/mobile/views.py +++ b/loko/mobile/views.py @@ -1370,7 +1370,23 @@ def mobile_assets_search(request): # Filtrage par boîte englobante (bbox / zoom de la carte) if bbox_poly_3812: - if hasattr(model_class, 'geom'): + if model_key == 'signpanel': + panel_geo_q = Q(geom__bboverlaps=bbox_poly_3812) + panel_geo_q |= Q(geom__isnull=True, pole__geom__bboverlaps=bbox_poly_3812) + panel_geo_q |= Q(geom__isnull=True, trafficlight_pole__geom__bboverlaps=bbox_poly_3812) + panel_geo_q |= Q(geom__isnull=True, publiclighting_pole__geom__bboverlaps=bbox_poly_3812) + if min_lon is not None: + panel_geo_q |= Q(lon__gte=min_lon, lon__lte=max_lon, lat__gte=min_lat, lat__lte=max_lat) + panel_geo_q |= Q(lon__isnull=True, pole__lon__gte=min_lon, pole__lon__lte=max_lon, pole__lat__gte=min_lat, pole__lat__lte=max_lat) + qs = qs.filter(panel_geo_q) + elif model_key == 'trafficlightlantern': + tl_geo_q = Q(geom__bboverlaps=bbox_poly_3812) + tl_geo_q |= Q(geom__isnull=True, pole__geom__bboverlaps=bbox_poly_3812) + if min_lon is not None: + tl_geo_q |= Q(lon__gte=min_lon, lon__lte=max_lon, lat__gte=min_lat, lat__lte=max_lat) + tl_geo_q |= Q(lon__isnull=True, pole__lon__gte=min_lon, pole__lon__lte=max_lon, pole__lat__gte=min_lat, pole__lat__lte=max_lat) + qs = qs.filter(tl_geo_q) + elif hasattr(model_class, 'geom'): qs = qs.filter(geom__bboverlaps=bbox_poly_3812) elif min_lon is not None and hasattr(model_class, 'lon') and hasattr(model_class, 'lat'): qs = qs.filter(lon__gte=min_lon, lon__lte=max_lon, lat__gte=min_lat, lat__lte=max_lat) @@ -1421,7 +1437,7 @@ def mobile_assets_search(request): for model_key, model_class, qs in model_querysets: if model_key == 'signpanel' and hasattr(model_class, 'signpanel_type'): - qs = qs.select_related('signpanel_type', 'model', 'pole') + qs = qs.select_related('signpanel_type', 'model', 'pole', 'trafficlight_pole', 'publiclighting_pole') elif model_key == 'trafficlightlantern' and hasattr(model_class, 'model'): qs = qs.select_related('model', 'pole') items = list(qs) @@ -1572,6 +1588,21 @@ def mobile_assets_search(request): structure_type_val = getattr(item, 'structure_type', None) feature_id = f"{model_key}_{item.id}" + pole_id = None + vertical_order = None + panel_width = None + panel_height = None + if model_key == 'signpanel': + pole_id = getattr(item, 'pole_id', None) or getattr(item, 'trafficlight_pole_id', None) or getattr(item, 'publiclighting_pole_id', None) + vertical_order = getattr(item, 'vertical_order', None) + try: + panel_width = item.get_width() + panel_height = item.get_height() + except Exception: + pass + elif hasattr(item, 'pole_id'): + pole_id = getattr(item, 'pole_id', None) + res_item = { 'id': item.id, 'feature_id': feature_id, @@ -1597,6 +1628,11 @@ def mobile_assets_search(request): 'lon': lon, 'distance_m': distance_m, 'detail_url': detail_url, + 'pole_id': pole_id, + 'vertical_order': vertical_order, + 'panel_vertical_order': vertical_order, + 'width': panel_width, + 'height': panel_height, } results.append(res_item) @@ -1631,6 +1667,11 @@ def mobile_assets_search(request): 'detail_url': detail_url, 'lon': lon, 'lat': lat, + 'pole_id': pole_id, + 'vertical_order': vertical_order, + 'panel_vertical_order': vertical_order, + 'width': panel_width, + 'height': panel_height, } })