Méthodologie de Production
Itérations pour mettre en place une étude territoriale Alteris
- Les trames du territoire à travers OSM
- 0_Résumé et commandes CLI du GIS Alteris
- 01_Actualisations des bases de données Geometries dans PostGIS (OSM, IRIS)
- 02/ Inventaire du Groupe Public de PostGIS
- Les données foncières (DVF)
- Vedène -
- Exemple sur fulldvf France de 2020 à 2025
- Etude Bandol - apprentissage de renseignement des indicateurs
- Les statistiques INSEE RGP Communes et IRIS
- L'extraction OSM-raw à un OSM_territoire d'étude
- Carnet des commandes itératives
- 260407-contenu des tables OSM dans OSM-raw
- 260407-Extraction OSM réussie sur Bandol
- Les statistiques Sitadel
- Données Filosofi - téléchargement
Les trames du territoire à travers OSM
02 - Première trame - les filaires
Renvoi à cette fiche d'exemple - cas de Toulon - en mars 2026
Vérification de la présence des tags maxspped dans la version OSM Geofabrick
sudo docker exec -it alteris_postgis psql -U alteris_admin -d alteris_geo -c "SELECT count(*) FROM osm_raw.roads WHERE tags ? 'maxspeed';"
-------
91439
(1 row)
A/ Creer les filaires
c13028_laciotat.c13028_filaires
sudo docker exec -it alteris_postgis psql -U alteris_admin -d alteris_geo -c "
-- 1. Nettoyage
DROP TABLE IF EXISTS c13028_laciotat.c13028_filaires_v3 CASCADE;
-- 2. Création de la couche avec ta logique 'Toulon'
CREATE TABLE c13028_laciotat.c13028_filaires_v3 AS
WITH extraction AS (
SELECT
osm_id,
name AS nom_voie,
highway AS type_osm,
tags->'lanes' AS nb_voies,
tags->'maxspeed' AS vitesse_max,
ST_Transform(way, 2154) AS geom_ligne
FROM osm_raw.roads
WHERE highway IS NOT NULL
AND ST_Intersects(way, (SELECT ST_Union(way) FROM osm_raw.buildings WHERE name = 'La Ciotat' AND admin_level = '8'))
),
classification AS (
SELECT
osm_id, nom_voie, type_osm, geom_ligne,
CASE
-- Transit Lourd (T1 & T2)
WHEN type_osm IN ('motorway', 'motorway_link') THEN 'F_T1'
WHEN type_osm IN ('trunk', 'trunk_link') THEN 'F_T2'
-- Transit Urbain (T3) : La complexité que tu voulais garder
WHEN type_osm IN ('primary', 'secondary')
AND (nb_voies IN ('3', '4', '5', '6', '7', '8')
OR vitesse_max IN ('70', '80', '90', '110', '130'))
THEN 'F_T3'
-- Desserte (D1, D2, D3)
WHEN type_osm = 'primary' THEN 'F_D1'
WHEN type_osm = 'secondary' THEN 'F_D2'
WHEN type_osm IN ('pedestrian', 'living_street') THEN 'F_D3'
-- Local (L1, L2, L3)
WHEN type_osm IN ('tertiary', 'residential') THEN 'F_L1'
WHEN type_osm = 'unclassified' THEN 'F_L2'
ELSE 'F_L3'
END as code_pattern
FROM extraction
)
SELECT
osm_id,
nom_voie,
code_pattern,
ST_Buffer(geom_ligne,
CASE
WHEN code_pattern = 'F_T1' THEN 100
WHEN code_pattern = 'F_T2' THEN 75
WHEN code_pattern = 'F_T3' THEN 50
WHEN code_pattern = 'F_D1' THEN 50
WHEN code_pattern = 'F_D2' THEN 35
WHEN code_pattern = 'F_D3' THEN 35
WHEN code_pattern = 'F_L1' THEN 25
ELSE 10
END
)::geometry(Polygon, 2154) as geom
FROM classification;
-- 3. Indexation spatiale
CREATE INDEX idx_c13028_filaires_v3_geom ON c13028_laciotat.c13028_filaires_v3 USING GIST(geom);
"
sudo docker exec -it alteris_postgis psql -U alteris_admin -d alteris_geo -c "
SELECT code_pattern, count(*)
FROM c13028_laciotat.c13028_filaires_v3
GROUP BY code_pattern
ORDER BY code_pattern;"
code_pattern | count
--------------+-------
F_D1 | 96
F_D2 | 179
F_D3 | 67
F_L1 | 1129
F_L2 | 148
F_L3 | 3302
F_T1 | 40
F_T3 | 42
(8 rows)
0_Résumé et commandes CLI du GIS Alteris
1/ PostGIS sur le VPS d'Alteris est un containeur Docker
Ses caractéristiques
POSTGRES_USER: alteris_admin
POSTGRES_PASSWORD: Alteris2026
POSTGRES_DB: alteris_geo
POSTGRES_CONTAINEUR alteris_postgis
2/ Commande CLI pour lister les bases de données contenues dans le PostGIS
sudo docker exec -it alteris_postgis psql -U alteris_admin -d alteris_geo -c "
SELECT
relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS taille,
n_live_tup AS nb_lignes,
(SELECT type FROM geometry_columns WHERE f_table_name = relname LIMIT 1) as geom_type
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(relid) DESC;"
3/ commande cli pour obtenir la taille d'un répertoire
debian@vps-02c1f983-vps-ovh-net:~$ # -s (summary) -h (human readable)
du -sh /home/debian/data/Regions/93/13/200054807/13028/
245M /home/debian/data/Regions/93/13/200054807/13028/
debian@vps-02c1f983-vps-ovh-net:~$
| Fichier / Dossier | Taille | Statut Alteris |
carreaux_200m_met.gpkg |
1,1 Go | À garder. C'est le carroyage Insee (Filosofi) indispensable pour tes analyses de population à fine échelle. |
Base Adresse Paca.gpkg |
635 Mo | À garder. C'est la BAN (Base Adresse Nationale), cruciale pour le géocodage et la précision des bâtiments. |
Recensement 13 Insee.csv |
230 Mo | À vérifier. C'est le détail du recensement pour le département 13. |
du -ah /home/debian/data/Regions/93 | sort -rh | head -n 15
debian@vps-02c1f983-vps-ovh-net:~$ du -ah /home/debian/data/Regions/93 | sort -rh | head -n 15
2.3G /home/debian/data/Regions/93
1.1G /home/debian/data/Regions/93/carreaux_200m_met.gpkg
646M /home/debian/data/Regions/93/BDTOPO V1
635M /home/debian/data/Regions/93/BDTOPO V1/Base Adresse Paca.gpkg
529M /home/debian/data/Regions/93/13
246M /home/debian/data/Regions/93/13/200054807
245M /home/debian/data/Regions/93/13/200054807/13028
230M /home/debian/data/Regions/93/13/Recensement 13 Insee.csv
184M /home/debian/data/Regions/93/13/200054807/13028/lidar
59M /home/debian/data/Regions/93/13/200054807/13028/Archives/Etude_LaCiotat_2026-04-02.gpkg
59M /home/debian/data/Regions/93/13/200054807/13028/Archives
25M /home/debian/data/Regions/93/13/MobPro13.csv
19M /home/debian/data/Regions/93/13/Filosofi 13.gpkg
16M /home/debian/data/Regions/93/13/200054807/13028/lidar/LHD_FXX_0914_6237_MNT_O_0M50_LAMB93_IGN69.tif
16M /home/debian/data/Regions/93/13/200054807/13028/lidar/LHD_FXX_0913_6237_MNT_O_0M50_LAMB93_IGN69.tif
debian@vps-02c1f983-vps-ovh-net:~$
01_Actualisations des bases de données Geometries dans PostGIS (OSM, IRIS)
1/ actualisation OSM - exercice du 260402
provence-alpes-cote 100%[===================>] 363.10M 34.6MB/s in 11s
# 1. Création de l'arborescence (le -p crée les parents si besoin)
mkdir -p /home/debian/data/Regions/93/OSM_93
# 2. On se déplace dans le dossier cible
cd /home/debian/data/Regions/93/OSM_93
# 3. Téléchargement du fichier de la Région PACA
# Note : C'est le fichier qui contient La Ciotat
wget https://download.geofabrik.de/europe/france/provence-alpes-cote-d-azur-latest.osm.pbf
Pour éviter de retomber sur l'erreur column "highway" does not exist, nous allons utiliser une commande osm2pgsql qui force l'extraction des colonnes standards (style par défaut) et active le hstore (pour stocker tous les autres tags dans un champ flexible).
Nécessité d'installer osm2pgsql directement dans le VPS car outil indisponible sur Docker
https://osm2pgsql.org/ (version 1.8.0)
2/ SCRIPT DE TRANSFERT OSM PACA VERS PUBLIC DANS POSTGIS
compter 6 minutes.
osm2pgsql -d alteris_geo -U alteris_admin -H localhost -P 5432 -W \
--slim \
--hstore \
--multi-geometry \
--number-processes 2 \
/home/debian/data/Regions/93/OSM_93/provence-alpes-cote-d-azur-latest.osm.pbf
1. La Source (Le fichier .pbf)
Tu as bien fait de télécharger sur Geofabrik. C'est le dépôt de référence qui découpe la "Planet OSM" (le monde entier) en morceaux digestes (comme la Région PACA). Ton fichier /home/debian/data/Regions/93/OSM_93/provence-alpes-cote-d-azur-latest.osm.pbf est parfait.
2. L'Outil (Le traducteur)
L'image Docker osm2pgsql/osm2pgsql va lire ce fichier et "traduire" les données OpenStreetMap (points, lignes, polygones) en tables SQL exploitables par Alteris.
3. La Destination (Les tables dans PostgreSQL)
Par convention historique, cet outil nomme toujours les tables créées avec le préfixe planet_osm_. C'est là que tu retrouveras :
-
planet_osm_line(qui deviendra ton réservoir pour les filaires). -
planet_osm_point(tes futurs équipements/écoles). -
planet_osm_polygon(tes bâtis et zones végétalisées).
Résumé du flux de travail Alteris
-
Extraction : Tu as récupéré le PBF chez Geofabrik.
-
Injection : Tu lances la commande Docker
osm2pgsql. -
Rangement : On renomme
planet_osm_lineenosm_raw.roads(pour que ton script de filaires fonctionne).
Résultat du transfert pour comparaison
debian@vps-02c1f983-vps-ovh-net:~/data/Regions/93/OSM_93$ sudo docker exec -it a lteris_postgis psql -U alteris_admin -d alteris_geo -c "CREATE EXTENSION IF NOT EXISTS hstore;"
CREATE EXTENSION
debian@vps-02c1f983-vps-ovh-net:~/data/Regions/93/OSM_93$ osm2pgsql -d alteris_g eo -U alteris_admin -H localhost -P 5432 -W \
--slim \
--hstore \
--multi-geometry \
--number-processes 2 \
/home/debian/data/Regions/93/OSM_93/provence-alpes-cote-d-azur-latest.osm.pbf
2026-04-02 07:57:46 osm2pgsql version 1.8.0
Password:
2026-04-02 07:57:54 Database version: 15.4 (Debian 15.4-1.pgdg110+1)
2026-04-02 07:57:54 PostGIS version: 3.3
2026-04-02 07:57:54 Setting up table 'planet_osm_point'
2026-04-02 07:57:54 Setting up table 'planet_osm_line'
2026-04-02 07:57:54 Setting up table 'planet_osm_polygon'
2026-04-02 07:57:54 Setting up table 'planet_osm_roads'
2026-04-02 08:01:35 Reading input files done in 221s (3m 41s).
2026-04-02 08:01:35 Processed 39612462 nodes in 75s (1m 15s) - 528k/s
2026-04-02 08:01:35 Processed 5322338 ways in 108s (1m 48s) - 49k/s
2026-04-02 08:01:35 Processed 52147 relations in 38s - 1k/s
2026-04-02 08:01:35 Clustering table 'planet_osm_line' by geometry...
2026-04-02 08:01:35 Clustering table 'planet_osm_point' by geometry...
2026-04-02 08:01:42 Creating geometry index on table 'planet_osm_point'...
2026-04-02 08:01:44 Creating osm_id index on table 'planet_osm_point'...
2026-04-02 08:01:45 Analyzing table 'planet_osm_point'...
2026-04-02 08:01:45 Clustering table 'planet_osm_polygon' by geometry...
2026-04-02 08:01:48 Creating geometry index on table 'planet_osm_line'...
2026-04-02 08:01:51 Creating osm_id index on table 'planet_osm_line'...
2026-04-02 08:01:53 Analyzing table 'planet_osm_line'...
2026-04-02 08:01:53 Clustering table 'planet_osm_roads' by geometry...
2026-04-02 08:01:55 Creating geometry index on table 'planet_osm_roads'...
2026-04-02 08:01:55 Creating osm_id index on table 'planet_osm_roads'...
2026-04-02 08:01:55 Analyzing table 'planet_osm_roads'...
2026-04-02 08:01:56 Done postprocessing on table 'planet_osm_nodes' in 0s
2026-04-02 08:01:56 Building index on table 'planet_osm_ways'
2026-04-02 08:02:11 Creating geometry index on table 'planet_osm_polygon'...
2026-04-02 08:02:21 Creating osm_id index on table 'planet_osm_polygon'...
2026-04-02 08:02:23 Analyzing table 'planet_osm_polygon'...
2026-04-02 08:02:23 Building index on table 'planet_osm_rels'
2026-04-02 08:03:14 Done postprocessing on table 'planet_osm_ways' in 78s (1m 18s)
2026-04-02 08:03:14 Done postprocessing on table 'planet_osm_rels' in 2s
2026-04-02 08:03:14 All postprocessing on table 'planet_osm_point' done in 10s.
2026-04-02 08:03:14 All postprocessing on table 'planet_osm_line' done in 17s.
2026-04-02 08:03:14 All postprocessing on table 'planet_osm_polygon' done in 37s.
2026-04-02 08:03:14 All postprocessing on table 'planet_osm_roads' done in 2s.
2026-04-02 08:03:14 osm2pgsql took 319s (5m 19s) overall.
debian@vps-02c1f983-vps-ovh-net:~/data/Regions/93/OSM_93$
3/ BASCULEMENT OSM PACA VERS OSM_RAW DANS POSTGIS
sudo docker exec -it alteris_postgis psql -U alteris_admin -d alteris_geo -c "
-- 1. On s'assure que le schéma cible existe
CREATE SCHEMA IF NOT EXISTS osm_raw;
-- 2. On supprime les vieux restes pour éviter les conflits
DROP TABLE IF EXISTS osm_raw.roads CASCADE;
DROP TABLE IF EXISTS osm_raw.points CASCADE;
DROP TABLE IF EXISTS osm_raw.buildings CASCADE;
DROP TABLE IF EXISTS osm_raw.transit_major CASCADE;
-- 3. On bascule les nouvelles tables vers osm_raw
ALTER TABLE public.planet_osm_line SET SCHEMA osm_raw;
ALTER TABLE public.planet_osm_point SET SCHEMA osm_raw;
ALTER TABLE public.planet_osm_polygon SET SCHEMA osm_raw;
ALTER TABLE public.planet_osm_roads SET SCHEMA osm_raw;
-- 4. Renommage pour une nomenclature propre
ALTER TABLE osm_raw.planet_osm_line RENAME TO roads;
ALTER TABLE osm_raw.planet_osm_point RENAME TO points;
ALTER TABLE osm_raw.planet_osm_polygon RENAME TO buildings;
ALTER TABLE osm_raw.planet_osm_roads RENAME TO transit_major;
"
4/ FAIRE L'INVENTAIRE DE OSM_RAW
sudo docker exec -it alteris_postgis psql -U alteris_admin -d alteris_geo -c "
SELECT
relname AS table_name,
pg_size_pretty(pg_total_relation_size(c.oid)) AS taille
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'osm_raw'
AND c.relkind = 'r'
ORDER BY pg_total_relation_size(c.oid) DESC;
"
| Rang | Table | Usage Alteris |
| 1 | buildings |
Emprises bâties et limites administratives (La Ciotat est dedans). |
| 2 | roads |
Réseau routier complet (La source de tes filaires). |
| 3 | points |
Équipements, services, commerces. |
| 4 | landuse |
Occupation du sol (Zones industrielles, parcs, forêts). |
| 5 | transit_major |
Grands axes structurants (pour cartographie grand angle). |
| 6 | pois |
Points d'intérêt spécifiques (doublon partiel de points). |
| 7 | waterways |
Rivières, canaux, fossés. |
| 8 | water |
Plans d'eau, bassins, mer. |
| 9 | railways |
Réseau ferré (Barrière physique majeure pour I-T6-01). |
| 10 | places |
Noms de quartiers, lieux-dits et communes. |
table_name | taille
---------------+---------
buildings | 1374 MB
roads | 625 MB
points | 232 MB
landuse | 131 MB
transit_major | 64 MB
pois | 64 MB
waterways | 29 MB
water | 14 MB
railways | 3680 kB
places | 2920 kB
(10 rows)
| Catégorie | Tables incluses | Poids estimé |
| Gros volumes | buildings + roads |
~2 000 MB |
| Moyens volumes | points + landuse |
~363 MB |
| Volumes légers | transit_major, pois, waterways, water |
~171 MB |
| Petits volumes | railways, places |
~6 MB |
| TOTAL | Miroir OSM PACA | ~2,54 GB |
debian@vps-02c1f983-vps-ovh-net:~$
debian@vps-02c1f983-vps-ovh-net:~$ sudo docker exec -it alteris_postgis psql -U alteris_admin -d alteris_geo -c "
SELECT pg_size_pretty(SUM(pg_total_relation_size(c.oid))) AS poids_total_osm_raw
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'osm_raw'
AND c.relkind = 'r';
"
-------------------------
poids_total_osm_raw
---------------------
2539 MB
(1 row)
debian@vps-02c1f983-vps-ovh-net:~$
Transfert des contours IRIS dans insee_raw
Pour ne pas réinventer la roue et éviter les galères de mot de passe ou de drivers manquants, voici la méthode "Golden" que nous avons validée.
L'astuce consiste à utiliser le binaire du VPS (ton host) pour lire le fichier et injecter le résultat dans le Docker via un "pipe" (flux). C'est la méthode la plus robuste car elle ne dépend pas de la configuration interne du conteneur.
🚀 La Méthode "Standard" (à copier-coller)
Voici la commande universelle pour recharger tes couches SIG :
# 1. On génère le SQL depuis le fichier local
# 2. On l'injecte dans le psql du Docker
ogr2ogr -f PGDump /vsistdout/ "/home/debian/data/contours-iris-pe.gpkg" \
-nln insee_raw.contours_iris_2025 \
-lco OVERWRITE=YES \
-lco GEOMETRY_NAME=geom \
-lco DROP_TABLE=YES \
-nlt PROMOTE_TO_MULTI | docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo
🧠 Pourquoi cette méthode gagne à tous les coups ?
-
Indépendance des Drivers : Tu utilises le
ogr2ogrde ton Debian (GDAL 3.6.2), qui est beaucoup plus complet et à jour que celui souvent pré-installé dans les images Docker. -
Bypass du Mot de Passe : En envoyant le flux vers
docker exec -i ... psql, tu profites de l'authentification "peer" ou de l'absence de mot de passe interne du conteneur. Plus besoin dePGPASSWORD. -
Propreté (OVERWRITE/DROP) : L'option
-lco DROP_TABLE=YESet-lco OVERWRITE=YESgarantit que tu ne crées pas de doublons et que tu nettoies les restes d'une couche corrompue. -
Standardisation (PROMOTE_TO_MULTI) : Elle force toutes les géométries en
MultiPolygon. C'est vital car QGIS ou PostGIS détestent avoir un mélange dePolygonetMultiPolygondans une même table.
📝 Le mémo de secours (si ça bloque encore)
Si un jour tu as une erreur de connexion, vérifie ces 3 points :
-
Le chemin : Fais toujours un
ls /chemin/vers/ton/fichierpour être sûr qu'il n'a pas bougé. -
Le schéma : Assure-toi que le schéma cible (ici
insee_raw) existe déjà dans ta base :-
CREATE SCHEMA IF NOT EXISTS insee_raw;
-
-
Les droits : Si tu as une erreur de permission, utilise
sudodevant la commandeogr2ogr.
Tu as maintenant la "recette secrète" pour tes imports. Est-ce qu'on s'en sert pour une autre couche (DVF, Sirene...) ou on finit la synthèse de ton indicateur A1 ?
02/ Inventaire du Groupe Public de PostGIS
sudo docker exec -it alteris_postgis psql -U alteris_admin -d alteris_geo -c "
SELECT
relname AS table_name,
pg_size_pretty(pg_total_relation_size(c.oid)) AS taille
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relkind = 'r'
AND relname != 'spatial_ref_sys'
ORDER BY pg_total_relation_size(c.oid) DESC;
table_name | taille
--------------------------------+--------
communes_fr | 351 MB
trafic | 11 MB
communes_paca | 10 MB
i_t1_03 | 152 kB
indicateurs_crise_de_confiance | 112 kB
pop_dep | 48 kB
indicateur_i_t1_02_final | 16 kB
pilote_atlas | 16 kB
(10 rows)
Les données foncières (DVF)
Vedène -
C'est un excellent test pour valider l'industrialisation du processus. Vedène (84141) va nous permettre de vérifier si la structure à 40 colonnes est constante.
Voici le plan d'action séquentiel.
1. Création de vedene_full (Le moule brut)
On prépare d'abord le réceptacle pour l'importation du CSV.
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
CREATE SCHEMA IF NOT EXISTS dvf_raw;
DROP TABLE IF EXISTS dvf_raw.vedene_full;
CREATE TABLE dvf_raw.vedene_full (
c1 text, c2 text, c3 text, c4 text, c5 text, c6 text, c7 text, c8 text, c9 text, c10 text,
c11 text, c12 text, c13 text, c14 text, c15 text, c16 text, c17 text, c18 text, c19 text, c20 text,
c21 text, c22 text, c23 text, c24 text, c25 text, c26 text, c27 text, c28 text, c29 text, c30 text,
c31 text, c32 text, c33 text, c34 text, c35 text, c36 text, c37 text, c38 text, c39 text, c40 text
);
"
2. Injection des données (Bash/Docker)
Assure-toi que le fichier est bien présent dans ton arborescence locale avant de lancer :
# 1. Injection du CSV (40 colonnes)
cat "/home/debian/data/Regions/93/84/248400251/84141/84141.csv" | docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "COPY dvf_raw.vedene_full FROM STDIN WITH (FORMAT CSV, HEADER, DELIMITER ',');"
# 2. Création du Jalon Géographique (Points Lambert 93)
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
DROP TABLE IF EXISTS dvf_raw.vedene_valide;
CREATE TABLE dvf_raw.vedene_valide AS
SELECT
f.c1 as id_mutation,
ST_Transform(ST_SetSRID(ST_MakePoint(NULLIF(f.c39, '')::double precision, NULLIF(f.c40, '')::double precision), 4326), 2154) as geom
FROM dvf_raw.vedene_full f
WHERE f.c39 ~ '^-?[0-9]';
CREATE INDEX sidx_vedene_valide ON dvf_raw.vedene_valide USING GIST (geom);
"
3. Création de vedene_valide (Le Jalon Géographique)
On extrait les points GPS pour "poser le premier jalon" et vérifier l'emprise.
DROP TABLE IF EXISTS dvf_raw.vedene_valide;
CREATE TABLE dvf_raw.vedene_valide AS
SELECT
f.c1 as id_mutation,
ST_Transform(ST_SetSRID(ST_MakePoint(NULLIF(f.c39, '')::double precision, NULLIF(f.c40, '')::double precision), 4326), 2154) as geom
FROM dvf_raw.vedene_full f
WHERE f.c39 ~ '^-?[0-9]';
CREATE INDEX sidx_vedene_valide ON dvf_raw.vedene_valide USING GIST (geom);
4. Fusion Finale : 84141_dvf_alteris (L'Item Unique)
On assemble le tout (DVF + Géométrie Validée + Jointure Cadastre) en éliminant les doublons de lignes DVF.
DROP TABLE IF EXISTS dvf_raw."84141_dvf_alteris";
CREATE TABLE dvf_raw."84141_dvf_alteris" AS
WITH cadastre AS (
-- Reconstruction spatiale du cadastre de Vedène
SELECT
(f->'properties'->>'id')::text as id_parcelle_cad,
ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON(f->'geometry'), 4326), 2154) as geom_poly
FROM (
SELECT jsonb_array_elements(string_agg(line, '')::jsonb->'features') as f
FROM dvf_raw.tmp_vedene_txt
) sub
)
SELECT DISTINCT ON (f.c1)
v.geom,
f.c1 as id_mutation,
NULLIF(f.c2, '')::date as date_mutation,
NULLIF(f.c5, '')::numeric as valeur_fonciere,
f.c16 as id_parcelle_dvf,
f.c8 || ' ' || f.c9 as adresse,
f.c28 as type_local,
NULLIF(f.c29, '')::numeric as surface_bati,
ST_Within(v.geom, c.geom_poly) as certification_spatiale
FROM dvf_raw.vedene_full f
JOIN dvf_raw.vedene_valide v ON f.c1 = v.id_mutation
LEFT JOIN cadastre c ON f.c16 = c.id_parcelle_cad
ORDER BY f.c1;
-- Nettoyage des tables temporaires
DROP TABLE dvf_raw.tmp_vedene_txt;
Chiche ! Les scripts sont prêts. Tu veux que je lance l'exécution ou tu préfères vérifier un point de structure avant ?
Exemple sur fulldvf France de 2020 à 2025
1/ on a regroupé les fichiers fullDVF sur le home/debian/data
2/ on a lancé le script
#!/bin/bash
TARGET_TABLE="dvf_raw.var_history_83"
DATA_DIR="/home/debian/data"
echo "----------------------------------------------------"
echo "ALTERIS - Moissonnage VAR (83) - Colonne 13"
echo "----------------------------------------------------"
# 1. On vide la table pour repartir sur une base propre
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "TRUNCATE $TARGET_TABLE;"
# 2. Boucle sur les 6 fichiers
for YEAR in {2020..2025}; do
FILE="$DATA_DIR/fullDVF_${YEAR}.csv"
if [ -f "$FILE" ]; then
echo "--> Extraction Var 83 dans l'année $YEAR..."
# On prépare le fichier temporaire avec l'entête
head -n 1 "$FILE" > "$DATA_DIR/var_tmp.csv"
# FILTRE : On cherche exactement le code 83 après 12 virgules
grep -E "^([^,]*,){12}83," "$FILE" >> "$DATA_DIR/var_tmp.csv"
# Injection
cat "$DATA_DIR/var_tmp.csv" | docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "COPY $TARGET_TABLE FROM STDIN WITH (FORMAT CSV, HEADER, DELIMITER ',');"
# Nettoyage
rm "$DATA_DIR/var_tmp.csv"
fi
done
echo "----------------------------------------------------"
echo "RÉSULTAT DE L'OBJECTIVATION :"
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "SELECT count(*) as total_var_83 FROM $TARGET_TABLE;"
On monte la table pure
C'est la voix de la sagesse Alteris : d'abord figer la donnée brute certifiée (le socle), puis construire la production par-dessus. On ne mélange pas l'archive et l'analyse.
On va donc créer la table DVFFull83_raw. Ce sera ta "Banque Centrale" du Var : une table physique, indexée, contenant l'intégralité des 451 336 lignes sans aucune transformation, mais avec une colonne géométrique propre pour QGIS.
1. Bloc d'évolutions proposées
-
Matérialisation : On passe d'une vue à une TABLE physique (plus rapide pour QGIS).
-
Typage SQL : On convertit les colonnes critiques (Date, Prix, Surface) pour permettre les tris immédiats, mais on garde tout le reste.
-
Indexation Double : Un index spatial (GIST) pour la carte et un index attributaire sur le
code_commune.
2. Création de la Table de Référence : DVFFull83_raw
Lance ce bloc pour graver tes données dans le marbre :
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
DROP TABLE IF EXISTS dvf_raw.\"DVFFull83_raw\";
CREATE TABLE dvf_raw.\"DVFFull83_raw\" AS
SELECT
-- Géométrie Lambert 93 générée à la volée
ST_Transform(ST_SetSRID(ST_MakePoint(NULLIF(c39, '')::double precision, NULLIF(c40, '')::double precision), 4326), 2154) as geom,
c1 as id_mutation,
NULLIF(c2, '')::date as date_mutation,
c4 as nature_mutation,
NULLIF(c5, '')::numeric as valeur_fonciere,
c8 as adresse_numero,
c9 as adresse_nom_voie,
c10 as code_postal,
c11 as code_commune,
c12 as nom_commune,
c13 as code_departement,
c16 as id_parcelle,
c29 as lot1_surface_carrez,
c31 as type_local,
NULLIF(c32, '')::numeric as surface_reelle_bati,
NULLIF(c33, '')::integer as nombre_pieces_principales,
NULLIF(c38, '')::numeric as surface_terrain,
c39 as longitude_wgs84,
c40 as latitude_wgs84
FROM dvf_raw.var_history_83
WHERE c39 ~ '^-?[0-9]';
-- Indexation de performance
CREATE INDEX sidx_dvffull83_geom ON dvf_raw.\"DVFFull83_raw\" USING GIST (geom);
CREATE INDEX idx_dvffull83_commune ON dvf_raw.\"DVFFull83_raw\" (code_commune);
CREATE INDEX idx_dvffull83_date ON dvf_raw.\"DVFFull83_raw\" (date_mutation);
"
3. Raisonnement par étape : Pourquoi c'est ta référence ?
-
Immutabilité : Cette table contient les 451 336 lignes. Si une vente a 3 lots, tu as les 3 lignes. C'est ta "boîte noire".
-
Vitesse QGIS : Contrairement à la table
var_history_83qui n'a que du texte (TEXT), ici les prix sont desNUMERICet les dates desDATE. Les filtres dans QGIS seront instantanés. -
Sécurité : Pour ton rendu de jeudi, si on te pose une question sur un lot spécifique, tu as l'info brute sous la main.
4. Audit Final
Une fois la table créée, vérifie que le compte est bon : SELECT count(*) FROM dvf_raw."DVFFull83_raw";
C'est fait ? Si oui, tu as officiellement ta base de référence pour le Var. On s'arrête là pour la structure ou tu veux qu'on prépare l'export pour ton NAS ?
L'export en GPKG n'a pas fonctionné - "C'est une erreur classique de "boîte à outils" : l'image Docker postgis/postgis contient la base de données, mais elle n'embarque pas toujours les utilitaires de conversion comme ogr2ogr (qui appartiennent à la bibliothèque GDAL)."
# Export de ta table de référence vers un fichier GeoPackage
docker exec -i alteris_postgis ogr2ogr -f "GPKG" \
/home/debian/data/DVFFull83_raw_2020_2025.gpkg \
PG:"dbname=alteris_geo user=alteris_admin password=alteris_pwd host=localhost" \
"dvf_raw.DVFFull83_raw"
Donc export du DVFFull83_raw
en csv avec point geolocalisation et ID Cadastre
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
COPY (SELECT * FROM dvf_raw.\"DVFFull83_raw\") TO STDOUT WITH (FORMAT CSV, HEADER, DELIMITER ',');
" > /home/debian/data/DVFFull83_raw_2020_2025.csv
Etude Bandol - apprentissage de renseignement des indicateurs
A - Demographie
A3 - l'évolution des CSP par IRIS (la gentrification)
'est la meilleure façon de documenter : garder le code brut sous le coude. Pour l'indicateur A3 (Profil CSP), voici le bloc SQL "propre" que nous avons utilisé.
Il est structuré pour être réutilisable : il suffit de changer les noms des tables ou les codes IRIS pour l'adapter à une autre ville.
💾 Script SQL : Calcul de l'indicateur A3 (Structure des Actifs)
/* INDICATEUR A3 : ANALYSE DE LA POPULATION ACTIVE
Période : 2016 (CS1) vs 2025 (GS)
Filtre : Population active uniquement (Codes 2 à 6)
*/
WITH stats_16 AS (
SELECT
iris,
-- Agrégation par catégorie (Pondération IPONDI)
SUM(CASE WHEN cs1 = '2' THEN ipondi::numeric ELSE 0 END) as art_16,
SUM(CASE WHEN cs1 = '3' THEN ipondi::numeric ELSE 0 END) as cad_16,
SUM(CASE WHEN cs1 = '4' THEN ipondi::numeric ELSE 0 END) as int_16,
SUM(CASE WHEN cs1 = '5' THEN ipondi::numeric ELSE 0 END) as emp_16,
SUM(CASE WHEN cs1 = '6' THEN ipondi::numeric ELSE 0 END) as ouv_16,
-- Total de la population active (Base de calcul pour les %)
SUM(ipondi::numeric) FILTER (WHERE cs1 IN ('2','3','4','5','6')) as tot_16
FROM "83009_bandol".stats_individus_2016
WHERE iris IN ('830090101', '830090102', '830090106')
GROUP BY iris
),
stats_25 AS (
SELECT
iris,
-- Agrégation par catégorie (Pondération IPONDI)
SUM(CASE WHEN gs = '2' THEN ipondi::numeric ELSE 0 END) as art_25,
SUM(CASE WHEN gs = '3' THEN ipondi::numeric ELSE 0 END) as cad_25,
SUM(CASE WHEN gs = '4' THEN ipondi::numeric ELSE 0 END) as int_25,
SUM(CASE WHEN gs = '5' THEN ipondi::numeric ELSE 0 END) as emp_25,
SUM(CASE WHEN gs = '6' THEN ipondi::numeric ELSE 0 END) as ouv_25,
-- Total de la population active (Base de calcul pour les %)
SUM(ipondi::numeric) FILTER (WHERE gs IN ('2','3','4','5','6')) as tot_25
FROM "83009_bandol".stats_individus
WHERE iris IN ('830090101', '830090102', '830090106')
GROUP BY iris
)
-- Calcul final des parts et de l'évolution
SELECT
s25.iris,
'Cadres' as csp,
ROUND((s16.cad_16/NULLIF(s16.tot_16,0))*100,1) as part_2016,
ROUND((s25.cad_25/NULLIF(s25.tot_25,0))*100,1) as part_2025,
ROUND(((s25.cad_25-s16.cad_16)/NULLIF(s16.cad_16,0))*100,1) as evol_vol_pct
FROM stats_25 s25 JOIN stats_16 s16 ON s25.iris = s16.iris
UNION ALL
SELECT
s25.iris, 'Prof. Interm.',
ROUND((s16.int_16/NULLIF(s16.tot_16,0))*100,1), ROUND((s25.int_25/NULLIF(s25.tot_25,0))*100,1),
ROUND(((s25.int_25-s16.int_16)/NULLIF(s16.int_16,0))*100,1)
FROM stats_25 s25 JOIN stats_16 s16 ON s25.iris = s16.iris
ORDER BY iris, part_2025 DESC;
🗝️ Pourquoi avoir codé comme ça ?
-
NULLIF(..., 0): C'est la sécurité anti-crash. Si un IRIS n'a aucun cadre en 2016, la division par zéro échoue. Cette fonction transforme le 0 enNULLpour éviter l'erreur. -
FILTER (WHERE ...): C'est la méthode SQL moderne pour compter uniquement ce qui nous intéresse dans une somme globale. -
UNION ALL: On "empile" les résultats des cadres et des prof. intermédiaires pour avoir une liste facile à lire, plutôt qu'un tableau avec 50 colonnes.
💡 Petit conseil d'expert :
Si tu dois refaire ça pour une autre ville (ex: Sanary), il te suffira de :
-
Changer le préfixe du schéma (
"83009_bandol"). -
Changer la liste des codes IRIS dans les clauses
WHERE.
C'est dans la boîte ! Tu te sens prêt à attaquer la mise en page de ton rapport ou tu as encore un doute sur une colonne ?
C - DVF
Toujours commencer par une approche semantique de la table pour que l'IA ne fasse pas de fautes
debian@vps-02c1f983-vps-ovh-net:~$ docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "SELECT nature_mutation, COUNT(*) FROM \"83009_bandol\".dvf_bandol_complet GROUP BY nature_mutation;"
C5 - Etude du marché de l'ancien
C5a/ - le marche immobilier
Le problème est que les micro-studios "balnéaires" hyper spéculatifs de moins de 20 m² faussent le prix moyen du m² global.
Dans l'analyse C5a - Nexte_stats neutralise ces données pour transcrire un marché immobilier courant incluant les studios d'habitat à l'année, mais excluant les meublés d'été inférieurs à 20 m². (A VALIDER)
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
-- 1. Nettoyage de l'ancienne couche
DROP TABLE IF EXISTS \"83009_bandol\".\"dvf_carto_2022_2025\";
DROP TABLE IF EXISTS \"83009_bandol\".\"dvf_carto_2022_2025_prixm²\";
-- 2. Création de la couche filtrée (> 20m²)
CREATE TABLE \"83009_bandol\".\"dvf_carto_2022_2025_prixm²\" AS
SELECT
id_mutation,
MAX(date_mutation) as date_mutation,
EXTRACT(YEAR FROM MAX(date_mutation)) as annee,
MAX(type_local) as type_bien,
MAX(valeur_fonciere) as prix_total,
SUM(surface_reelle_bati) as surface_totale,
ROUND(MAX(valeur_fonciere) / NULLIF(SUM(surface_reelle_bati), 0), 0) as prix_m2,
ST_Centroid(ST_Collect(geom)) as geom
FROM \"83009_bandol\".dvf_bandol_complet
WHERE nature_mutation IN ('Vente', 'Vente en l''état futur d''achèvement')
AND type_local IN ('Maison', 'Appartement')
AND date_mutation >= '2022-01-01'
AND valeur_fonciere > 50000
GROUP BY id_mutation
HAVING SUM(surface_reelle_bati) >= 20; -- FILTRE ANTI-SINGLETONS
-- 3. Indexation
CREATE INDEX idx_dvf_carto_prixm2_geom ON \"83009_bandol\".\"dvf_carto_2022_2025_prixm²\" USING GIST(geom);"
C5b/ le tableau des typologies globales ancien/neuf en appartements sur les 3 dernières années
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
SELECT
CASE
WHEN nombre_pieces_principales = 1 THEN 'Studio'
WHEN nombre_pieces_principales = 2 THEN 'T2'
WHEN nombre_pieces_principales = 3 THEN 'T3'
WHEN nombre_pieces_principales = 4 THEN 'T4'
ELSE 'T5+'
END as typologie,
COUNT(DISTINCT id_mutation) as nb_ventes,
ROUND(AVG(surface_reelle_bati), 0) as surface_moyenne,
ROUND(AVG(valeur_fonciere), 0) as prix_moyen,
ROUND(AVG(valeur_fonciere / NULLIF(surface_reelle_bati, 0)), 0) as prix_m2_moyen
FROM \"83009_bandol\".dvf_bandol_complet
WHERE nature_mutation = 'Vente'
AND type_local = 'Appartement'
AND surface_reelle_bati >= 20
AND date_mutation >= '2022-01-01'
GROUP BY nombre_pieces_principales
ORDER BY nombre_pieces_principales;"
C5c - le tableau des typologies globales ancien/neuf en appartements VEFA sur les 3 dernières années
📘 Documentation Indicateur C5c : Analyse du Neuf (DVF x BDNB)
Cet indicateur permet de filtrer les ventes de la DVF non pas sur le champ "Nature de mutation" (souvent peu fiable), mais sur la réalité physique du bâtiment (Année de construction certifiée par les Fichiers Fonciers).
1. Intégration de la BDNB (Var - 83)
L'import se fait depuis un dump SQL de la Base Nationale des Bâtiments.
# Copie du fichier SQL vers le container
docker cp data/Regions/93/83/bdnb.sql alteris_postgis:/tmp/bdnb.sql
# Restauration dans la base de données
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -f /tmp/bdnb.sql
Note : Le schéma créé est bdnb_2025_07_a_open_data_dep83.
2. Croisement DVF x BDNB (Filtre $\ge$ 2017)
On crée une table de synthèse qui lie les mutations DVF aux caractéristiques du bâtiment via la parcelle cadastrale.
-- Création de la table de travail sur le VPS
CREATE TABLE public.temp_bandol_neuf AS
SELECT
d.id_mutation,
d.date_mutation,
d.valeur_fonciere,
d.surface_reelle_bati,
d.nombre_pieces_principales,
ffo.annee_construction,
d.geom
FROM "83009_bandol".dvf_bandol_complet d
JOIN "bdnb_2025_07_a_open_data_dep83".rel_batiment_groupe_parcelle rel
ON d.id_parcelle = rel.parcelle_id
JOIN "bdnb_2025_07_a_open_data_dep83".batiment_groupe_ffo_bat ffo
ON rel.batiment_groupe_id = ffo.batiment_groupe_id
WHERE d.type_local = 'Appartement'
AND d.surface_reelle_bati > 0
AND ffo.annee_construction >= 2017;
3. Export GeoJSON (Projection GPS WGS84)
Pour que le fichier soit immédiatement exploitable dans QGIS sans décalage, on projette la géométrie du Lambert-93 (2154) vers le WGS84 (4326).
# Génération du JSON propre à l'intérieur du container
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
COPY (
SELECT jsonb_build_object(
'type', 'FeatureCollection',
'features', jsonb_agg(features.feature)
)
FROM (
SELECT jsonb_build_object(
'type', 'Feature',
'geometry', ST_AsGeoJSON(ST_Transform(geom, 4326))::jsonb,
'properties', to_jsonb(inputs) - 'geom'
) AS feature
FROM (SELECT * FROM public.temp_bandol_neuf) inputs
) features
) TO '/tmp/final_gps.geojson';"
# Extraction vers le dossier d'échange du VPS
docker cp alteris_postgis:/tmp/final_gps.geojson /home/debian/data/bandol_expert_gps.geojson
🎯 Résultats obtenus (Bandol)
Le croisement a permis d'identifier 31 transactions sur le cycle 2017-2025.
Typologie |
Nb Ventes |
Surface Moy. |
Prix m² Moyen |
T2 |
11 |
44 m² |
7 551 € |
T3 |
9 |
68 m² |
7 560 € |
T4 |
8 |
91 m² |
6 547 € |
Alerte Point de Vigilance : Une vente record (Studio) à 26 409 €/m² a été détectée, signalant un segment "Ultra-Luxe" spécifique à isoler de la moyenne générale pour ne pas fausser les prévisionnels de sortie du projet AH0219.
C'est ta "recette de cuisine" pour n'importe quelle autre commune du Var maintenant. On passe à l'analyse par IRIS pour voir si le quartier du projet sur-performe la moyenne de Bandol
Autre aspect C5c - dynamique des VEFA sur Bandol
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "SELECT CASE WHEN date_mutation >= '2025-01-01' AND date_mutation <= '2025-06-30' THEN '2025 (S1)' ELSE TO_CHAR(date_mutation, 'YYYY') END as periode, COUNT(DISTINCT id_mutation) as nb_vefa, ROUND(AVG(surface_reelle_bati), 0) as surface_moy, ROUND(AVG(valeur_fonciere / NULLIF(surface_reelle_bati, 0)), 0) as prix_m2_vefaFROM \"83009_bandol\".dvf_bandol_completWHERE nature_mutation = 'Vente en l''état futur d''achèvement' AND type_local = 'Appartement' AND surface_reelle_bati > 0 AND date_mutation >= '2022-01-01'GROUP BY periodeORDER BY periode DESC;"
C5d - Studios pondérés
C'est une excellente stratégie. En statistiques, on appelle cela une moyenne tronquée (ou moyenne élaguée). En excluant les 20 % extrêmes de chaque côté (le "bas de marché" souvent dégradé et les "ovnis" spéculatifs), on obtient l'image la plus fidèle du cœur de marché.
Pour être précis : si tes calculs précédents commençaient à 20 m², nous filtrons ici strictement de 12 à 19,99 m².
🛠️ La Requête SQL "Moyenne Tronquée" (Studios 12-19m²)
Voici la commande qui réalise ce filtrage complexe. Elle utilise des "Common Table Expressions" (CTE) pour classer les prix et ne garder que le milieu de la distribution (les 60 % centraux).
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
WITH studios_calcules AS (
SELECT
valeur_fonciere / surface_reelle_bati as prix_m2,
surface_reelle_bati,
valeur_fonciere,
NTILE(5) OVER (ORDER BY valeur_fonciere / surface_reelle_bati) as quintile
FROM \"83009_bandol\".dvf_bandol_complet
WHERE type_local = 'Appartement'
AND surface_reelle_bati >= 12
AND surface_reelle_bati < 20
AND nature_mutation = 'Vente'
AND date_mutation >= '2022-01-01'
)
SELECT
COUNT(*) as nb_ventes_retenues,
ROUND(AVG(surface_reelle_bati), 1) as surface_moyenne,
ROUND(AVG(prix_m2), 0) as prix_m2_moyen_tronque,
ROUND(MIN(prix_m2), 0) as borne_basse_retenue,
ROUND(MAX(prix_m2), 0) as borne_haute_retenue
FROM studios_calcules
WHERE quintile IN (2, 3, 4); -- On exclut le quintile 1 (20% bas) et le 5 (20% haut)"
Voici la fiche de documentation technique et méthodologique pour l'indicateur C5e. Elle est conçue pour être insérée directement dans
📘 Documentation Indicateur C5e : Dynamique des Programmes Immobiliers Neufs
1. Définition et Objectif
L'indicateur C5e vise à isoler et analyser les opérations de promotion immobilière (VEFA) par une approche de clustering géographique. Contrairement aux indicateurs globaux, il permet de distinguer la vente au détail (particuliers) de la vente en bloc (institutionnels) et d'identifier les valeurs de sortie réelles par programme.
2. Méthodologie d'Extraction
Le workflow repose sur l'identification de "grappes" de mutations à une même adresse. Le seuil de détection est fixé à :
-
Volume : Minimum 3 mutations distinctes sur une même voie la même année.
-
Valeur : Ou un Chiffre d'Affaires cumulé supérieur à 2 000 000 €.
Requête SQL de référence :
SELECT
adresse_numero AS nom_de_la_rue,
TO_CHAR(date_mutation, 'YYYY') AS annee,
COUNT(DISTINCT id_mutation) AS nb_mutations,
SUM(valeur_fonciere) AS ca_total_dvf
FROM "83009_bandol".dvf_bandol_complet
WHERE nature_mutation LIKE 'Vente%futur%'
AND date_mutation >= '2022-01-01'
GROUP BY nom_de_la_rue, annee
HAVING COUNT(DISTINCT id_mutation) >= 3
OR SUM(valeur_fonciere) >= 2000000
ORDER BY annee DESC, ca_total_dvf DESC;
3. Protocole d'Analyse des Résultats
Une fois la liste des adresses extraite, l'expertise se décompose en trois niveaux de lecture :
| Segment détecté | Indicateur DVF | Action Expertise |
| Vente en Bloc | CA > 20 M€ pour < 5 mutations | Identifier l'acquéreur institutionnel (Bailleur/Foncière) via Sitadel. |
| Luxe / Exception | Prix moyen / mutation > 1,5 M€ | Vérifier la typologie (Grands plateaux, Villas sur toit). |
| Cœur de Marché | Prix moyen / mutation [400k€ - 900k€] | Comparable direct pour les projets de promotion standard. |
4. Cas Pratique : Analyse de Bandol (2022-2025)
L'application de l'indicateur C5e sur la base DVF a permis d'isoler les marqueurs suivants :
-
Pôle Institutionnel (Biais statistique à isoler) :
-
Bd de Marseille (2024) et Moulin à Vent (2025) totalisent près de 258 M€ de CA pour seulement 4 mutations majeures. Ces données valident une activité de promotion massive (ventes en bloc) sur la commune.
-
-
Pôle Comparables Directs (Résidentiel) :
-
Rue J.J. Rousseau (2022) : 10 mutations pour 26,2 M€ (Moyenne 2,6 M€/acte).
-
Av. du 11 Novembre (2022) : 8 mutations pour 4,3 M€ (Moyenne 547k€/acte).
-
Rue Pasteur (2022) : 3 mutations pour 2,2 M€ (Moyenne 742k€/acte).
-
Les statistiques INSEE RGP Communes et IRIS
Origine des données INSEE RGP Communes et IRIS
Les données RGP sont accessibles sur cette page pour 2025
Sur cette page pour 2016.
Il faut télécharger les rubriques Logements et Individus.
------pour mémoire------------------
LE DOSSIER COMPLET NATIONAL
https://www.insee.fr/fr/statistiques/5359146
Les dossiers complets par commune (changer code insee)
https://www.insee.fr/fr/statistiques/2011101?geo=COM-83009
-----------------------------------------------------------------
Pour les fichiers RGP (1.1 Go chacun), filtrer sur PACA (04, 05, 06, 13, 83, 84) avant l'injection est une excellente décision : cela va économiser énormément d'espace disque et accélérer tes requêtes SQL futures.
Comme nous n'avons pas d'outil de filtrage lourd, on va utiliser la puissance native de awk sur Debian pour streamer uniquement les lignes dont le code département commence par un de tes départements cibles.
1. Préparation des tables réceptacles
D'abord, on crée les structures vides dans PostGIS.
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
CREATE TABLE IF NOT EXISTS insee_raw.rgp_individus_paca_2025 (
id serial PRIMARY KEY,
donnees jsonb -- On peut stocker en JSONB pour la flexibilité ou définir les colonnes
);
CREATE TABLE IF NOT EXISTS insee_raw.rgp_logements_paca_2025 (
id serial PRIMARY KEY,
donnees jsonb
);"
2. Injection filtrée (Le "Stream")
Voici la commande pour ne garder que la Région PACA. Note : Je pars du principe que le code département est dans la 2ème ou 3ème colonne. Si tu as un doute sur la position de la colonne, dis-le moi.
Individus (Filtre Départements 04,05,06,13,83,84) :
awk -F',' 'NR==1 || $2 ~ /^(04|05|06|13|83|84)/' "/home/debian/data/INSEE_RGP_2022-25_Individus_zone E.csv" | \
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo \
-c "COPY insee_raw.rgp_individus_paca_2025 FROM STDIN WITH (FORMAT CSV, HEADER, DELIMITER ',', ENCODING 'UTF8');"
Logements (Filtre Départements 04,05,06,13,83,84) :
awk -F',' 'NR==1 || $2 ~ /^(04|05|06|13|83|84)/' "/home/debian/data/INSEE_RGP_2022-25_Logements_zone E.csv" | \
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo \
-c "COPY insee_raw.rgp_logements_paca_2025 FROM STDIN WITH (FORMAT CSV, HEADER, DELIMITER ',', ENCODING 'UTF8');"
Pourquoi cette méthode ?
-
Rapidité :
awktraite le fichier ligne par ligne sans le charger en mémoire vive. -
Efficacité : Seules les lignes utiles traversent le pipe vers Docker.
-
Propreté : On garde le
HEADER(la première ligne) pour que la commandeCOPYne soit pas perdue.
Vérifie juste une chose : Peux-tu me donner les 3 premières lignes d'un des fichiers avec head -n 3 "/home/debian/data/INSEE_RGP_2022-25_Individus_zone E.csv" ? Cela me permettra de confirmer le numéro de la colonne département pour ajuster le $2 dans la commande awk.
3- Identification du nom de la colonne
Lance cette commande pour être certain du nom de la colonne (car l'INSEE utilise parfois COMM ou COMMUNE) :
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "SELECT column_name
Bingo ! On a le jackpot. Regarde bien les trois premières lignes du résultat :
-
commune: On a bien le code commune (83009 pour Bandol). -
arm: Utile pour Marseille (arrondissements). -
iris: C'est la surprise du chef. Tes fichiers RGP contiennent déjà le code IRIS. On n'aura pas besoin d'attendre cet après-midi pour faire de la dentelle géographique.
C'est très clair. La documentation varmod_indcvi_2022.csv que tu as partagée se confirme : dans le fichier Individus, la colonne commune n'existe pas en tant que telle. L'échelle la plus fine disponible ici est cantville ou iris (si elle apparaît plus bas dans la liste).
Pour le fichier Logements, nous avons vu qu'il contient commune. Mais pour les Individus, nous devons ruser.
1. Vérification de la présence de la colonne iris
Avant de filtrer, vérifions si iris est présent dans la table Individus (comme c'était le cas pour les Logements) :
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
SELECT column_name FROM information_schema.columns
WHERE table_name = 'rgp_individus_paca_2025' AND table_schema = 'insee_raw' AND column_name = 'iris';"
2. Stratégie d'indexation et d'extraction
Si iris existe : On utilise les 5 premiers chiffres de l'IRIS (qui correspondent au code commune).
# Indexation sur IRIS
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "CREATE INDEX idx_rgp_ind_iris ON insee_raw.rgp_individus_paca_2025 (iris);"
# Extraction Bandol (83009) via IRIS
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
CREATE TABLE bandol.stats_individus AS
SELECT * FROM insee_raw.rgp_individus_paca_2025
WHERE iris LIKE '83009%';"
C'est parfait. La colonne iris est présente dans les deux tables. C'est la clé de voûte de notre structuration car elle permet une précision chirurgicale (le quartier) là où cantville est trop large.
Voici la séquence de commandes pour finaliser l'extraction de Bandol proprement :
1. Indexation sur la colonne iris (Individus)
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "CREATE INDEX IF NOT EXISTS idx_rgp_ind_iris ON insee_raw.rgp_individus_paca_2025 (iris);"
2. Création de la table Individus pour Bandol
Pour Bandol (code commune 83009), on récupère tous les IRIS commençant par ces 5 chiffres.
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
CREATE TABLE bandol.stats_individus AS
SELECT * FROM insee_raw.rgp_individus_paca_2025
WHERE iris LIKE '83009%';"
3. Création de la table Logements pour Bandol
On utilise la colonne commune qui est déjà présente dans cette table.
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
CREATE TABLE bandol.stats_logements AS
SELECT * FROM insee_raw.rgp_logements_paca_2025
WHERE commune = '83009';"
Vérification finale du contenu
Une fois ces commandes passées, on vérifie que les deux tables sont bien peuplées :
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
SELECT 'Individus Bandol' as label, count(*) FROM bandol.stats_individus
UNION ALL
SELECT 'Logements Bandol' as label, count(*) FROM bandol.stats_logements;"
Une fois que tu as ces chiffres, nous sommes prêts pour la première analyse Nexte_Stats. Par exemple : calculer le taux de motorisation des ménages de Bandol (combien ont 0, 1 ou 2 voitures via la variable voit) croisé avec leur quartier (iris).
-------------------------------
Re pour 2016
C'est parfait, les fichiers sont là et font la même taille que ceux de 2022 (~1.1 Go), ce qui confirme qu'on a bien les bases complètes.
Voici le protocole pour intégrer 2016 et préparer l'analyse d'évolution pour Nexte_Stats.
1. Création et Import des Individus 2016
On utilise la méthode dynamique pour créer les colonnes automatiquement.
# Création de la table
head -n 1 "/home/debian/data/INSEE_RGP_2016-19_Individus.csv" | sed 's/;/ text,/g' | sed 's/$/ text/' | xargs -I {} docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "CREATE TABLE IF NOT EXISTS insee_raw.rgp_individus_paca_2016 ({});"
# Injection PACA
awk -F';' 'NR==1 || $1 ~ /^(04|05|06|13|83|84)/' "/home/debian/data/INSEE_RGP_2016-19_Individus.csv" | \
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo \
-c "COPY insee_raw.rgp_individus_paca_2016 FROM STDIN WITH (FORMAT CSV, HEADER, DELIMITER ';', ENCODING 'UTF8');"
2. Création et Import des Logements 2016
# Création de la table
head -n 1 "/home/debian/data/INSEE_RGP_2016-19_Logements.csv" | sed 's/;/ text,/g' | sed 's/$/ text/' | xargs -I {} docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "CREATE TABLE IF NOT EXISTS insee_raw.rgp_logements_paca_2016 ({});"
# Injection PACA
awk -F';' 'NR==1 || $1 ~ /^(04|05|06|13|83|84)/' "/home/debian/data/INSEE_RGP_2016-19_Logements.csv" | \
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo \
-c "COPY insee_raw.rgp_logements_paca_2016 FROM STDIN WITH (FORMAT CSV, HEADER, DELIMITER ';', ENCODING 'UTF8');"
3. Extraction Bandol 2016
Maintenant, on isole Bandol (83009). On vérifie si les colonnes sont les mêmes (iris pour les individus, commune pour les logements).
# Extraction Individus 2016
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
CREATE TABLE bandol.stats_individus_2016 AS
SELECT * FROM insee_raw.rgp_individus_paca_2016
WHERE iris LIKE '83009%';"
# Extraction Logements 2016
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
CREATE TABLE bandol.stats_logements_2016 AS
SELECT * FROM insee_raw.rgp_logements_paca_2016
WHERE commune = '83009';"
Pourquoi c'est génial pour ton projet :
Une fois ces commandes terminées, on pourra lancer la requête de la "Vérité" : L'évolution de la population réelle de Bandol en 6 ans.
Lance ces imports (compte environ 5 minutes pour l'ensemble). Dès que c'est fini, on compare les chiffres de 2016 avec les 8 268 habitants de 2022 !
Calcul des indicateurs INSEE
G1 - origine des nouveaux habitants
Pour documenter cette analyse de la mobilité résidentielle à long terme (au-delà de 1 an), voici la méthodologie et les calculs que nous avons appliqués. Cette documentation pourra être intégrée directement dans votre rapport technique.
📝 Méthodologie d'analyse de l'ancienneté d'emménagement
Pour "voir plus loin" que le flux annuel, nous utilisons la variable ANEMR (Ancienneté d'Emménagement) issue du Recensement de la Population (RGP). Elle permet de segmenter les ménages selon leur date d'entrée dans le logement actuel.
1. Source des données
-
Fichier exploité :
INSEE_RGP_2025_Communes_83009.csv(Données de recensement millésimées 2022). -
Indicateur : Population des ménages en résidences principales.
2. Calcul des segments de stabilité
Les calculs reposent sur le regroupement des variables de flux de la base Insee :
-
Flux récents (moins de 2 ans) : Correspond aux emménagements post-2020.
-
Flux de moyen terme (2 à 9 ans) : Installation entre 2013 et 2020 (période incluant votre point de comparaison 2016).
-
Ancrage historique (10 ans et plus) : Ménages présents dans le même logement depuis 2012 ou avant.
📊 Résultats documentés pour Bandol
| Ancienneté dans le logement | Nombre de ménages | Part relative | Dynamique territoriale |
| Moins de 2 ans | 534 | 12,0 % | Mobilité immédiate (marché locatif et mutations) |
| 2 à 4 ans | 810 | 18,2 % | Attractivité récente (post-confinement) |
| 5 à 9 ans | 895 | 20,1 % | Installation durable (période 2016-2020) |
| 10 ans et plus | 2 212 | 49,7 % | Noyau dur / Stabilité patrimoniale |
| TOTAL | 4 451 | 100 % | - |
💡 Interprétation technique pour le rapport
1. Le "Seuil de Renouvellement" :
On constate que 50,3 % des ménages de Bandol se sont installés dans leur logement actuel il y a moins de 10 ans. Cela démontre une dynamique de renouvellement de la population plus importante que ce que laisse suggérer le simple flux annuel de 12 %.
2. Analyse du croisement avec Sitadel :
Le croisement de cette donnée avec vos 131 chantiers (429 logements) montre que la production neuve est absorbée par deux leviers :
-
Le renouvellement interne : Une partie des 2 212 ménages installés depuis plus de 10 ans (souvent vieillissants) cherchent à quitter des villas ou des appartements sans ascenseur pour du neuf adapté.
-
L'apport extérieur : Les 534 ménages arrivés depuis moins de 2 ans s'orientent prioritairement vers le parc récent ou neuf.
3. Corrélation avec les revenus (Q1/Q2/Q3) :
La stabilité (10 ans+) est fortement corrélée aux propriétaires (67%) disposant d'un revenu médian solide. À l'inverse, les tranches de moins de 5 ans d'ancienneté correspondent souvent aux actifs du Q2 (en accession) ou aux locataires mobiles du Q1.
🚩 Conclusion de la documentation
Cette analyse prouve que Bandol n'est pas une "ville figée". Le turn-over réel sur une décennie concerne un ménage sur deux, validant ainsi la nécessité d'une offre de logements neufs continue pour accompagner cette rotation naturelle et répondre à l'attractivité du territoire.
L'extraction OSM-raw à un OSM_territoire d'étude
Commandes itératives menées par l'IA pour raffiner OSM à l'échelle du territoire d'analyse avec une précision importante sur le regroupement des aménités
Carnet des commandes itératives
C’est une excellente initiative. Pour Nexte_Stats, disposer d'un "Playbook" SQL permet de reproduire cette précision chirurgicale sur n'importe quelle autre commune (Sanary, Six-Fours, etc.) en quelques minutes.
Voici la synthèse technique de notre workflow "Bandol", nettoyée des erreurs de syntaxe et optimisée pour ton architecture PostGIS.
📑 Playbook : Extraction & Nettoyage OSM (Référentiel Nexte_Stats)
Ce script part du principe que tes données brutes sont dans le schéma osm_raw (SRID 4326 ou 3857) et que ta commune cible est définie par sa table de parcelles dans le schéma 83009_bandol (SRID 2154).
1. Préparation de l'emprise (La "Découpe")
Avant d'extraire, on définit la forme exacte de la commune pour éviter les "gros carrés" (Bounding Box).
-- On utilise l'Union des parcelles comme emporte-pièce universel
-- On la stocke mentalement comme : (SELECT ST_Union(geom) FROM "83009_bandol".bandol_parcelles)
2. Couche Bâtiments (Buildings)
Extraction des emprises bâties, transformation en Lambert-93 et nettoyage des géométries aberrantes.
DROP TABLE IF EXISTS "83009_bandol".osm_buildings;
CREATE TABLE "83009_bandol".osm_buildings AS
SELECT b.osm_id, b.name, b.building, b.tags,
ST_Multi(ST_Transform(b.way, 2154)) as geom
FROM osm_raw.buildings b
WHERE ST_Intersects(
b.way,
(SELECT ST_Transform(ST_Union(geom), 3857) FROM "83009_bandol".bandol_parcelles)
);
-- Nettoyage des artefacts et indexation
DELETE FROM "83009_bandol".osm_buildings WHERE ST_Area(geom) > 50000;
CREATE INDEX idx_osm_buildings_geom ON "83009_bandol".osm_buildings USING GIST(geom);
3. Couche Voirie (Roads / Ways)
Découpage (ST_Intersection) pour que les routes s'arrêtent pile à la frontière communale.
DROP TABLE IF EXISTS "83009_bandol".osm_roads;
CREATE TABLE "83009_bandol".osm_roads AS
SELECT r.osm_id, r.name, r.highway, r.ref, r.oneway, r.surface, r.z_order,
ST_Multi(ST_Intersection(
ST_Transform(r.way, 2154),
(SELECT ST_Union(geom) FROM "83009_bandol".bandol_parcelles)
)) as geom
FROM osm_raw.roads r
WHERE ST_Intersects(
r.way,
(SELECT ST_Transform(ST_Union(geom), 3857) FROM "83009_bandol".bandol_parcelles)
);
CREATE INDEX idx_osm_roads_geom ON "83009_bandol".osm_roads USING GIST(geom);
4. Couche Occupation du Sol (Landuse)
Gestion spécifique du format Geofabrik (fclass) et conversion Lambert-93.
DROP TABLE IF EXISTS "83009_bandol".osm_landuse;
CREATE TABLE "83009_bandol".osm_landuse AS
SELECT l.osm_id, l.name, l.fclass as landuse_type,
ST_Multi(ST_Intersection(
ST_Transform(l.geom, 2154),
(SELECT ST_Union(geom) FROM "83009_bandol".bandol_parcelles)
)) as geom
FROM osm_raw.landuse l
WHERE ST_Intersects(
ST_Transform(l.geom, 2154),
(SELECT ST_Union(geom) FROM "83009_bandol".bandol_parcelles)
);
CREATE INDEX idx_osm_landuse_geom ON "83009_bandol".osm_landuse USING GIST(geom);
5. Couche Équipements Publics (Amenities - La "Hiérarchie")
Fusion des points et des centroïdes de bâtiments pour créer un référentiel de services unique.
DROP TABLE IF EXISTS "83009_bandol"."83009_equipements_publics";
-- Étape A : Import des points natifs
CREATE TABLE "83009_bandol"."83009_equipements_publics" AS
SELECT osm_id, name, amenity, tags, ST_Transform(way, 2154) as geom
FROM osm_raw.points
WHERE ST_Intersects(way, (SELECT ST_Transform(ST_Union(geom), 3857) FROM "83009_bandol".bandol_parcelles))
AND (amenity IN ('school','townhall','police','hospital','library','post_office','community_centre'));
-- Étape B : Ajout des centroïdes des bâtiments (Mairie, Gymnases, etc.)
INSERT INTO "83009_bandol"."83009_equipements_publics" (osm_id, name, amenity, geom)
SELECT osm_id, name, COALESCE(amenity, tags->'leisure', tags->'building'), ST_Centroid(geom)
FROM "83009_bandol".osm_buildings
WHERE amenity IN ('school','townhall','hospital')
OR tags->'leisure' IN ('sports_centre', 'stadium')
OR tags->'building' IN ('public', 'train_station');
CREATE INDEX idx_equip_pub_geom ON "83009_bandol"."83009_equipements_publics" USING GIST(geom);
💡 Pourquoi ce référentiel est "pro" ?
-
SRID Unique (2154) : Plus de bugs d'affichage ou de calculs en degrés. Tout est en mètres.
-
ST_Intersection vs ST_Intersects : On a fait la distinction entre "ce qui touche" (bâtiments) et "ce qui est coupé" (routes/landuse).
-
Performance : Les index GIST garantissent que tes cartes QGIS s'affichent instantanément, même avec 10 000 bâtiments.
Une question me taraude maintenant que tout est prêt : sur quel type d'analyse veux-tu que l'on se concentre en premier ? Est-ce le "taux de vacance théorique" (Bâtiments sans ventes DVF) ou la "pression foncière sur les vignobles" ?
260407-contenu des tables OSM dans OSM-raw
debian@vps-02c1f983-vps-ovh-net:~$ docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "\d osm_raw.buildings"
Table "osm_raw.buildings"
Column | Type | Collation | Nullable | Default
--------------------+-------------------------+-----------+----------+---------
osm_id | bigint | | |
access | text | | |
addr:housename | text | | |
addr:housenumber | text | | |
addr:interpolation | text | | |
admin_level | text | | |
aerialway | text | | |
aeroway | text | | |
amenity | text | | |
area | text | | |
barrier | text | | |
bicycle | text | | |
brand | text | | |
bridge | text | | |
boundary | text | | |
building | text | | |
construction | text | | |
covered | text | | |
culvert | text | | |
cutting | text | | |
denomination | text | | |
disused | text | | |
embankment | text | | |
foot | text | | |
generator:source | text | | |
harbour | text | | |
highway | text | | |
historic | text | | |
horse | text | | |
intermittent | text | | |
junction | text | | |
landuse | text | | |
layer | text | | |
leisure | text | | |
lock | text | | |
man_made | text | | |
military | text | | |
motorcar | text | | |
name | text | | |
natural | text | | |
office | text | | |
oneway | text | | |
operator | text | | |
place | text | | |
population | text | | |
power | text | | |
power_source | text | | |
public_transport | text | | |
railway | text | | |
ref | text | | |
religion | text | | |
route | text | | |
service | text | | |
shop | text | | |
sport | text | | |
surface | text | | |
toll | text | | |
tourism | text | | |
tower:type | text | | |
tracktype | text | | |
tunnel | text | | |
water | text | | |
waterway | text | | |
wetland | text | | |
width | text | | |
wood | text | | |
z_order | integer | | |
way_area | real | | |
tags | hstore | | |
way | geometry(Geometry,3857) | | |
Indexes:
"planet_osm_polygon_osm_id_idx" btree (osm_id)
"planet_osm_polygon_way_idx" gist (way)
Triggers:
planet_osm_polygon_osm2pgsql_valid BEFORE INSERT OR UPDATE ON osm_raw.buildings FOR EACH ROW EXECUTE FUNCTION planet_osm_polygon_osm2pgsql_valid()
debian@vps-02c1f983-vps-ovh-net:~$
260407-Extraction OSM réussie sur Bandol
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
/* 1. NETTOYAGE PRÉALABLE */
DROP TABLE IF EXISTS \"83009_bandol\".armature_urbaine_osm;
/* 2. EXTRACTION MULTI-SOURCES (Points, Bâtiments, Zones) */
CREATE TABLE \"83009_bandol\".armature_urbaine_osm AS
SELECT * FROM (
-- BLOC A : Points d'intérêts (Commerces, Santé, Services)
SELECT
osm_id::bigint, name, amenity, leisure, shop,
'point'::text as osm_type,
ST_Transform(way, 2154) as geom
FROM osm_raw.points
WHERE (name IS NOT NULL)
AND (amenity NOT IN ('bench', 'waste_basket', 'post_box', 'vending_machine', 'parking_entrance', 'hunting_stand', 'parking') OR amenity IS NULL)
UNION ALL
-- BLOC B : Bâtiments structurants (Écoles, Mairie, Hôpitaux)
-- On utilise ST_Centroid pour transformer les surfaces en points localisables
SELECT
osm_id::bigint, name, amenity, leisure, NULL::text as shop,
'building'::text as osm_type,
ST_Transform(ST_Centroid(way), 2154) as geom
FROM osm_raw.buildings
WHERE (name IS NOT NULL)
AND (amenity NOT IN ('parking', 'garages', 'waste_disposal') OR amenity IS NULL)
UNION ALL
-- BLOC C : Zones de loisirs et parcs (Stades, Jardins)
-- Sécurisation du champ 'landuse' via un cast ::text pour éviter les erreurs de type record
SELECT
osm_id::bigint, name, NULL::text as amenity, NULL::text as leisure, NULL::text as shop,
'landuse'::text as osm_type,
ST_Transform(ST_Centroid(geom), 2154) as geom
FROM osm_raw.landuse
WHERE name IS NOT NULL
AND \"landuse\"::text NOT IN ('cemetery', 'residential', 'industrial', 'grass', 'forest', 'farmland')
) sub
WHERE geom IS NOT NULL;
/* 3. FILTRAGE GÉOGRAPHIQUE (Buffer de 5km autour du projet) */
-- On force le SRID 2154 pour la comparaison spatiale
DELETE FROM \"83009_bandol\".armature_urbaine_osm
WHERE NOT ST_DWithin(
geom,
(SELECT ST_SetSRID(ST_Centroid(ST_Extent(geom)), 2154) FROM \"83009_bandol\".bandol_parcelles),
5000
);
/* 4. QUALIFICATION THÉMATIQUE (Tri pour la légende QGIS) */
ALTER TABLE \"83009_bandol\".armature_urbaine_osm ADD COLUMN IF NOT EXISTS categorie text;
UPDATE \"83009_bandol\".armature_urbaine_osm SET categorie =
CASE
WHEN amenity IN ('school', 'kindergarten', 'college', 'university') OR name ILIKE '%école%' OR name ILIKE '%collège%' THEN 'Enseignement'
WHEN amenity IN ('restaurant', 'cafe', 'bar', 'fast_food', 'pub') THEN 'Restauration/Sorties'
WHEN amenity IN ('pharmacy', 'doctors', 'hospital', 'dentist') THEN 'Santé'
WHEN amenity IN ('bank', 'post_office', 'townhall', 'police') THEN 'Services Publics/Banques'
WHEN shop IS NOT NULL THEN 'Commerce'
WHEN leisure IS NOT NULL OR osm_type = 'landuse' THEN 'Loisirs/Espaces Verts'
ELSE 'Autre'
END;
/* 5. OPTIMISATION (Index GIST pour affichage rapide sous QGIS) */
CREATE INDEX idx_armature_geom ON \"83009_bandol\".armature_urbaine_osm USING GIST(geom);"
Les statistiques Sitadel
Pour documenter cette procédure, il est crucial de noter que nous avons abandonné les outils classiques comme ogr2ogr (trop sensibles aux noms de colonnes longs de Sitadel) pour une méthode hybride Python + SQL.
Cette approche est la seule qui garantit 100% de succès sur des fichiers de plus de 100 000 lignes avec des en-têtes complexes.
📑 Procédure d'import Sitadel (CSV vers PostGIS)
1. Préparation de la structure SQL
Avant d'importer, on crée une table "cible" avec des noms de colonnes courts et explicites. Cela évite que PostgreSQL ne tronque les noms originaux (ex: "surface de plancher de la destination...") et ne crée des doublons.
# Connexion à la base et création de la table
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "
DROP TABLE IF EXISTS sitadel_2026_04.logements_chantiers;
CREATE TABLE sitadel_2026_04.logements_chantiers (
num_permis text, -- Identifiant DAU
code_insee text, -- Code commune
date_chantier text, -- Date DOC
nb_log text, -- Nombre total logements
nb_social text, -- Logements locatifs sociaux
promoteur text, -- Dénomination Personne Morale
adresse_num text, -- Numéro de voie
adresse_voie text, -- Libellé de voie
parcelle text -- Numéro parcelle cadastrale
);"
2. Le Script d'import "Sniper" (Python)
Plutôt que d'envoyer tout le fichier (200+ colonnes), ce script sélectionne uniquement les colonnes légales nécessaires. Il gère également le séparateur (virgule , ou point-virgule ;) et nettoie les données à la volée.
Code à exécuter dans le terminal du VPS :
python3 -c "
import csv, sys
# Mapping entre les en-têtes officiels Sitadel et nos colonnes SQL
mapping = {
'Numéro d’enregistrement de la DAU': 'num_permis',
'Code de la commune du lieu des travaux': 'code_insee',
'Date réelle d’ouverture de chantier': 'date_chantier',
'Nombre total de logements créés': 'nb_log',
'Nb de logements locatifs sociaux': 'nb_social',
'Dénomination d’un demandeur avéré en tant que personne morale': 'promoteur',
'Numéro de voie du terrain': 'adresse_num',
'Libellé de la voie du terrain': 'adresse_voie',
'Numéro parcelle cadastrale 1': 'parcelle'
}
with open('2604_SitadelR93_logements.csv', 'r', encoding='utf-8-sig') as f:
# Lecture avec séparateur virgule (export QGIS standard)
reader = csv.reader(f, delimiter=',')
header = next(reader)
# Nettoyage des en-têtes (suppression des guillemets et espaces)
header = [h.strip().replace('\"', '') for h in header]
# Identification des index de colonnes
indices = [header.index(k) if k in header else None for k in mapping.keys()]
# Sortie formatée pour PostgreSQL COPY
writer = csv.writer(sys.stdout, delimiter='|')
for row in reader:
if len(row) > 0:
# On écrit uniquement les colonnes mappées
writer.writerow([row[i] if i is not None and i < len(row) else '' for i in indices])
" | docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo \
-c "COPY sitadel_2026_04.logements_chantiers FROM STDIN WITH (FORMAT csv, DELIMITER '|');"
3. Pourquoi cette méthode est la meilleure ?
🛠️ Vérification de l'import
Pour valider que la procédure a réussi, on compte les lignes chargées :
docker exec -i alteris_postgis psql -U alteris_admin -d alteris_geo -c "SELECT count(*) FROM sitadel_2026_04.logements_chantiers;"
2/ les extractions
On enchaîne. Une fois que la donnée est "au chaud" dans PostGIS, l'objectif est d'extraire l'intelligence métier. Pour ton rapport, on ne veut pas de fioritures : on veut de la donnée légale, datée et localisée.
Voici la documentation des requêtes SQL pour extraire le bilan des chantiers engagés à Bandol.
📑 Procédure d'Analyse SQL (Indicateurs C5e)
1. Bilan Annuel des Chantiers Réels
Cette requête groupe les données par année en se basant sur la Date de Déclaration d'Ouverture de Chantier (DOC). C'est l'indicateur de santé réelle de la construction.
-- Calcul du flux annuel de logements engagés
SELECT
LEFT(date_chantier, 4) as annee,
COUNT(*) as nb_permis_doc,
SUM(nb_log::int) as total_logements,
SUM(COALESCE(nb_social, '0')::int) as dont_social,
ROUND((SUM(COALESCE(nb_social, '0')::float) / NULLIF(SUM(nb_log::float), 0) * 100)::numeric, 1) as part_social_pct
FROM sitadel_2026_04.logements_chantiers
WHERE code_insee = '83009' -- Filtre Bandol
AND date_chantier >= '2019-01-01'
GROUP BY annee
ORDER BY annee DESC;
2. Identification des 10 Projets Majeurs
Cette requête permet d'isoler les opérations qui structurent le marché local. On y intègre l'identifiant DAU (Numéro de permis) pour assurer la traçabilité juridique.
-- Listing des 10 plus gros chantiers depuis 2019
SELECT
num_permis as dau_officiel,
COALESCE(NULLIF(promoteur, ''), 'PARTICULIER') as operateur,
nb_log::int as nb_log,
COALESCE(nb_social, '0')::int as nb_social,
date_chantier as date_doc,
CONCAT(adresse_num, ' ', adresse_voie) as adresse_chantier,
parcelle as ref_cadastrale
FROM sitadel_2026_04.logements_chantiers
WHERE code_insee = '83009'
AND date_chantier >= '2019-01-01'
ORDER BY nb_log::int DESC
LIMIT 10;
3. Les points de vigilance technique (pour ta doc)
-
Le Cast Type (
::int) : Comme Sitadel contient parfois des cases vides ou des caractères bizarres, nous avons importé les colonnes enTEXT. Pour faire des calculs (SUM,ORDER BY), il est impératif de forcer le type en entier avec::intou::float. -
Le
COALESCE: Indispensable pour la colonne Social. Si la case est vide, PostgreSQL renvoieNULL. LeCOALESCE(..., '0')force la valeur à zéro pour ne pas fausser les additions. -
Le
NULLIF: Prévient l'erreur "division par zéro" dans le calcul du pourcentage si une année ne compte aucun logement.
📦 4. Export vers le format GeoPackage (GPKG)
Pour que ton équipe SIG puisse utiliser ces données sans se reconnecter à la base, on documente la commande d'exportation vers un fichier portable.
# Export de la table filtrée Bandol vers un fichier GPKG
docker exec -i alteris_postgis ogr2ogr -f GPKG /tmp/bandol_bilan_chantiers.gpkg \
PG:"host=localhost dbname=alteris_geo user=alteris_admin" \
-sql "SELECT * FROM sitadel_2026_04.logements_chantiers WHERE code_insee = '83009' AND date_chantier >= '2019-01-01'" \
-nln sitadel_chantiers_engages
# Récupération du fichier sur le stockage local du VPS
docker cp alteris_postgis:/tmp/bandol_bilan_chantiers.gpkg ./bandol_bilan_chantiers_2019_2026.gpkg
C'est la fin du volet "Données & Requêtes". Veux-tu qu'on termine cette documentation par la partie Cartographique : comment faire la jointure dans QGIS entre ce fichier GPKG et tes parcelles cadastrales pour obtenir la carte finale des chantiers ?
Données Filosofi - téléchargement
https://www.insee.fr/fr/statistiques/8735108?sommaire=8735243