From 25781935144a812da000a026e3b7e571ad9c1557 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Sat, 11 Jul 2026 11:53:15 +0200 Subject: [PATCH 1/6] Feat(Auth): Display public data if user not auth --- backend/configs/public_config.json | 210 ++++++++++++++++++ backend/maps/views.py | 25 ++- webapp/src/app/providers/AuthProvider.tsx | 2 + .../app/providers/map-provider-all4trees.tsx | 4 + 4 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 backend/configs/public_config.json diff --git a/backend/configs/public_config.json b/backend/configs/public_config.json new file mode 100644 index 00000000..6083a6a3 --- /dev/null +++ b/backend/configs/public_config.json @@ -0,0 +1,210 @@ +{ + "layers": [ + { + "id": "satellite", + "type": "xyzservices", + "provider": "Esri.WorldImagery" + }, + { + "id": "villages", + "type": "openmaptiles", + "layer": "place", + "filters": { + "class": "village" + } + }, + { + "id": "towns", + "type": "openmaptiles", + "layer": "place", + "filters": { + "class": "town" + } + }, + { + "id": "boundaries", + "type": "openmaptiles", + "layer": "boundary", + "filters": { + "admin_level": 2 + }, + "style": { + "paint": { + "line-color": "hsl(248,7%,66%)" + } + } + }, + { + "id": "inventaire_for", + "type": "datapackage", + "path": "../catalog/inventaire_for", + "resource": "inv_for", + "layerType": "symbol", + "cluster": { + "radius": 50, + "maxZoom": 12.5, + "steps": [10, 20], + "colors": ["#99D6C2", "#009966", "#006B47"] + }, + "filter": "conf = 1", + "columns": { + "id": "_id", + "geom": "gps.merge().centroid()", + "for": "loc2", + "cod": "cod", + "ecos": "ecos", + "taille_placette": "20^2*pi()/10000", + "project": "proj", + "tree_pop": "count(1 if adu.cond = 1)", + "biomass_volume": "adu.sum(0.0673 * (for_sp.dens * (meas1 + meas2 + meas3 + meas4 + meas5 + meas6 + meas7 + meas8 + meas9 + meas10)^2 * hgt_perp)^0.976 if cond = 1) / (20^2 * pi() / 10000) / 1000", + "tree_density": "count(1 if adu.cond = 1) / (20^2 * pi() / 10000)", + "richness": "count(unique(adu.adu_sp))", + "relative_abundance": "histogram(adu.adu_sp)", + "bio_idx_tree_density": "count(1 if adu.cond = 1) / (20^2 * pi() / 10000) / 100", + "bio_idx_deadWood": "((adu.sum(0.0673 * (for_dw.dens * (meas1 + meas2 + meas3 + meas4 + meas5 + meas6 + meas7 + meas8 + meas9 + meas10)^2 * hgt_perp)^0.976 if cond = 2) + adu.sum(pi() / 3 * ((meas_b / 2)^2 + ((meas_e / 2)^2 + meas_b / 2 + meas_e / 2) * lght * for_dw.dens) if cond = 3)) / adu.sum(0.0673 * (for_sp.dens * (meas1 + meas2 + meas3 + meas4 + meas5 + meas6 + meas7 + meas8 + meas9 + meas10)^2 * hgt_perp)^0.976 if cond = 1)) * 10", + "bio_idx_tree_diversity": "shannon(adu.adu_sp) / 5 * 10", + "bio_idx_spatial_distribution": "adu.categorical_gini(area) * 10", + "bio_idx_diametric_distribution": "adu.gini(meas1 + meas2 + meas3 + meas4 + meas5 + meas6 + meas7 + meas8 + meas9 + meas10) * 10", + "bio_idx_vertical_distribution": "gini(adu.hgt_perp) * 10", + "bio_idx_dominant_height": "adu.avg(hgt_perp if hgt_perp > percentile(hgt_perp, 80)) * 10 / 40", + "bio_idx_microhabitats": "adu.list(trem).flatten().list_unique() * 10 / trem_group_max", + "soil_structure_idx": "(thk1*float(not1) + thk2*float(not2) + thk3*float(not3) + thk4*float(not4) + thk5*float(not5)) / (thk1 + thk2 + thk3 + thk4 + thk5) * 2", + "soil_composition": "count(1)", + "soil_eros_rainfall_and_wind": "for_weath.concat_ws('-', rain, wind)", + "soil_eros_slope": "int(10 - slop if slop < 10 else 0)", + "soil_eros_cover": "veg", + "soil_eros_stability": "(int(slak1) + int(slak2) + int(slak3)) / 3", + "soil_eros_water_infiltration": "600 * 300 / ( (epoch(interval(beer1)) + epoch(interval(beer2)) + epoch(interval(beer3)) + epoch(interval(beer4)) + epoch(interval(beer5)) + epoch(interval(beer6)) + epoch(interval(beer7)) + epoch(interval(beer8)) + epoch(interval(beer9)) + epoch(interval(beer10)) ) / 10 * pi() * 9^2) if beer1 != '' else 0", + "soil_fauna_density": "sum(tsbf_001.tsbf_no) / tsbf_samp", + "soil_fauna_diversity": "list_unique(list(nullif(tsbf_001.concat_ws('-', tsbf_tax1, tsbf_tax2, tsbf_tax3), '')))", + "soil_fauna_abundance": "list_aggregate(list(tsbf_001.concat_ws('-', tsbf_tax1, tsbf_tax2, tsbf_tax3)), 'histogram')", + "soil_surface_fauna_density": "(sum(barba_001.barbA_no) + sum(barbb_001.barbB_no) + sum(barbc_001.barbC_no) + sum(barbd_001.barbD_no)) / barb_samp", + "soil_surface_fauna_diversity": "list_unique(list_concat(list(nullif(barba_001.concat_ws('-', barbA_tax1, barbA_tax2, barbA_tax3), '')), list(nullif(barbb_001.concat_ws('-', barbB_tax1, barbB_tax2, barbB_tax3), '')), list(nullif(barbc_001.concat_ws('-', barbC_tax1, barbC_tax2, barbC_tax3), '')), list(nullif(barbd_001.concat_ws('-', barbD_tax1, barbD_tax2, barbD_tax3), ''))))", + "soil_surface_fauna_abundance": "list_aggregate(list_concat(list(barba_001.concat_ws('-', barbA_tax1, barbA_tax2, barbA_tax3)), list(barbb_001.concat_ws('-', barbB_tax1, barbB_tax2, barbB_tax3)), list(barbc_001.concat_ws('-', barbC_tax1, barbC_tax2, barbC_tax3)), list(barbd_001.concat_ws('-', barbD_tax1, barbD_tax2, barbD_tax3))), 'histogram')" + }, + "popup": { + "trigger": "click" + } + }, + { + "id": "enquete", + "type": "datapackage", + "path": "../catalog/enquete", + "resource": "enquete_menage", + "layerType": "symbol", + "groupby": ["loc2"], + "cluster": { + "radius": 50, + "maxZoom": 12.5, + "steps": [10, 20], + "colors": ["#A1B5F5", "#1447E6", "#0E32A1"] + }, + "filter": "conf = 1", + "columns": { + "geom": "centroid(merge(point(enq_gps.long, enq_gps.latit)))", + "household_nb": "count(_id)", + "population": "sum(res)", + "fuel_sources": "list_aggregate(flatten(list(ener)), 'histogram')", + "wood_fuel_cons": "sum( 12 * ( ifnull(fw_qty * enq_fw.fw_vol * fw_freq, 0) + ifnull(coal_qty * enq_coal.coal_kg * coal_freq * enq_coal.coal_fw, 0) / enq_coal.coal_dens )) / sum(res)", + "firewood_collec_time": "ifnull(avg(coll_freq * coll_dur)/30, 0)", + "firewood_satis": "histogram(wf_satis if int(wf_satis) > 0)", + "firewood_satis_tot": "count(1 if int(wf_satis) > 0)", + "firewood_satis1": "count(1 if wf_satis = '1') / count(1 if int(wf_satis) > 0) * 100", + "firewood_satis2": "count(1 if wf_satis = '2') / count(1 if int(wf_satis) > 0) * 100", + "firewood_satis3": "count(1 if wf_satis = '3') / count(1 if int(wf_satis) > 0) * 100", + "firewood_satis98": "count(1 if wf_satis = '98') / count(1 if int(wf_satis) > 0) * 100", + "firewood_satis99": "count(1 if wf_satis = '99') / count(1 if int(wf_satis) > 0) * 100", + "timber_satis1": "count(1 if timb_satis = '1' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", + "timber_satis2": "count(1 if timb_satis = '2' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", + "timber_satis3": "count(1 if timb_satis = '3' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", + "timber_satis98": "count(1 if timb_satis = '98' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", + "timber_satis99": "count(1 if timb_satis = '99' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", + "timber_satis": "histogram(timb_satis if timb_yn = '1')", + "timber_satis_tot": "count(1 if timb_yn = '1')", + "lean_period": "avg(len(lean) if lean_yn = 1)", + "food_diversity": "list_aggregate(flatten(list(food_cons)), 'histogram')", + "food_diversity_score": "avg(list_unique(food_cons) / int(food_cons_max) * 10)", + "food_self_suff_score": "avg(list_unique(food_self) / list_unique(food_cons) * 100)", + "living_cond_perception": "histogram(cond_evol)", + "revenue_change": "avg(0 - inc_dif if inc_evol = '2' else ifnull(inc_dif, 0))", + "assets_idx": "10 / int(ass_tot_max) * avg( ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + list_unique(ass) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + int( land_own ) + int( land_titl ) + int( land_size ) ) if ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + list_unique(ass) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + int( land_own ) + int( land_titl ) + int( land_size ) ) <= int(ass_tot_max) )", + "nb_income_sources" : "avg(int(inc_no))", + "nb_additional_incomes": "'N/A'", + "sector_benef": "'N/A'", + "sector_support_amount": "'N/A'", + "nb_improved_skills": "'N/A'", + "stak_typ": "'N/A'", + "benef_pract1": "count(1 if int(pract1) = 2) / count(1) * 100", + "benef_pract2": "count(1 if int(pract2) = 2) / count(1) * 100", + "benef_pract3": "count(1 if int(pract3) = 2) / count(1) * 100", + "benef_pract4": "count(1 if int(pract4) = 2) / count(1) * 100", + "benef_pract5": "count(1 if int(pract5) = 2) / count(1) * 100" + }, + "popup": { + "trigger": "click" + } + }, + { + "id": "seed", + "type": "datapackage", + "path": "../catalog/seed/datapackage.json", + "resource": "survey", + "minzoom": 13, + "columns": { + "geom": "geom", + "id": "OGC_FID", + "orga": "votre_orga", + "responsable": "nom_du_res", + "date_plantation": "date_de_pl", + "type_plant": "type_de_pl", + "prelevement_mangrove": "trace_de_p", + "test": "round(random() * 4 ) + 1" + }, + "paint": { + "fill-color": [ + "interpolate", + ["linear"], + ["number", ["get", "test"]], + 1, + "#2DC4B2", + 2, + "#3BB3C3", + 3, + "#669EC4", + 4, + "#A2719B", + 5, + "#AA5E79" + ], + "fill-opacity": 0.8 + }, + "popup": { + "trigger": "click" + } + }, + { + "id": "seed_point", + "type": "datapackage", + "path": "../catalog/seed/datapackage.json", + "resource": "survey", + "columns": { + "geom": "centroid(geom)" + }, + "maxzoom": 13 + } + ], + "controls": [ + { + "type": "compass", + "position": "top-left" + }, + { + "type": "layer", + "position": "top-right" + }, + { + "type": "scale", + "position": "bottom-left" + } + ] +} diff --git a/backend/maps/views.py b/backend/maps/views.py index 8a277f31..f0950c78 100644 --- a/backend/maps/views.py +++ b/backend/maps/views.py @@ -2,30 +2,36 @@ from django.conf import settings from django.http import JsonResponse from django.contrib.auth.decorators import permission_required -from rest_framework.decorators import api_view, permission_classes +from rest_framework.decorators import api_view, permission_classes, authentication_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status +from rest_framework_simplejwt.authentication import JWTAuthentication from . import stats from .datapackage_manager import DatapackageManager config_path = settings.BASE_DIR / "configs" / "config.json" +public_config_path = settings.BASE_DIR / "configs" / "public_config.json" +catalog_path = settings.BASE_DIR / "catalog" map = Map.from_file(config_path) +public_map = Map.from_file(public_config_path) +@api_view(['GET', 'POST']) +@authentication_classes([JWTAuthentication]) def my_map_view(request, subpath): - return JsonResponse( - map.handle_request( + return JsonResponse(get_map(request.user).handle_request( request.method, subpath, request.body, - ) - ) + )) +@api_view(['GET']) +@authentication_classes([JWTAuthentication]) def dashboard_view(request, layer_id): - data = map.handle_request( + data = get_map(request.user).handle_request( 'POST', layer_id, request.body) @@ -39,7 +45,6 @@ def dashboard_view(request, layer_id): }, status=status.HTTP_501_NOT_IMPLEMENTED) - @api_view(["POST"]) @permission_classes([IsAuthenticated]) @permission_required("users.add_data") @@ -97,4 +102,8 @@ def remove_foreign_key_view(request): """ View for adding a foreign key to a DataPackage. """ - return DatapackageManager(request).remove_foreign_key() \ No newline at end of file + return DatapackageManager(request).remove_foreign_key() + + +def get_map(user): + return map if user.is_authenticated else public_map diff --git a/webapp/src/app/providers/AuthProvider.tsx b/webapp/src/app/providers/AuthProvider.tsx index 10d33c2f..5fd3fa0f 100644 --- a/webapp/src/app/providers/AuthProvider.tsx +++ b/webapp/src/app/providers/AuthProvider.tsx @@ -53,6 +53,8 @@ export function AuthProvider({ children }: AuthProviderProps) { localStorage.removeItem("username"); setToken(null); setIsAuthenticated(false); + // Reload page to reinstantiate map at logout + window.location.reload(); }; return ( diff --git a/webapp/src/app/providers/map-provider-all4trees.tsx b/webapp/src/app/providers/map-provider-all4trees.tsx index b31f2a29..7adc6c60 100644 --- a/webapp/src/app/providers/map-provider-all4trees.tsx +++ b/webapp/src/app/providers/map-provider-all4trees.tsx @@ -10,6 +10,7 @@ import { } from "@shared/contexts/map-context-all4trees"; import { useLocalStorage } from "@shared/hooks/use-local-storage"; import { createMap, EVENTS } from "@shared/lib/coordo"; +import { useAuth } from "@features/auth"; const STYLE_URL = `${API_URL}/maps/style.json`; @@ -29,6 +30,8 @@ type MapProviderAll4TreesProps = { export function MapProviderAll4Trees({ children }: MapProviderAll4TreesProps) { const [isReady, setIsReady] = useState(false); + const { isAuthenticated, token } = useAuth(); + const mapApiRef = useRef | null>(null); const [forests, setForests] = useState([]); const [mapSettings, setMapSettings] = useLocalStorage( @@ -79,6 +82,7 @@ export function MapProviderAll4Trees({ children }: MapProviderAll4TreesProps) { renderLayerRow, }, zoom: mapSettings.zoom, + headers: isAuthenticated ? { "Authorization": `Bearer ${token}` } : {} }); if (import.meta.env.DEV) { // biome-ignore lint/suspicious/noExplicitAny : debug only From 43cc58aae5105bd2359685ae77d476c67e4377fe Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Sat, 11 Jul 2026 11:59:31 +0200 Subject: [PATCH 2/6] Fix lint --- webapp/src/app/providers/AuthProvider.tsx | 2 +- webapp/src/app/providers/map-provider-all4trees.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/src/app/providers/AuthProvider.tsx b/webapp/src/app/providers/AuthProvider.tsx index 5fd3fa0f..6b5e3b83 100644 --- a/webapp/src/app/providers/AuthProvider.tsx +++ b/webapp/src/app/providers/AuthProvider.tsx @@ -54,7 +54,7 @@ export function AuthProvider({ children }: AuthProviderProps) { setToken(null); setIsAuthenticated(false); // Reload page to reinstantiate map at logout - window.location.reload(); + window.location.reload(); }; return ( diff --git a/webapp/src/app/providers/map-provider-all4trees.tsx b/webapp/src/app/providers/map-provider-all4trees.tsx index 7adc6c60..51771624 100644 --- a/webapp/src/app/providers/map-provider-all4trees.tsx +++ b/webapp/src/app/providers/map-provider-all4trees.tsx @@ -1,5 +1,6 @@ import { type ReactNode, useCallback, useRef, useState } from "react"; +import { useAuth } from "@features/auth"; import { useCategoriesFilters } from "@features/categories-filters/use-categories-filters"; import { renderAnchor, renderLayerRow } from "@features/controls/layer-control"; @@ -10,7 +11,6 @@ import { } from "@shared/contexts/map-context-all4trees"; import { useLocalStorage } from "@shared/hooks/use-local-storage"; import { createMap, EVENTS } from "@shared/lib/coordo"; -import { useAuth } from "@features/auth"; const STYLE_URL = `${API_URL}/maps/style.json`; @@ -81,8 +81,8 @@ export function MapProviderAll4Trees({ children }: MapProviderAll4TreesProps) { renderAnchor, renderLayerRow, }, + headers: isAuthenticated ? { Authorization: `Bearer ${token}` } : {}, zoom: mapSettings.zoom, - headers: isAuthenticated ? { "Authorization": `Bearer ${token}` } : {} }); if (import.meta.env.DEV) { // biome-ignore lint/suspicious/noExplicitAny : debug only From 74fede0c9eb1b3e21c72d51a7753477598602f89 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Wed, 15 Jul 2026 16:19:09 +0200 Subject: [PATCH 3/6] Use new coordo map model to filter by confidentiality and project --- backend/configs/seed_config.json | 100 +++++++++++++++++++++++++++++++ backend/maps/views.py | 17 ++++-- backend/requirements.txt | 2 +- 3 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 backend/configs/seed_config.json diff --git a/backend/configs/seed_config.json b/backend/configs/seed_config.json new file mode 100644 index 00000000..2333a25d --- /dev/null +++ b/backend/configs/seed_config.json @@ -0,0 +1,100 @@ +{ + "layers": [ + { + "id": "satellite", + "type": "xyzservices", + "provider": "Esri.WorldImagery" + }, + { + "id": "villages", + "type": "openmaptiles", + "layer": "place", + "filters": { + "class": "village" + } + }, + { + "id": "towns", + "type": "openmaptiles", + "layer": "place", + "filters": { + "class": "town" + } + }, + { + "id": "boundaries", + "type": "openmaptiles", + "layer": "boundary", + "filters": { + "admin_level": 2 + }, + "style": { + "paint": { + "line-color": "hsl(248,7%,66%)" + } + } + }, + { + "id": "seed", + "type": "datapackage", + "path": "../catalog/seed/datapackage.json", + "resource": "survey", + "minzoom": 13, + "columns": { + "geom": "geom", + "id": "OGC_FID", + "orga": "votre_orga", + "responsable": "nom_du_res", + "date_plantation": "date_de_pl", + "type_plant": "type_de_pl", + "prelevement_mangrove": "trace_de_p", + "test": "round(random() * 4 ) + 1" + }, + "paint": { + "fill-color": [ + "interpolate", + ["linear"], + ["number", ["get", "test"]], + 1, + "#2DC4B2", + 2, + "#3BB3C3", + 3, + "#669EC4", + 4, + "#A2719B", + 5, + "#AA5E79" + ], + "fill-opacity": 0.8 + }, + "popup": { + "trigger": "click" + } + }, + { + "id": "seed_point", + "type": "datapackage", + "path": "../catalog/seed/datapackage.json", + "resource": "survey", + "columns": { + "geom": "centroid(geom)" + }, + "maxzoom": 13 + } + ], + "controls": [ + { + "type": "compass", + "position": "top-left" + }, + { + "type": "layer", + "position": "top-right" + }, + { + "type": "scale", + "position": "bottom-left" + } + ] +} diff --git a/backend/maps/views.py b/backend/maps/views.py index f0950c78..f7bfd5de 100644 --- a/backend/maps/views.py +++ b/backend/maps/views.py @@ -8,19 +8,19 @@ from rest_framework import status from rest_framework_simplejwt.authentication import JWTAuthentication +from copy import copy from . import stats from .datapackage_manager import DatapackageManager +ALL4TREES_LAYERS = ['inventaire_for', 'enquete'] + config_path = settings.BASE_DIR / "configs" / "config.json" -public_config_path = settings.BASE_DIR / "configs" / "public_config.json" -catalog_path = settings.BASE_DIR / "catalog" map = Map.from_file(config_path) -public_map = Map.from_file(public_config_path) - @api_view(['GET', 'POST']) @authentication_classes([JWTAuthentication]) def my_map_view(request, subpath): + return JsonResponse(get_map(request.user).handle_request( request.method, subpath, @@ -106,4 +106,11 @@ def remove_foreign_key_view(request): def get_map(user): - return map if user.is_authenticated else public_map + user_map = copy(map) + if user.is_authenticated: + filter = f"proj = {user.project}" + else: + filter = 'conf = 1' + + user_map.set_filters(ALL4TREES_LAYERS, filter) + return user_map diff --git a/backend/requirements.txt b/backend/requirements.txt index 927fe2d6..49eaf319 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.8.1 chardet==7.4.3 -coordo @ git+https://github.com/dataforgoodfr/Coordonnees.git@0.7.0#subdirectory=coordo-py +coordo @ git+https://github.com/dataforgoodfr/Coordonnees.git@test-0.8.0#subdirectory=coordo-py Django>=4.2.27 djangorestframework==3.16.0 djangorestframework_simplejwt==5.5.1 From 939463e37b5b73c26469aaab5d8a68d5dfddff79 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Wed, 15 Jul 2026 17:09:46 +0200 Subject: [PATCH 4/6] Correctly filter by project --- backend/maps/views.py | 5 ++++- backend/users/models.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/maps/views.py b/backend/maps/views.py index f7bfd5de..056c81e3 100644 --- a/backend/maps/views.py +++ b/backend/maps/views.py @@ -8,6 +8,7 @@ from rest_framework import status from rest_framework_simplejwt.authentication import JWTAuthentication +from users.models import ADMIN_PROJECT from copy import copy from . import stats from .datapackage_manager import DatapackageManager @@ -108,7 +109,9 @@ def remove_foreign_key_view(request): def get_map(user): user_map = copy(map) if user.is_authenticated: - filter = f"proj = {user.project}" + project = user.project + if (project.lower() != ADMIN_PROJECT): + filter = f"proj = '{project}' or conf = 1" else: filter = 'conf = 1' diff --git a/backend/users/models.py b/backend/users/models.py index 46476488..51585bc5 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import AbstractUser from django.db.models import AutoField, CharField +ADMIN_PROJECT = "all4trees" class CustomUser(AbstractUser): id = AutoField(primary_key=True) project = CharField("projet", max_length=30, blank=True, null=True) From 143108cbc675f1f5d266ff149c2e51954b5092c3 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Wed, 15 Jul 2026 17:14:36 +0200 Subject: [PATCH 5/6] Bump coordo version --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 49eaf319..68fbf144 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.8.1 chardet==7.4.3 -coordo @ git+https://github.com/dataforgoodfr/Coordonnees.git@test-0.8.0#subdirectory=coordo-py +coordo @ git+https://github.com/dataforgoodfr/Coordonnees.git@0.8.0#subdirectory=coordo-py Django>=4.2.27 djangorestframework==3.16.0 djangorestframework_simplejwt==5.5.1 From 801d0fe305bd30fdbecc5d6c139703607b17e099 Mon Sep 17 00:00:00 2001 From: Arnaud Fournier Date: Sat, 18 Jul 2026 14:26:11 +0200 Subject: [PATCH 6/6] fix review --- .../{config.json => all4trees_config.json} | 0 backend/configs/public_config.json | 210 ------------------ backend/maps/views.py | 2 +- 3 files changed, 1 insertion(+), 211 deletions(-) rename backend/configs/{config.json => all4trees_config.json} (100%) delete mode 100644 backend/configs/public_config.json diff --git a/backend/configs/config.json b/backend/configs/all4trees_config.json similarity index 100% rename from backend/configs/config.json rename to backend/configs/all4trees_config.json diff --git a/backend/configs/public_config.json b/backend/configs/public_config.json deleted file mode 100644 index 6083a6a3..00000000 --- a/backend/configs/public_config.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "layers": [ - { - "id": "satellite", - "type": "xyzservices", - "provider": "Esri.WorldImagery" - }, - { - "id": "villages", - "type": "openmaptiles", - "layer": "place", - "filters": { - "class": "village" - } - }, - { - "id": "towns", - "type": "openmaptiles", - "layer": "place", - "filters": { - "class": "town" - } - }, - { - "id": "boundaries", - "type": "openmaptiles", - "layer": "boundary", - "filters": { - "admin_level": 2 - }, - "style": { - "paint": { - "line-color": "hsl(248,7%,66%)" - } - } - }, - { - "id": "inventaire_for", - "type": "datapackage", - "path": "../catalog/inventaire_for", - "resource": "inv_for", - "layerType": "symbol", - "cluster": { - "radius": 50, - "maxZoom": 12.5, - "steps": [10, 20], - "colors": ["#99D6C2", "#009966", "#006B47"] - }, - "filter": "conf = 1", - "columns": { - "id": "_id", - "geom": "gps.merge().centroid()", - "for": "loc2", - "cod": "cod", - "ecos": "ecos", - "taille_placette": "20^2*pi()/10000", - "project": "proj", - "tree_pop": "count(1 if adu.cond = 1)", - "biomass_volume": "adu.sum(0.0673 * (for_sp.dens * (meas1 + meas2 + meas3 + meas4 + meas5 + meas6 + meas7 + meas8 + meas9 + meas10)^2 * hgt_perp)^0.976 if cond = 1) / (20^2 * pi() / 10000) / 1000", - "tree_density": "count(1 if adu.cond = 1) / (20^2 * pi() / 10000)", - "richness": "count(unique(adu.adu_sp))", - "relative_abundance": "histogram(adu.adu_sp)", - "bio_idx_tree_density": "count(1 if adu.cond = 1) / (20^2 * pi() / 10000) / 100", - "bio_idx_deadWood": "((adu.sum(0.0673 * (for_dw.dens * (meas1 + meas2 + meas3 + meas4 + meas5 + meas6 + meas7 + meas8 + meas9 + meas10)^2 * hgt_perp)^0.976 if cond = 2) + adu.sum(pi() / 3 * ((meas_b / 2)^2 + ((meas_e / 2)^2 + meas_b / 2 + meas_e / 2) * lght * for_dw.dens) if cond = 3)) / adu.sum(0.0673 * (for_sp.dens * (meas1 + meas2 + meas3 + meas4 + meas5 + meas6 + meas7 + meas8 + meas9 + meas10)^2 * hgt_perp)^0.976 if cond = 1)) * 10", - "bio_idx_tree_diversity": "shannon(adu.adu_sp) / 5 * 10", - "bio_idx_spatial_distribution": "adu.categorical_gini(area) * 10", - "bio_idx_diametric_distribution": "adu.gini(meas1 + meas2 + meas3 + meas4 + meas5 + meas6 + meas7 + meas8 + meas9 + meas10) * 10", - "bio_idx_vertical_distribution": "gini(adu.hgt_perp) * 10", - "bio_idx_dominant_height": "adu.avg(hgt_perp if hgt_perp > percentile(hgt_perp, 80)) * 10 / 40", - "bio_idx_microhabitats": "adu.list(trem).flatten().list_unique() * 10 / trem_group_max", - "soil_structure_idx": "(thk1*float(not1) + thk2*float(not2) + thk3*float(not3) + thk4*float(not4) + thk5*float(not5)) / (thk1 + thk2 + thk3 + thk4 + thk5) * 2", - "soil_composition": "count(1)", - "soil_eros_rainfall_and_wind": "for_weath.concat_ws('-', rain, wind)", - "soil_eros_slope": "int(10 - slop if slop < 10 else 0)", - "soil_eros_cover": "veg", - "soil_eros_stability": "(int(slak1) + int(slak2) + int(slak3)) / 3", - "soil_eros_water_infiltration": "600 * 300 / ( (epoch(interval(beer1)) + epoch(interval(beer2)) + epoch(interval(beer3)) + epoch(interval(beer4)) + epoch(interval(beer5)) + epoch(interval(beer6)) + epoch(interval(beer7)) + epoch(interval(beer8)) + epoch(interval(beer9)) + epoch(interval(beer10)) ) / 10 * pi() * 9^2) if beer1 != '' else 0", - "soil_fauna_density": "sum(tsbf_001.tsbf_no) / tsbf_samp", - "soil_fauna_diversity": "list_unique(list(nullif(tsbf_001.concat_ws('-', tsbf_tax1, tsbf_tax2, tsbf_tax3), '')))", - "soil_fauna_abundance": "list_aggregate(list(tsbf_001.concat_ws('-', tsbf_tax1, tsbf_tax2, tsbf_tax3)), 'histogram')", - "soil_surface_fauna_density": "(sum(barba_001.barbA_no) + sum(barbb_001.barbB_no) + sum(barbc_001.barbC_no) + sum(barbd_001.barbD_no)) / barb_samp", - "soil_surface_fauna_diversity": "list_unique(list_concat(list(nullif(barba_001.concat_ws('-', barbA_tax1, barbA_tax2, barbA_tax3), '')), list(nullif(barbb_001.concat_ws('-', barbB_tax1, barbB_tax2, barbB_tax3), '')), list(nullif(barbc_001.concat_ws('-', barbC_tax1, barbC_tax2, barbC_tax3), '')), list(nullif(barbd_001.concat_ws('-', barbD_tax1, barbD_tax2, barbD_tax3), ''))))", - "soil_surface_fauna_abundance": "list_aggregate(list_concat(list(barba_001.concat_ws('-', barbA_tax1, barbA_tax2, barbA_tax3)), list(barbb_001.concat_ws('-', barbB_tax1, barbB_tax2, barbB_tax3)), list(barbc_001.concat_ws('-', barbC_tax1, barbC_tax2, barbC_tax3)), list(barbd_001.concat_ws('-', barbD_tax1, barbD_tax2, barbD_tax3))), 'histogram')" - }, - "popup": { - "trigger": "click" - } - }, - { - "id": "enquete", - "type": "datapackage", - "path": "../catalog/enquete", - "resource": "enquete_menage", - "layerType": "symbol", - "groupby": ["loc2"], - "cluster": { - "radius": 50, - "maxZoom": 12.5, - "steps": [10, 20], - "colors": ["#A1B5F5", "#1447E6", "#0E32A1"] - }, - "filter": "conf = 1", - "columns": { - "geom": "centroid(merge(point(enq_gps.long, enq_gps.latit)))", - "household_nb": "count(_id)", - "population": "sum(res)", - "fuel_sources": "list_aggregate(flatten(list(ener)), 'histogram')", - "wood_fuel_cons": "sum( 12 * ( ifnull(fw_qty * enq_fw.fw_vol * fw_freq, 0) + ifnull(coal_qty * enq_coal.coal_kg * coal_freq * enq_coal.coal_fw, 0) / enq_coal.coal_dens )) / sum(res)", - "firewood_collec_time": "ifnull(avg(coll_freq * coll_dur)/30, 0)", - "firewood_satis": "histogram(wf_satis if int(wf_satis) > 0)", - "firewood_satis_tot": "count(1 if int(wf_satis) > 0)", - "firewood_satis1": "count(1 if wf_satis = '1') / count(1 if int(wf_satis) > 0) * 100", - "firewood_satis2": "count(1 if wf_satis = '2') / count(1 if int(wf_satis) > 0) * 100", - "firewood_satis3": "count(1 if wf_satis = '3') / count(1 if int(wf_satis) > 0) * 100", - "firewood_satis98": "count(1 if wf_satis = '98') / count(1 if int(wf_satis) > 0) * 100", - "firewood_satis99": "count(1 if wf_satis = '99') / count(1 if int(wf_satis) > 0) * 100", - "timber_satis1": "count(1 if timb_satis = '1' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", - "timber_satis2": "count(1 if timb_satis = '2' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", - "timber_satis3": "count(1 if timb_satis = '3' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", - "timber_satis98": "count(1 if timb_satis = '98' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", - "timber_satis99": "count(1 if timb_satis = '99' and timb_yn = '1') / count(1 if timb_yn = '1') * 100", - "timber_satis": "histogram(timb_satis if timb_yn = '1')", - "timber_satis_tot": "count(1 if timb_yn = '1')", - "lean_period": "avg(len(lean) if lean_yn = 1)", - "food_diversity": "list_aggregate(flatten(list(food_cons)), 'histogram')", - "food_diversity_score": "avg(list_unique(food_cons) / int(food_cons_max) * 10)", - "food_self_suff_score": "avg(list_unique(food_self) / list_unique(food_cons) * 100)", - "living_cond_perception": "histogram(cond_evol)", - "revenue_change": "avg(0 - inc_dif if inc_evol = '2' else ifnull(inc_dif, 0))", - "assets_idx": "10 / int(ass_tot_max) * avg( ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + list_unique(ass) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + int( land_own ) + int( land_titl ) + int( land_size ) ) if ( ( int(hous_typ) + int(hous_size)) * int(hous_own) + list_unique(ass) + int(lvst_yn) * ( int(lvst_s) + int(lvst_l) ) + int( land_own ) + int( land_titl ) + int( land_size ) ) <= int(ass_tot_max) )", - "nb_income_sources" : "avg(int(inc_no))", - "nb_additional_incomes": "'N/A'", - "sector_benef": "'N/A'", - "sector_support_amount": "'N/A'", - "nb_improved_skills": "'N/A'", - "stak_typ": "'N/A'", - "benef_pract1": "count(1 if int(pract1) = 2) / count(1) * 100", - "benef_pract2": "count(1 if int(pract2) = 2) / count(1) * 100", - "benef_pract3": "count(1 if int(pract3) = 2) / count(1) * 100", - "benef_pract4": "count(1 if int(pract4) = 2) / count(1) * 100", - "benef_pract5": "count(1 if int(pract5) = 2) / count(1) * 100" - }, - "popup": { - "trigger": "click" - } - }, - { - "id": "seed", - "type": "datapackage", - "path": "../catalog/seed/datapackage.json", - "resource": "survey", - "minzoom": 13, - "columns": { - "geom": "geom", - "id": "OGC_FID", - "orga": "votre_orga", - "responsable": "nom_du_res", - "date_plantation": "date_de_pl", - "type_plant": "type_de_pl", - "prelevement_mangrove": "trace_de_p", - "test": "round(random() * 4 ) + 1" - }, - "paint": { - "fill-color": [ - "interpolate", - ["linear"], - ["number", ["get", "test"]], - 1, - "#2DC4B2", - 2, - "#3BB3C3", - 3, - "#669EC4", - 4, - "#A2719B", - 5, - "#AA5E79" - ], - "fill-opacity": 0.8 - }, - "popup": { - "trigger": "click" - } - }, - { - "id": "seed_point", - "type": "datapackage", - "path": "../catalog/seed/datapackage.json", - "resource": "survey", - "columns": { - "geom": "centroid(geom)" - }, - "maxzoom": 13 - } - ], - "controls": [ - { - "type": "compass", - "position": "top-left" - }, - { - "type": "layer", - "position": "top-right" - }, - { - "type": "scale", - "position": "bottom-left" - } - ] -} diff --git a/backend/maps/views.py b/backend/maps/views.py index 056c81e3..e7e53617 100644 --- a/backend/maps/views.py +++ b/backend/maps/views.py @@ -15,7 +15,7 @@ ALL4TREES_LAYERS = ['inventaire_for', 'enquete'] -config_path = settings.BASE_DIR / "configs" / "config.json" +config_path = settings.BASE_DIR / "configs" / "all4trees_config.json" map = Map.from_file(config_path) @api_view(['GET', 'POST'])