refactor: restructure map layers for assets and locations, add SRID 3812 projection, and update UI thematic selection logic.
This commit is contained in:
parent
41aa78e382
commit
f41b10bff8
5 changed files with 301 additions and 97 deletions
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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'],
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue