refactor: restructure map layers for assets and locations, add SRID 3812 projection, and update UI thematic selection logic.

This commit is contained in:
kdeterme 2026-08-11 16:27:11 +02:00
parent 41aa78e382
commit f41b10bff8
5 changed files with 301 additions and 97 deletions

View file

@ -97,71 +97,200 @@
} }
map.on('load', function() { map.on('load', function() {
// Source GeoJSON pour les assets // Source GeoJSON pour les assets et localisations
map.addSource('assets-source', { map.addSource('assets-source', {
type: 'geojson', type: 'geojson',
data: { type: 'FeatureCollection', features: [] } data: { type: 'FeatureCollection', features: [] }
}); });
// Couche Polygones (ex: ParkingSpot polygon) // 1. Couche Localisations (Zones GIEP, etc.) - EN DESSOUS DES ASSETS
map.addLayer({
id: 'locations-polygons',
type: 'fill',
source: 'assets-source',
filter: [
'all',
['==', ['coalesce', ['get', 'is_location'], false], true],
['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false]
],
paint: {
'fill-color': '#0094ff',
'fill-opacity': 0.18
}
});
map.addLayer({
id: 'locations-lines',
type: 'line',
source: 'assets-source',
filter: [
'all',
['==', ['coalesce', ['get', 'is_location'], false], true],
['match', ['geometry-type'], ['LineString', 'MultiLineString', 'Polygon', 'MultiPolygon'], true, false]
],
paint: {
'line-color': '#0094ff',
'line-width': 2,
'line-dasharray': [2, 2]
}
});
// 2. Couche Polygones d'Assets (Noue, EVC, OED, TA, etc.) - AU-DESSUS DES LOCALISATIONS
map.addLayer({ map.addLayer({
id: 'assets-polygons', id: 'assets-polygons',
type: 'fill', type: 'fill',
source: 'assets-source', source: 'assets-source',
filter: ['==', '$type', 'Polygon'], filter: [
'all',
['!', ['coalesce', ['get', 'is_location'], false]],
['!=', ['coalesce', ['get', 'model_name'], ''], 'parkingspot'],
['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false]
],
paint: { paint: {
'fill-color': '#0d6efd', 'fill-color': [
'fill-opacity': 0.35, 'match', ['coalesce', ['get', 'structure_type'], ''],
'fill-outline-color': '#0d6efd' ['massif drainant', 'tranchee drainante', 'collecteur en beton poreux', 'empierrement sedimentation', 'oed'], '#8d6e63',
['jardin de pluie', 'noue', 'toiture verte', 'bande filtrante', 'espace vert sur dalle', 'evc'], '#009688',
['bassin sec/infiltration', 'bassin en eau', 'bassin orage', 'citerne', 'toiture stockante', 'SAUL-vide', 'SAUL-terre', 'ta'], '#0277bd',
'#0288d1'
],
'fill-opacity': 0.55
} }
}); });
// Couche Lignes pour le contour des polygones // 3. Couche Polygones de Stationnement (Affichés à partir du Zoom 20 et plus)
map.addLayer({
id: 'parking-polygons',
type: 'fill',
source: 'assets-source',
minzoom: 20,
filter: [
'all',
['!', ['coalesce', ['get', 'is_location'], false]],
['==', ['coalesce', ['get', 'model_name'], ''], 'parkingspot'],
['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false]
],
paint: {
'fill-color': '#0d6efd',
'fill-opacity': 0.55,
'fill-outline-color': '#0a58ca'
}
});
// 4. Couche Lignes d'Assets (LineStrings & contours de polygones d'assets)
map.addLayer({ map.addLayer({
id: 'assets-lines', id: 'assets-lines',
type: 'line', type: 'line',
source: 'assets-source', source: 'assets-source',
filter: ['==', '$type', 'Polygon'], filter: [
'all',
['!', ['coalesce', ['get', 'is_location'], false]],
['match', ['geometry-type'], ['LineString', 'MultiLineString', 'Polygon', 'MultiPolygon'], true, false]
],
paint: { paint: {
'line-color': '#0d6efd', 'line-color': [
'line-width': 2 'match', ['coalesce', ['get', 'structure_type'], ''],
['massif drainant', 'tranchee drainante', 'drain', 'collecteur en beton poreux', 'reseau egout separatif', 'oed'], '#8d6e63',
['noue', 'jardin de pluie', 'bande filtrante', 'evc'], '#009688',
['filet eau', 'caniveau infiltrant', 'gargouille de trottoir', 'avaloir de rail', 'trop plein', 'ta'], '#0277bd',
'#0288d1'
],
'line-width': 2.5
} }
}); });
// Couche Points (ex: Poteaux, Armoires) // 5. Couche Points de Stationnement (1 seul point au centroïde pour Zoom < 20)
map.addLayer({
id: 'parking-points',
type: 'circle',
source: 'assets-source',
maxzoom: 20,
filter: [
'all',
['!', ['coalesce', ['get', 'is_location'], false]],
['==', ['coalesce', ['get', 'model_name'], ''], 'parkingspot'],
['match', ['geometry-type'], ['Point', 'MultiPoint'], true, false]
],
paint: {
'circle-radius': 6,
'circle-color': '#0d6efd',
'circle-stroke-width': 1.5,
'circle-stroke-color': '#ffffff'
}
});
// 6. Couche Points d'Assets généraux (Poteaux, Avaloirs, Regards, etc.)
map.addLayer({ map.addLayer({
id: 'assets-points', id: 'assets-points',
type: 'circle', type: 'circle',
source: 'assets-source', source: 'assets-source',
filter: ['==', '$type', 'Point'], filter: [
'all',
['!', ['coalesce', ['get', 'is_location'], false]],
['!=', ['coalesce', ['get', 'model_name'], ''], 'parkingspot'],
['match', ['geometry-type'], ['Point', 'MultiPoint'], true, false]
],
paint: { paint: {
'circle-radius': 8, 'circle-radius': 7,
'circle-color': '#0d6efd', 'circle-color': [
'match', ['coalesce', ['get', 'structure_type'], ''],
['massif drainant', 'tranchee drainante', 'oed'], '#8d6e63',
['jardin de pluie', 'noue', 'evc'], '#009688',
['bassin orage', 'citerne', 'ta'], '#0277bd',
'#0288d1'
],
'circle-stroke-width': 2, 'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff' 'circle-stroke-color': '#ffffff'
} }
}); });
// Clic sur un asset const interactiveAssetLayers = [
map.on('click', ['assets-polygons', 'assets-points'], function(e) { 'assets-points', 'parking-points', 'assets-lines', 'assets-polygons', 'parking-polygons', 'locations-polygons'
// Si un marker d'inspection est présent à cet endroit, ne pas ouvrir la modale directement ];
// Clic Priorisé sur un Asset / Localisation
map.on('click', interactiveAssetLayers, function(e) {
if (map.getLayer('inspections-history-points')) { if (map.getLayer('inspections-history-points')) {
const inspFeatures = map.queryRenderedFeatures(e.point, { layers: ['inspections-history-points'] }); const inspFeatures = map.queryRenderedFeatures(e.point, { layers: ['inspections-history-points'] });
if (inspFeatures && inspFeatures.length > 0) { if (inspFeatures && inspFeatures.length > 0) {
return; return;
} }
} }
if (e.features && e.features.length > 0) {
const feature = e.features[0]; const renderedFeatures = map.queryRenderedFeatures(e.point, { layers: interactiveAssetLayers });
handleAssetClick(feature); if (!renderedFeatures || renderedFeatures.length === 0) return;
}
// Priorisation : Point (1) > Line (2) > Asset Polygon (3) > Location Polygon (4)
const priorityOrder = {
'Point': 1,
'LineString': 2,
'MultiLineString': 2,
'Polygon': 3,
'MultiPolygon': 3
};
renderedFeatures.sort((a, b) => {
const aIsLoc = a.properties && (a.properties.is_location === true || a.properties.is_location === 'true');
const bIsLoc = b.properties && (b.properties.is_location === true || b.properties.is_location === 'true');
if (!aIsLoc && bIsLoc) return -1;
if (aIsLoc && !bIsLoc) return 1;
const typeA = a.geometry ? a.geometry.type : '';
const typeB = b.geometry ? b.geometry.type : '';
const prioA = priorityOrder[typeA] || 9;
const prioB = priorityOrder[typeB] || 9;
return prioA - prioB;
});
handleAssetClick(renderedFeatures[0]);
}); });
// Survol curseur // Survol curseur
map.on('mouseenter', ['assets-polygons', 'assets-points'], function() { map.on('mouseenter', interactiveAssetLayers, function() {
map.getCanvas().style.cursor = 'pointer'; map.getCanvas().style.cursor = 'pointer';
}); });
map.on('mouseleave', ['assets-polygons', 'assets-points'], function() { map.on('mouseleave', interactiveAssetLayers, function() {
map.getCanvas().style.cursor = ''; map.getCanvas().style.cursor = '';
}); });
@ -173,8 +302,8 @@
// Placer la couche d'inspections EN DESSOUS des couches d'assets // Placer la couche d'inspections EN DESSOUS des couches d'assets
let beforeLayerId = undefined; let beforeLayerId = undefined;
if (map.getLayer('assets-polygons')) { if (map.getLayer('locations-polygons')) {
beforeLayerId = 'assets-polygons'; beforeLayerId = 'locations-polygons';
} else if (map.getLayer('assets-points')) { } else if (map.getLayer('assets-points')) {
beforeLayerId = 'assets-points'; beforeLayerId = 'assets-points';
} }
@ -401,7 +530,7 @@
[sw.lng, sw.lat], [ne.lng, sw.lat], [sw.lng, sw.lat], [ne.lng, sw.lat],
[ne.lng, ne.lat], [sw.lng, ne.lat], [ne.lng, ne.lat], [sw.lng, ne.lat],
[sw.lng, sw.lat], [sw.lng, sw.lat],
]], ]]
}; };
} }
@ -530,7 +659,9 @@
} }
function initFilterStorage() { function initFilterStorage() {
const savedThematic = localStorage.getItem(STORAGE_THEMATIC_KEY) || 'parking'; const firstOpt = document.querySelector('#filter-thematic option:not([value=""])');
const defaultThematic = firstOpt ? firstOpt.value : 'parking';
const savedThematic = localStorage.getItem(STORAGE_THEMATIC_KEY) || defaultThematic;
const savedCategory = localStorage.getItem(STORAGE_CATEGORY_KEY) || ''; const savedCategory = localStorage.getItem(STORAGE_CATEGORY_KEY) || '';
updateUIElements(savedThematic, savedCategory); updateUIElements(savedThematic, savedCategory);
@ -691,7 +822,7 @@
.catch(err => console.warn('Erreur chargement GeoJSON inspections:', err)); .catch(err => console.warn('Erreur chargement GeoJSON inspections:', err));
} }
const MIN_ZOOM_FOR_GEOJSON = 16; const MIN_ZOOM_FOR_GEOJSON = 13;
function loadAssets() { function loadAssets() {
if (!map) return; if (!map) return;
@ -710,18 +841,21 @@
} }
const thematicSelect = document.getElementById('filter-thematic'); const thematicSelect = document.getElementById('filter-thematic');
const thematic = thematicSelect ? thematicSelect.value : ''; let thematic = thematicSelect ? thematicSelect.value : '';
if (!thematic) {
const firstOpt = document.querySelector('#filter-thematic option:not([value=""])');
if (firstOpt) thematic = firstOpt.value;
}
const categorySelect = document.getElementById('filter-category'); const categorySelect = document.getElementById('filter-category');
const categoryId = categorySelect ? categorySelect.value : ''; const categoryId = categorySelect ? categorySelect.value : '';
let fetchUrl = '/assets/parking/assets/geojson/'; let fetchUrls = [`/assets/${thematic || 'parking'}/assets/geojson/`];
if (thematic === 'parking') { if (thematic === 'water' || thematic === 'giep') {
fetchUrl = '/assets/parking/assets/geojson/'; fetchUrls = [
} else if (thematic === 'trafficlights') { '/assets/water/assets/geojson/',
fetchUrl = '/assets/trafficlights/assets/geojson/'; '/assets/water/locations/geojson/'
} else if (thematic === 'publiclighting') { ];
fetchUrl = '/assets/publiclighting/assets/geojson/';
} }
const payload = {}; const payload = {};
@ -734,45 +868,80 @@
payload.filter_geom = { type: 'Feature', geometry: bbox }; payload.filter_geom = { type: 'Feature', geometry: bbox };
} }
fetch(fetchUrl, { const fetchPromises = fetchUrls.map(url =>
method: 'POST', fetch(url, {
body: JSON.stringify(payload), method: 'POST',
headers: { body: JSON.stringify(payload),
'Content-Type': 'application/json', headers: {
'X-CSRFToken': getCsrfToken() 'Content-Type': 'application/json',
} 'X-CSRFToken': getCsrfToken()
}) }
.then(res => { })
if (res.status === 413) { .then(res => (res.ok && res.status !== 413) ? res.json() : null)
if (warningAlert) warningAlert.classList.remove('d-none'); .catch(err => {
console.warn('Erreur chargement assets depuis ' + url, err);
return null; return null;
} })
if (!res.ok) { );
if (warningAlert) warningAlert.classList.remove('d-none');
return null;
}
return res.json();
})
.then(data => {
if (!data) return;
if (data.error) { Promise.all(fetchPromises).then(results => {
if (warningAlert) warningAlert.classList.remove('d-none'); let combinedFeatures = [];
return; results.forEach(res => {
} if (res && res.features && Array.isArray(res.features)) {
combinedFeatures = combinedFeatures.concat(res.features);
}
});
// Pour les parkings : générer 1 seul point centroïde pour l'affichage sous le zoom 20
let processedFeatures = [];
combinedFeatures.forEach(f => {
processedFeatures.push(f);
if (f.properties && (f.properties.model_name === 'parkingspot' || f.properties.thematic === 'parking')) {
if (f.geometry && (f.geometry.type === 'Polygon' || f.geometry.type === 'MultiPolygon')) {
let lon = parseFloat(f.properties.lon);
let lat = parseFloat(f.properties.lat);
if ((isNaN(lon) || isNaN(lat) || lon === 0 || lat === 0) && f.geometry.coordinates) {
try {
const ring = f.geometry.type === 'Polygon' ? f.geometry.coordinates[0] : (f.geometry.coordinates[0] ? f.geometry.coordinates[0][0] : null);
if (ring && ring.length > 0) {
let sumLon = 0, sumLat = 0;
ring.forEach(pt => { sumLon += pt[0]; sumLat += pt[1]; });
lon = sumLon / ring.length;
lat = sumLat / ring.length;
}
} catch (e) {
console.warn('Erreur calcul centroïde parking:', e);
}
}
if (!isNaN(lon) && !isNaN(lat) && lon !== 0 && lat !== 0) {
processedFeatures.push({
type: 'Feature',
id: (f.id || f.properties.id) + '_point',
geometry: {
type: 'Point',
coordinates: [lon, lat]
},
properties: Object.assign({}, f.properties, { is_centroid: true })
});
}
}
}
});
if (warningAlert) warningAlert.classList.add('d-none'); if (warningAlert) warningAlert.classList.add('d-none');
if (data.features) { loadedAssets = processedFeatures;
loadedAssets = data.features; const source = map.getSource('assets-source');
const source = map.getSource('assets-source'); if (source) {
if (source) { source.setData({
source.setData(data); type: 'FeatureCollection',
} features: processedFeatures
});
} }
}) }).catch(err => {
.catch(err => { console.error('Erreur globale loadAssets:', err);
console.error('Error loading asset GeoJSON:', err);
if (warningAlert) warningAlert.classList.remove('d-none'); if (warningAlert) warningAlert.classList.remove('d-none');
}); });
} }
@ -992,20 +1161,30 @@
document.getElementById('insp_content_type_id').value = ctId || ''; document.getElementById('insp_content_type_id').value = ctId || '';
document.getElementById('insp_object_id').value = objId; document.getElementById('insp_object_id').value = objId;
document.getElementById('insp_target_type').textContent = props.asset_type_display || props.model_name || 'Asset'; let modelName = props.model_name || props.location_type || '';
document.getElementById('insp_target_code').textContent = props.target_display || props.code || (objId ? ('#' + objId) : '-'); if (modelName === 'NatureRWIZ' || modelName === 'NatureRWIAsset' || modelName === 'naturerwiasset' || props.thematic === 'water' || props.location_type === 'NatureRWIZ') {
modelName = 'naturerwiz';
}
document.getElementById('insp_target_type').textContent = props.element_type || props.asset_type_display || props.model_name || 'Asset';
document.getElementById('insp_target_code').textContent = props.code || props.target_display || (objId ? ('#' + objId) : '-');
document.getElementById('insp_target_name').textContent = props.name || props.name_fr || props.target_display || '-'; document.getElementById('insp_target_name').textContent = props.name || props.name_fr || props.target_display || '-';
document.getElementById('insp_target_status').textContent = props.latest_result_status_display || props.status_display || props.status || '-'; document.getElementById('insp_target_status').textContent = props.latest_result_status_display || props.status_display || props.status || '-';
const container = document.getElementById('assetSpecificFieldsContainer'); const container = document.getElementById('assetSpecificFieldsContainer');
if (container) { if (container) {
container.innerHTML = ''; container.innerHTML = '';
const modelName = props.model_name || '';
const url = `/inspections/api/partial-form/?content_type_id=${encodeURIComponent(ctId || '')}&object_id=${encodeURIComponent(objId)}&model_name=${encodeURIComponent(modelName)}`; const url = `/inspections/api/partial-form/?content_type_id=${encodeURIComponent(ctId || '')}&object_id=${encodeURIComponent(objId)}&model_name=${encodeURIComponent(modelName)}`;
fetch(url) fetch(url)
.then(res => res.text()) .then(res => res.text())
.then(html => { .then(html => {
container.innerHTML = html; container.innerHTML = html;
Array.from(container.querySelectorAll('script')).forEach(oldScript => {
const newScript = document.createElement('script');
Array.from(oldScript.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value));
newScript.appendChild(document.createTextNode(oldScript.innerHTML));
oldScript.parentNode.replaceChild(newScript, oldScript);
});
}) })
.catch(err => console.error('Error loading inspection fields partial:', err)); .catch(err => console.error('Error loading inspection fields partial:', err));
} }

View file

@ -62,9 +62,10 @@ def parking_assets_geojson(request):
status = data.get('status') status = data.get('status')
filter_geom_raw = data.get("filter_geom") filter_geom_raw = data.get("filter_geom")
if filter_geom_raw: if filter_geom_raw and isinstance(filter_geom_raw, dict):
try: try:
filter_geom = GEOSGeometry(json.dumps(filter_geom_raw['geometry']), srid=4326) geom_json = filter_geom_raw.get('geometry', filter_geom_raw) if filter_geom_raw.get('type') == 'Feature' else filter_geom_raw
filter_geom = GEOSGeometry(json.dumps(geom_json), srid=4326)
filter_geom.transform(3812) filter_geom.transform(3812)
except Exception as e: except Exception as e:
return JsonResponse({"error": f"Invalid geometry: {str(e)}"}, status=400) return JsonResponse({"error": f"Invalid geometry: {str(e)}"}, status=400)

View file

@ -99,27 +99,32 @@ def water_locations_geojson(request):
# Récupération et parsing de la géométrie # Récupération et parsing de la géométrie
filter_geom_raw = data.get("filter_geom") filter_geom_raw = data.get("filter_geom")
if filter_geom_raw: if filter_geom_raw and isinstance(filter_geom_raw, dict):
try: try:
filter_geom = GEOSGeometry(json.dumps(filter_geom_raw['geometry']), srid=4326) geom_json = filter_geom_raw.get('geometry', filter_geom_raw) if filter_geom_raw.get('type') == 'Feature' else filter_geom_raw
filter_geom.transform(3812) filter_geom = GEOSGeometry(json.dumps(geom_json), srid=4326)
filter_geom_3812 = filter_geom.clone()
filter_geom_3812.transform(3812)
except Exception as e: except Exception as e:
return JsonResponse({"error": f"Invalid geometry: {str(e)}"}, status=400) return JsonResponse({"error": f"Invalid geometry: {str(e)}"}, status=400)
else: else:
filter_geom = None filter_geom_3812 = None
else: else:
filter_geom = None filter_geom_3812 = None
rwiz_locations = NatureRWIZ.objects.all() rwiz_locations = NatureRWIZ.objects.all()
if filter_geom: if filter_geom_3812:
rwiz_locations = rwiz_locations.filter(geom__intersects=filter_geom) rwiz_locations = rwiz_locations.filter(geom__intersects=filter_geom_3812)
# Recherche plein texte # Recherche plein texte
if request.method == 'POST': if request.method == 'POST':
from assets.views.core import apply_search from assets.views.core import apply_search
rwiz_locations = apply_search(rwiz_locations, data) rwiz_locations = apply_search(rwiz_locations, data)
from django.contrib.contenttypes.models import ContentType
rwiz_ct_id = ContentType.objects.get_for_model(NatureRWIZ).id
from assets.views.core import get_element_type_label from assets.views.core import get_element_type_label
def obj_to_feature(obj): def obj_to_feature(obj):
geometry = json.loads(obj.geojson) if obj.geojson else None geometry = json.loads(obj.geojson) if obj.geojson else None
@ -128,6 +133,9 @@ def water_locations_geojson(request):
"id": obj.id, "id": obj.id,
"geometry": geometry, "geometry": geometry,
"properties": { "properties": {
"object_id": obj.id,
"content_type_id": rwiz_ct_id,
"model_name": "naturerwiz",
"code": obj.code, "code": obj.code,
"name": obj.get_name(), "name": obj.get_name(),
"location_type": obj.__class__.__name__, "location_type": obj.__class__.__name__,
@ -168,22 +176,25 @@ def water_assets_geojson(request):
# Récupération et parsing de la géométrie # Récupération et parsing de la géométrie
filter_geom_raw = data.get("filter_geom") filter_geom_raw = data.get("filter_geom")
if filter_geom_raw: if filter_geom_raw and isinstance(filter_geom_raw, dict):
try: try:
filter_geom = GEOSGeometry(json.dumps(filter_geom_raw['geometry']), srid=4326) geom_json = filter_geom_raw.get('geometry', filter_geom_raw) if filter_geom_raw.get('type') == 'Feature' else filter_geom_raw
filter_geom = GEOSGeometry(json.dumps(geom_json), srid=4326)
filter_geom_3812 = filter_geom.clone()
filter_geom_3812.transform(3812)
except Exception as e: except Exception as e:
return JsonResponse({"error": f"Invalid geometry: {str(e)}"}, status=400) return JsonResponse({"error": f"Invalid geometry: {str(e)}"}, status=400)
else: else:
filter_geom = None filter_geom_3812 = None
else: else:
section_id = request.GET.get('location') section_id = request.GET.get('location')
sections_ids = [section_id] if section_id else [] sections_ids = [section_id] if section_id else []
statuses = request.GET.getlist('asset_status') statuses = request.GET.getlist('asset_status')
categories = request.GET.getlist('category', []) categories = request.GET.getlist('category', [])
filter_geom = None filter_geom_3812 = None
_search_q = (data.get('q') or '').strip() if request.method == 'POST' else '' _search_q = (data.get('q') or '').strip() if request.method == 'POST' else ''
if not request.GET.get('unified') and not sections_ids and not categories and not filter_geom and not _search_q: if not request.GET.get('unified') and not sections_ids and not categories and not filter_geom_3812 and not _search_q:
return JsonResponse({ return JsonResponse({
"type": "FeatureCollection", "type": "FeatureCollection",
"features": [], "features": [],
@ -197,9 +208,12 @@ def water_assets_geojson(request):
if categories: if categories:
naturerwiasset_qs = naturerwiasset_qs.filter(category__id__in=categories) naturerwiasset_qs = naturerwiasset_qs.filter(category__id__in=categories)
if filter_geom: if filter_geom_3812:
filter_geom_3812 = filter_geom.transform(3812, clone=True) from django.db.models import Q
naturerwiasset_qs = naturerwiasset_qs.filter(geom__intersects=filter_geom_3812) naturerwiasset_qs = naturerwiasset_qs.filter(
Q(geom__intersects=filter_geom_3812) |
Q(geom__isnull=True, location__geom__intersects=filter_geom_3812)
)
if statuses: if statuses:
naturerwiasset_qs = naturerwiasset_qs.filter(status__in=statuses) naturerwiasset_qs = naturerwiasset_qs.filter(status__in=statuses)
@ -214,7 +228,7 @@ def water_assets_geojson(request):
if naturerwiasset_qs.count() > MAX_ASSET_FEATURES: if naturerwiasset_qs.count() > MAX_ASSET_FEATURES:
return JsonResponse({"error": f"{_("Trop d'objets à afficher")} (> {MAX_ASSET_FEATURES})"}, status=413) return JsonResponse({"error": f"{_("Trop d'objets à afficher")} (> {MAX_ASSET_FEATURES})"}, status=413)
from django.db.models import FloatField, Value, CharField from django.db.models import FloatField, Value, CharField, F
from assets.views.core import get_element_type_label from assets.views.core import get_element_type_label
naturerwiasset_qs = (naturerwiasset_qs.annotate( naturerwiasset_qs = (naturerwiasset_qs.annotate(
@ -222,8 +236,8 @@ def water_assets_geojson(request):
category_name=F(f'category__name_{lang}'), category_name=F(f'category__name_{lang}'),
model_name=Value(None, output_field=CharField()), model_name=Value(None, output_field=CharField()),
color=Value(None, output_field=CharField()), color=Value(None, output_field=CharField()),
location_lon=Value(None, output_field=FloatField()), location_lon=F('location__lon'),
location_lat=Value(None, output_field=FloatField()), location_lat=F('location__lat'),
) )
.values( .values(
'id', 'code', 'name', 'status', 'validation_status', 'lon', 'lat', 'surface_area', 'id', 'code', 'name', 'status', 'validation_status', 'lon', 'lat', 'surface_area',
@ -241,8 +255,8 @@ def water_assets_geojson(request):
except (TypeError, ValueError, json.JSONDecodeError): except (TypeError, ValueError, json.JSONDecodeError):
geometry = None geometry = None
lon = obj.get('lon') lon = obj.get('lon') if obj.get('lon') is not None else obj.get('location_lon')
lat = obj.get('lat') lat = obj.get('lat') if obj.get('lat') is not None else obj.get('location_lat')
if geometry is None and lon is not None and lat is not None: if geometry is None and lon is not None and lat is not None:
geometry = { geometry = {
@ -251,6 +265,8 @@ def water_assets_geojson(request):
} }
return geometry, lon, lat return geometry, lon, lat
asset_ct_id = ContentType.objects.get_for_model(NatureRWIAsset).id
def obj_to_feature(obj, asset_type): def obj_to_feature(obj, asset_type):
geometry, lon, lat = build_geometry_and_coords(obj) geometry, lon, lat = build_geometry_and_coords(obj)
detail_url = reverse('assets:water_assets_detail', kwargs={'asset_model': asset_type, 'asset_id': obj['id']}) detail_url = reverse('assets:water_assets_detail', kwargs={'asset_model': asset_type, 'asset_id': obj['id']})
@ -259,6 +275,9 @@ def water_assets_geojson(request):
"id": obj['id'], "id": obj['id'],
"geometry": geometry, "geometry": geometry,
"properties": { "properties": {
"object_id": obj['id'],
"content_type_id": asset_ct_id,
"model_name": "naturerwiasset",
"code": obj['code'], "code": obj['code'],
"name": obj['name'], "name": obj['name'],
"category": obj['category_name'], "category": obj['category_name'],

View file

@ -213,7 +213,7 @@
<select id="filter-thematic" class="d-none"> <select id="filter-thematic" class="d-none">
<option value="">{% translate "-- Thématique --" %}</option> <option value="">{% translate "-- Thématique --" %}</option>
{% for t in thematics %} {% for t in thematics %}
<option value="{{ t.code }}" data-icon="{{ t.icon|default:'bi-folder2' }}" data-name="{{ t.name }}" {% if t.code == 'parking' %}selected{% endif %}>{{ t.name }}</option> <option value="{{ t.code }}" data-icon="{{ t.icon|default:'bi-folder2' }}" data-name="{{ t.name }}" {% if forloop.first %}selected{% endif %}>{{ t.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
@ -256,7 +256,7 @@
<div class="row g-2" id="thematic-tiles-container"> <div class="row g-2" id="thematic-tiles-container">
{% for t in thematics %} {% for t in thematics %}
<div class="col-12 col-sm-6"> <div class="col-12 col-sm-6">
<div class="thematic-tile d-flex align-items-center gap-3 {% if t.code == 'parking' %}active{% endif %}" data-code="{{ t.code }}" data-icon="{{ t.icon|default:'bi-folder2' }}" data-name="{{ t.name }}" data-bs-dismiss="modal"> <div class="thematic-tile d-flex align-items-center gap-3 {% if forloop.first %}active{% endif %}" data-code="{{ t.code }}" data-icon="{{ t.icon|default:'bi-folder2' }}" data-name="{{ t.name }}" data-bs-dismiss="modal">
<div class="thematic-tile-icon" style="background-color: {{ t.color|default:'#0d6efd' }}15; color: {{ t.color|default:'#0d6efd' }};"> <div class="thematic-tile-icon" style="background-color: {{ t.color|default:'#0d6efd' }}15; color: {{ t.color|default:'#0d6efd' }};">
<i class="bi {{ t.icon|default:'bi-folder2' }}"></i> <i class="bi {{ t.icon|default:'bi-folder2' }}"></i>
</div> </div>

View file

@ -478,6 +478,10 @@ def inspections_geojson(request):
if category_id: if category_id:
categories = categories.filter(id=category_id) categories = categories.filter(id=category_id)
ct_ids = {c for c in categories.values_list('allowed_models', flat=True).distinct() if c} ct_ids = {c for c in categories.values_list('allowed_models', flat=True).distinct() if c}
if thematic_code == 'water':
from assets.models.nature import NatureRWIZ, NatureRWIAsset
ct_ids.add(ContentType.objects.get_for_model(NatureRWIZ).id)
ct_ids.add(ContentType.objects.get_for_model(NatureRWIAsset).id)
except Thematic.DoesNotExist: except Thematic.DoesNotExist:
pass pass
@ -497,7 +501,8 @@ def inspections_geojson(request):
asset_qs = ModelClass.objects.filter(geom__isnull=False) asset_qs = ModelClass.objects.filter(geom__isnull=False)
if bbox_geom: if bbox_geom:
asset_qs = asset_qs.filter(geom__intersects=bbox_geom) bbox_geom_3812 = bbox_geom.transform(3812, clone=True)
asset_qs = asset_qs.filter(geom__intersects=bbox_geom_3812)
asset_qs = asset_qs.annotate( asset_qs = asset_qs.annotate(
wgs84_json=AsGeoJSON(Transform(Centroid('geom'), 4326)) wgs84_json=AsGeoJSON(Transform(Centroid('geom'), 4326))