Estimation de la surface habitable — Documentation technique
Derniere mise a jour : 2026-03-15 Fichier source :
api/services/building_height_service.py
Table des matieres
- Vue d'ensemble
- Sources de donnees
- Architecture du service
- Algorithme principal — mode combine
- Fallback — Solar seul
- Fallback — LiDAR seul
- Cache PostgreSQL
- Coefficients d'habitabilite
- Exemples de calcul traces
- Limites connues et precision
1. Vue d'ensemble
Le service building_height_service.py estime la surface habitable d'un batiment belge
a partir de 3 sources de donnees combinees en parallele :
fetch_building_analysis(lat, lon, ...)
|
+--------------+--------------+
| | |
LiDAR DSM LiDAR DTM Google Solar API
(surface 3D) (terrain nu) (segments de toit)
| | |
+----- H = DSM - DTM |
(hauteur) |
| |
+----- _parse_combined() -----> Resultat
Formule finale
Surface habitable = Emprise_corrigee × (Niveaux + Facteur_combles) × Coefficient
Ou :
- Emprise_corrigee = emprise CadGIS (si disponible, confiance 0.95) ou emprise OSM (confiance 0.75), ajustee par ratio segmentation (excluant terrain/chapiteaux). Exception : pour les maisons mitoyennes/en rangee (2-3 facades), l'emprise OSM brute est utilisee directement sans correction Solar (voir §2.4)
- Niveaux = estimation via hauteur a l'egout / 3m (seuil 0.7, ou OSM
building:levelssi disponible) - Facteur_combles = 0 par defaut. Active uniquement si l'analyse visuelle IA (OpenAI Vision via Street View) confirme la presence de combles amenages (
has_visible_attic = true, detection de lucarnes/Velux). Quand active : fraction de l'etage sous toit habitable (>= 1.8m, norme belge) - Coefficient = ratio surface utile / surface brute selon le type de batiment
2. Sources de donnees
2.1 LiDAR regional belge (hauteur precise)
| Region | Service | Resolution | Protocole |
|---|---|---|---|
| Wallonie | SPW geoservices.wallonie.be MNS/MNT 2021-2022 | 50 cm | ArcGIS REST identify |
| Flandre + Bruxelles | Vlaanderen geo.api.vlaanderen.be DHMVII | 1 m | OGC WCS GetCoverage |
- DSM (Digital Surface Model / MNS) : elevation incluant les batiments, arbres, etc.
- DTM (Digital Terrain Model / MNT) : elevation du terrain nu
- Hauteur batiment = DSM - DTM (faitAge au-dessus du sol)
Le seuil de latitude pour choisir Wallonie vs Flandre est 50.75°N.
Wallonie — ArcGIS REST identify
GET geoservices.wallonie.be/.../MapServer/identify
?geometry={lon},{lat}&sr=4326&f=json
Reponse : results[0].attributes["Stretch.Pixel Value"] → float (metres ASL)
Flandre — WCS GetCoverage
GET geo.api.vlaanderen.be/DHMV/wcs
?COVERAGEID=DHMVII_DSM_1m
&subset=x,...(lambert72)
&subset=y,...(lambert72)
&FORMAT=image/tiff
- Necessite conversion WGS84 → Lambert 72 (EPSG:31370)
- Reponse : multipart MIME avec GeoTIFF float32
- Extraction du pixel central via parseur TIFF minimal (stdlib
struct) - Tuile 3×3m centree sur le point
Conversion WGS84 → Lambert 72
Formule mathematique approchee (pas de pyproj) basee sur les parametres NGI :
- Ellipsoide : Hayford 1924 (a=6378388, e=0.08199189)
- Paralleles : 49°50' et 51°10'
- Origine : 150000.013, 5400088.438
- Precision : ~1-2m sur toute la Belgique
2.2 Google Solar API (segmentation du toit)
GET solar.googleapis.com/v1/buildingInsights:findClosest
?location.latitude={lat}&location.longitude={lon}
&requiredQuality=MEDIUM&key={GOOGLE_MAPS_KEY}
Champs cles de la reponse :
{
"solarPotential": {
"buildingStats": {
"areaMeters2": 360.8, // surface toit inclinee totale
"groundAreaMeters2": 303.3 // projection au sol totale
},
"roofSegmentStats": [
{
"planeHeightAtCenterMeters": 143.1, // altitude ASL (PAS hauteur AGL !)
"pitchDegrees": 39, // pente du segment
"azimuthDegrees": 235, // orientation
"stats": {
"areaMeters2": 189.6, // surface inclinee du segment
"groundAreaMeters2": 147.3 // projection au sol du segment
}
}
]
},
"imageryQuality": "MEDIUM"
}
ATTENTION — Piege planeHeightAtCenterMeters :
- C'est une altitude absolue (ASL = Above Sea Level), PAS une hauteur au-dessus du sol
- C'est le centre geometrique du plan de toit, PAS le faitAge
- Pour un toit en pente, le centre est significativement plus bas que le sommet
- Conversion necessaire :
AGL = ASL - DTM(LiDAR terrain nu)
ATTENTION — buildingStats.groundAreaMeters2 :
- C'est la projection au sol de tous les segments de toit detectes par Google
- Ce n'est PAS l'emprise du batiment et PAS la surface habitable
- Google peut detecter des batiments voisins, des chapiteaux, du terrain comme faisant partie du meme "batiment"
- Souvent tres different du polygone OSM (parfois 50-60% plus grand)
2.3 OpenStreetMap (emprise au sol) + CadGIS (emprise cadastrale officielle)
L'emprise au sol peut provenir de deux sources, par ordre de preference :
-
CadGIS (SPF Finances) — emprises de construction officielles du cadastre belge (~9.6M batiments). Table
cadgis_buildings. Jointure automatique dansassess_point()v6 par proximite de centroide (ST_DWithin10m). Confiance : 0.95. -
OpenStreetMap — table
buildings(7.4M batiments belges). Polygones dessines par des contributeurs benevoles. Calculee commeST_Area(geom::geography). Confiance : 0.75.
Le champ footprint_source dans la reponse indique quelle source a ete utilisee ('cadgis' ou 'osm'). Quand CadGIS est disponible, cadgis_area_m2 contient la surface officielle et le service building_height_service.py l'utilise en priorite pour le calcul de surface habitable.
Champs OSM utilises :
area_m2: emprise au sol du polygonebuilding: type (house,apartments,garage,yes, etc.)building:levels: nombre de niveaux (souvent absent)
2.4 Selection de l'emprise selon la morphologie du batiment
Ajout mars 2026 — La fonction
_select_footprint()dansassess_service.pyprend desormais un parametremorphologyqui influence le choix de la source d'emprise au sol. Cette logique s'applique aux deux appels a_select_footprint()(selection initiale et re-verification multi-source).
Problematique : maisons mitoyennes et en rangee
Pour les batiments 4 facades (detaches), CadGIS est la meilleure source : l'emprise cadastrale officielle est precise pour les batiments isoles (confiance 0.95).
Pour les batiments 2-3 facades (mitoyens, en rangee), trois problemes rendent CadGIS et les corrections Solar/LiDAR inadaptes :
-
CadGIS ne couvre que le corps principal — le cadastre belge enregistre l'emprise du batiment d'origine, sans les extensions, verandas, annexes integrees. Or ces ajouts sont tres courants dans les maisons en rangee belges.
-
Google Solar ne separe pas les maisons adjacentes — les segments de toit d'une maison mitoyenne incluent souvent les toitures voisines (meme hauteur de faitAge, toits contigus). Le ratio de correction Solar est donc fausse.
-
LiDAR ne distingue pas les batiments accoles — la grille 2D DSM-DTM mesure la hauteur du toit en continu sur toute la rangee. Impossible de delimiter l'emprise d'une seule maison.
En revanche, les contributeurs OpenStreetMap tracent generalement le contour complet de chaque maison individuelle, y compris les extensions arriere. L'emprise OSM est donc plus representative de la surface reelle au sol pour ce type de batiment.
Regle de selection
_select_footprint(cadgis_area, osm_area, morphology):
SI morphology ∈ ("terraced", "semi_detached") ET osm_area > 15 m2 :
→ retourner (osm_area, "osm", "morphology_row_house")
→ PAS de correction Solar/LiDAR sur l'emprise
SINON (4 facades / detached / inconnu) :
→ logique existante : CadGIS (confiance 0.95) > OSM (confiance 0.75)
→ corrections Solar/LiDAR appliquees normalement
Le seuil de 15 m2 evite d'utiliser des polygones OSM aberrants (micro-traces,
erreurs de cartographie).
Impact sur le calcul de surface
Pour les maisons mitoyennes (morphology_row_house) :
- Emprise :
osm_areabrut, sans correction par le ratio Solar - Niveaux : toujours estimes via LiDAR (hauteur DSM-DTM) ou OSM
building:levels - LiDAR 2D : la grille ne fournit que le nombre de niveaux (
estimated_levels) via l'analyse de hauteur ; elle ne surcharge plusmain_area_m2 - Cache : version v15
Le champ footprint_source dans la reponse API vaut "osm" et un nouveau champ
footprint_reason vaut "morphology_row_house".
Exemple reel : maison mitoyenne a Trooz (50.61028, 5.60205)
Donnees :
Morphologie : terraced (2 facades)
CadGIS area : 49.0 m2 (corps principal uniquement)
OSM area : 76.5 m2 (inclut extension arriere)
Ancien comportement :
footprint_source = cadgis ou satellite_confirms_osm
main_area = 60.1 m2 (corrige par ratio Solar — faux car Solar voit les voisins)
Nouveau comportement :
footprint_source = osm
footprint_reason = morphology_row_house
main_area = 76.5 m2 (emprise OSM brute, pas de correction Solar)
Calcul surface :
76.5 × 3 niveaux × 0.80 coeff = 183.6 m2
Comparaison :
Avec CadGIS seul : 49.0 × 3 × 0.80 = 117.6 m2 (sous-estime — ignore extensions)
Avec Solar-corrected: 60.1 × 3 × 0.80 = 144.2 m2 (sous-estime — ratio fausse)
Avec OSM brut : 76.5 × 3 × 0.80 = 183.6 m2 (le plus realiste)
3. Architecture du service
3.1 Point d'entree
async def fetch_building_analysis(
lat, lon,
osm_id=None, # Pour persister dans building_lidar
osm_levels=None, # Override niveaux si disponible
building_type=None, # "house", "apartments", "garage", etc.
footprint_area=None, # Emprise OSM en m2
ground_elevation=None, # Altitude Open-Meteo (fallback DTM)
) -> Optional[dict]
3.2 Orchestration parallele
t=0ms Lancer LiDAR DSM + LiDAR DTM + Google Solar en parallele
t=~500ms Await DSM, DTM → calculer hauteur = DSM - DTM
t=~800ms Await Google Solar (timeout 10s)
t=~800ms Combiner selon disponibilite :
1. LiDAR + Solar → _parse_combined() [meilleur]
2. Solar seul → _parse_solar_only() [moyen]
3. LiDAR seul → _make_lidar_result() [basique]
t=~850ms Persister dans building_lidar (cache PostgreSQL)
3.3 Integration dans assess_service.py
Le service est appele depuis assess() apres l'obtention de l'elevation Open-Meteo :
# 1. PostGIS assess_point(lat, lon) → building info basique
# 2. await elevation (Open-Meteo)
# 3. Si building existe MAIS building_height_m is None :
# → Lancer fetch_building_analysis() en parallele (timeout 12s)
# 4. Enrichir BuildingInfo avec le resultat
Le building_lidar est aussi integre dans assess_point() v3 via LEFT JOIN :
si les donnees ont deja ete calculees, elles sont retournees directement
par PostGIS sans appel externe.
4. Algorithme principal — mode combine
Fichier : _parse_combined() (L185-365)
C'est le mode le plus precis. Il combine :
- LiDAR pour la hauteur (DSM-DTM = faitAge - sol, resolution 50cm-1m)
- Google Solar pour la segmentation (pentes, annexes, type de toit)
- OSM pour l'emprise au sol (polygone batiment)
Etape 1 — Extraction des segments avec AGL
for chaque segment Google Solar:
AGL = ASL_segment - DTM_terrain
si AGL > 0: garder le segment (agl, surface_au_sol, pente)
si max(AGL) < 2m: pas de batiment → retourner None
On ne filtre PAS par hauteur a cette etape. Tous les segments avec AGL > 0 sont conserves pour l'analyse gap-based (etape 2).
Etape 2 — Detection d'annexe (gap-based)
Objectif : separer la structure principale des structures basses (garages, chapiteaux, terrasses, terrain).
Algorithme :
1. Trier les segments par AGL decroissant
2. Calculer l'ecart (gap) entre chaque paire consecutive
3. Si le plus grand ecart > 3m → separation
- Au-dessus du gap = structure principale (main)
- En-dessous du gap = structures basses (low)
Exemple (batiment Route des Canons, Namur) :
Segments tries par AGL :
Seg 3: AGL=6.7m, 147.3 m2 ← PRINCIPAL
────── gap = 5.6m > 3m ────── ← COUPURE
Seg 2: AGL=1.1m, 78.1 m2 ← BAS (terrain/chapiteau)
Seg 1: AGL=1.0m, 42.6 m2 ← BAS
Seg 0: AGL=0.1m, 25.2 m2 ← BAS
Pourquoi gap-based plutot qu'un seuil fixe ? Un seuil fixe (ex: 50% de la hauteur max) echoue quand le batiment principal est bas (4-5m) — le seuil tombe a 2-2.5m et inclut des garages. Le gap naturel detecte la rupture structurelle reelle.
Etape 3 — Classification des segments bas
Les segments sous le gap sont classes en 3 categories :
| AGL | Classification | Traitement |
|---|---|---|
| < 2m | Terrain (chapiteaux, terrasses, sol) | Exclu du footprint |
| 2m a 3.5m | Garage / abri | Annexe non-habitable (exclue de la surface) |
| > 3.5m | Extension habitable | Comptee comme 1 niveau de surface |
Etape 4 — Correction de l'emprise au sol
L'emprise OSM peut inclure des structures non-bati (chapiteaux, terrasses) detectees par Google Solar comme segments au sol. On corrige :
building_ratio = surface_segments_batiment / surface_segments_total
emprise_corrigee = emprise_OSM × building_ratio
Exemple (Route des Canons) :
Segments batiment : 147.3 m2 (principal uniquement)
Segments terrain : 145.9 m2 (3 segments AGL < 2m)
Total : 293.2 m2
building_ratio = 147.3 / 293.2 = 0.502
emprise_corrigee = 192.7 × 0.502 = 96.8 m2
Si aucun segment terrain n'est detecte, l'emprise OSM est utilisee telle quelle.
Etape 5 — Type de toit
pente_max = max(pente des segments principaux)
type_toit = "plat" si pente_max < 10°, sinon "pente"
Etape 6 — Detection structure mono-niveau
Certains types de batiments OSM sont forces a 1 niveau :
garage, shed, industrial, warehouse, hangar, barn
Etape 7 — Estimation des niveaux et combles
Toit plat
niveaux = int(hauteur_LiDAR / 3.0 + 0.3) # seuil 0.7 (conservateur assurance)
pas de combles
Toit en pente
La hauteur LiDAR inclut le toit. Il faut estimer la hauteur du toit pour deduire la hauteur a l'egout (= hauteur des murs = niveaux).
╱╲ ← faitAge (hauteur LiDAR)
╱ ╲
╱ ╲ hauteur_toit
╱ pente ╲
────────────╱────────╲────────── egout
| |
| niveaux | hauteur_egout = hauteur_LiDAR - hauteur_toit
| × 3m |
────────────|────────|────────── sol
|<-demi->|
portee
Calcul detaille :
# Ratio d'aspect typique Belgique
aspect = 1.5 # maison individuelle (longueur ≈ 1.5× largeur)
aspect = 2.0 # immeuble, commercial (longueur ≈ 2× largeur)
# Dimensions estimees
largeur = sqrt(emprise_corrigee / aspect)
demi_portee = largeur / 2
# Hauteur geometrique du toit
hauteur_toit = demi_portee × tan(pente_max)
# Bornee : min 1.5m, max 40% de la hauteur totale
hauteur_toit = max(1.5, min(hauteur_toit, hauteur_LiDAR × 0.4))
# Hauteur a l'egout
hauteur_egout = hauteur_LiDAR - hauteur_toit
# Niveaux
niveaux = int(hauteur_egout / 3.0 + 0.3) # seuil 0.7 (conservateur assurance)
niveaux = max(1, min(niveaux, 15)) # bornes securite
Exemple (Trooz, 50.61028°N, 5.60205°E) :
emprise = 76.5 m2, hauteur_LiDAR = 10.0m, pente = 28°
aspect = 1.5 (type "yes" = residentiel)
largeur = sqrt(76.5 / 1.5) = 7.14 m
demi_portee = 3.57 m
hauteur_toit = 3.57 × tan(28°) = 1.90 m
bornee: max(1.5, min(1.90, 4.0)) = 1.90 m ✓
hauteur_egout = 10.0 - 1.90 = 8.10 m
niveaux = int(8.10/3.0 + 0.3) = int(3.0) = 3
Facteur combles (toit en pente uniquement)
Le facteur combles represente la fraction de l'etage sous toit ou la hauteur sous plafond est ≥ 1.8m (norme belge d'habitabilite).
Changement mars 2026 : le facteur combles est desormais desactive par defaut (
attic_factor = 0.0) dans tous les parsers (_parse_combined,_parse_solar_only,_make_lidar_result). En Belgique, environ 65% des maisons n'ont pas de combles amenages. L'ancien algorithme, qui activait les combles des que la geometrie le permettait, surestimait la surface habitable de 7 a 15%.Le facteur geometrique complet n'est active que lorsque l'analyse visuelle IA (OpenAI Vision via Street View) detecte des lucarnes ou Velux et renvoie
has_visible_attic = true. Dans ce cas, la fonctionrecalculate_living_area()applique le facteur geometrique ci-dessous.
╱╲
╱ ╲
╱ ╲
1.8m╱......╲ ← seuil habitabilite
╱ habit. ╲
───────────╱──────────╲───────── egout
|<- zone ->|
| ≥ 1.8m |
Formule (appliquee uniquement si vision IA confirme les combles) :
facteur_combles = max(0, 1 - 1.8 / (demi_portee × tan(pente)))
Intuition : c'est le ratio entre la largeur ou la hauteur depasse 1.8m et la largeur totale sous le toit.
Conditions d'activation (cumulatives) :
has_visible_attic = true(lucarnes/Velux detectes par vision IA)facteur_combles >= 0.15(au moins 15% de la surface est habitable)hauteur_toit > 1.8m(le toit est assez haut pour y habiter)
Si la condition 1 n'est pas remplie, facteur_combles = 0 quel que soit
le resultat geometrique. Le champ API attic_area_m2 vaut alors null ou 0.
Exemples :
| Pente | Demi-portee | Hauteur toit | Facteur geo. | Vision IA | Facteur applique | Interpretation |
|---|---|---|---|---|---|---|
| 28° | 3.57m | 1.90m | 5% | — | 0 | Combles trop etroits (< 15%), meme si vision confirme |
| 39° | 5.67m | 4.59m | 61% | non | 0 | Geometrie OK mais pas de lucarnes → 0 par defaut |
| 39° | 5.67m | 4.59m | 61% | oui | 61% | Lucarnes detectees → facteur geometrique applique |
| 45° | 4.00m | 4.00m | 55% | oui | 55% | Velux detectes → 55% habitable |
| 15° | 5.00m | 1.34m | 0% | — | 0 | Toit trop plat → pas de combles |
Etape 8 — Override OSM
Si le tag OSM building:levels est disponible (et ≥ 1), il remplace
l'estimation LiDAR pour le nombre de niveaux. C'est plus fiable car
il provient de contributeurs humains qui connaissent le batiment.
si osm_levels >= 1:
niveaux = osm_levels
source = "osm"
sinon:
source = "google_solar+lidar"
Etape 9 — Surface habitable finale
coefficient = COEFFICIENTS[type_batiment] # ex: 0.82 pour "house"
surface_principale = emprise_corrigee × (niveaux + facteur_combles) × coefficient
surface_annexe = emprise_annexe × coefficient # si annexe habitable (AGL > 3.5m)
# sinon 0
attic_area_m2 = emprise_corrigee × facteur_combles × coefficient
# null/0 si facteur_combles = 0 (vision non confirmee)
surface_habitable = surface_principale + surface_annexe
Note :
facteur_combles = 0par defaut. La formule revient donc aemprise_corrigee × niveaux × coefficientsauf si la vision IA a confirme les combles amenages.
5. Fallback — Solar seul
Fonction : _parse_solar_only() (L368-456)
Utilise quand le LiDAR est indisponible (ex: zone frontaliere, erreur reseau).
Differences avec le mode combine :
- Hauteur = centre du plan de toit le plus haut - DTM (sous-estime pour toits en pente)
- Separation main/annexe par seuil fixe
max(4m, 50% × hauteur_max) - Facteur combles = 0 par defaut (comme le mode combine). Active uniquement si vision IA confirme (
has_visible_attic = true), auquel cas le facteur geometrique est utilise viarecalculate_living_area() - Moins precis globalement (~20-30% d'erreur sur la hauteur)
6. Fallback — LiDAR seul
Fonction : _make_lidar_result() (L459-500)
Utilise quand Google Solar est indisponible (404, timeout, pas de cle API).
Differences avec le mode combine :
- Pas de segmentation toit (pas de detection d'annexe)
- Correction toit approximative :
hauteur_effective = hauteur × 0.85 - Pas de detection de combles (
has_attic = None) - L'emprise OSM complete est utilisee (pas de correction terrain)
7. Cache PostgreSQL
Table building_lidar
CREATE TABLE building_lidar (
osm_id BIGINT PRIMARY KEY,
building_height_m FLOAT,
roof_type TEXT, -- 'flat' | 'pitched'
annex_detected BOOLEAN,
main_area_m2 FLOAT,
annex_area_m2 FLOAT DEFAULT 0,
estimated_levels SMALLINT,
has_attic BOOLEAN,
attic_area_m2 FLOAT, -- surface combles (null/0 si non confirme, valeur si vision IA confirme)
estimated_living_area_m2 FLOAT,
area_coefficient FLOAT,
levels_source TEXT, -- 'osm' | 'google_solar+lidar' | 'lidar'
data_source TEXT, -- 'google_solar+lidar' | 'google_solar' | 'lidar_*'
imagery_quality TEXT, -- 'HIGH' | 'MEDIUM' | 'LOW'
computed_at TIMESTAMPTZ DEFAULT NOW()
);
Cycle de vie
- Premiere requete pour un batiment → appels LiDAR + Google Solar (~1-2s)
- Resultat stocke dans
building_lidar(INSERT ON CONFLICT DO UPDATE) - Requetes suivantes →
assess_point()fait LEFT JOIN → donnees servies depuis PostgreSQL (0ms, pas d'appel externe)
8. Coefficients d'habitabilite
Le coefficient represente le ratio entre la surface habitable nette et la surface brute (emprise × niveaux). Il tient compte des murs, cages d'escalier, gaines techniques, parties communes, etc.
| Type OSM | Coefficient | Justification |
|---|---|---|
house, detached, residential, semidetached_house, terrace | 0.82 | Murs porteurs, cage d'escalier |
apartments, dormitory | 0.77 | Parties communes, hall, ascenseur |
commercial, retail | 0.87 | Grandes surfaces, moins de cloisonnement |
office | 0.85 | Espaces ouverts, gaines techniques |
industrial, warehouse | 0.90 | Volume unique, peu de pertes |
garage, shed | 0.95 | Structure simple, quasi tout utilisable |
yes (generique) | 0.80 | Valeur par defaut conservatrice |
Si le type OSM n'est pas dans la table, le coefficient par defaut est 0.80.
9. Exemples de calcul traces
9.1 Maison individuelle a Trooz (50.61028, 5.60205)
Donnees brutes :
OSM : osm_id=796597150, type="yes", levels=null, area=76.5 m2
LiDAR DSM : 76.68m → DTM : 66.64m → hauteur = 10.0m
Google Solar : 2 segments, imagery=MEDIUM
Seg 0: AGL=3.8m, area=39.3m2, pitch=28°
Seg 1: AGL=4.2m, area=36.5m2, pitch=28°
Etape 2 — Gap analysis :
Trie: [(4.2, 36.5, 28), (3.8, 39.3, 28)]
Gap : 0.4m < 3m → pas de separation
main_segs = tous, low_segs = aucun
Etape 3 — Classification : pas de segments bas
Etape 4 — Emprise : pas de terrain detecte
main_area = 76.5 m2 (OSM complet)
Etape 5 — Toit : pente=28° → "pitched"
Etape 7 — Niveaux :
aspect=1.5, largeur=sqrt(76.5/1.5)=7.14m, demi_portee=3.57m
hauteur_toit = 3.57 × tan(28°) = 3.57 × 0.5317 = 1.90m
bornee: max(1.5, min(1.90, 4.0)) = 1.90m
hauteur_egout = 10.0 - 1.90 = 8.10m
niveaux = int(8.10/3.0 + 0.3) = int(2.70 + 0.3) = int(3.0) = 3
Facteur combles :
facteur_geo = 1 - 1.8/(3.57 × 0.5317) = 1 - 1.8/1.90 = 0.052 (5.2%)
5.2% < 15% → combles trop etroits (exclu geometriquement)
attic_factor = 0
attic_area_m2 = null
Etape 9 — Surface :
coefficient = 0.80 (type "yes")
surface = 76.5 × (3 + 0) × 0.80 = 76.5 × 3 × 0.80 = 183.6 m2
RESULTAT : 183.6 m2 habitable, 3 niveaux, toit pente, pas de combles
9.2 Batiment avec chapiteau adjacent (50.45973, 4.85704)
Donnees brutes :
OSM : osm_id=628906621, type="yes", levels=null, area=192.7 m2
LiDAR DSM : 149.26m → DTM : 136.36m → hauteur = 12.9m
Google Solar : 4 segments, imagery=MEDIUM
Seg 0: ASL=136.5m → AGL=0.1m, area=25.2m2, pitch=12°
Seg 1: ASL=137.4m → AGL=1.0m, area=42.6m2, pitch=23°
Seg 2: ASL=137.5m → AGL=1.1m, area=78.1m2, pitch=13°
Seg 3: ASL=143.1m → AGL=6.7m, area=147.3m2, pitch=39°
Contexte : chapiteau (~60 m2) a cote du batiment,
polygone OSM couvre les deux (192.7 m2 au lieu de ~122 m2 reel)
Etape 2 — Gap analysis :
Trie par AGL desc: [(6.7, 147.3, 39), (1.1, 78.1, 13), (1.0, 42.6, 23), (0.1, 25.2, 12)]
Gaps : 5.6, 0.1, 0.9
Meilleur gap = 5.6m > 3m → COUPURE a index 0
main_segs = [(6.7, 147.3, 39)]
low_segs = [(1.1, 78.1, 13), (1.0, 42.6, 23), (0.1, 25.2, 12)]
Etape 3 — Classification :
Tous les low_segs ont AGL < 2m → TERRAIN (chapiteau + sol)
terrain_seg_area = 145.9 m2
annex_segs = aucun
Etape 4 — Correction emprise :
building_seg_area = 147.3 (principal seul)
total_seg_area = 147.3 + 145.9 = 293.2
building_ratio = 147.3 / 293.2 = 0.502
emprise_corrigee = 192.7 × 0.502 = 96.8 m2
(proche du reel ~122 m2 — l'ecart vient de Google qui voit les voisins)
Etape 7 — Niveaux :
aspect=1.5, largeur=sqrt(96.8/1.5)=8.03m, demi_portee=4.01m
hauteur_toit = 4.01 × tan(39°) = 4.01 × 0.8098 = 3.25m
bornee: max(1.5, min(3.25, 5.16)) = 3.25m
hauteur_egout = 12.9 - 3.25 = 9.65m
niveaux = int(9.65/3.0 + 0.3) = int(3.22 + 0.3) = int(3.52) = 3
Facteur combles :
facteur_geo = 1 - 1.8/(4.01 × 0.8098) = 1 - 1.8/3.25 = 0.446 (44.6%)
44.6% >= 15% ET 3.25m > 1.8m → geometrie OK
CAS A — Vision IA non effectuee ou pas de lucarnes detectees :
attic_factor = 0 (defaut)
attic_area_m2 = null
surface = 96.8 × (3 + 0) × 0.80 = 96.8 × 3 × 0.80 = 232.3 m2
CAS B — Vision IA confirme des lucarnes (has_visible_attic = true) :
attic_factor = 0.446 (facteur geometrique applique)
attic_area_m2 = 96.8 × 0.446 × 0.80 = 34.5 m2
surface = 96.8 × (3 + 0.446) × 0.80 = 96.8 × 3.446 × 0.80 = 266.9 m2
RESULTAT (sans vision) : 232.3 m2, 3 niveaux, toit pente, pas de combles
RESULTAT (avec vision) : 266.9 m2, 3 niveaux + combles (45%), toit pente
Comparaison :
Sans correction terrain : 192.7 × 3 × 0.80 = 462.5 m2 (FAUX)
Avec correction, sans combles : 96.8 × 3 × 0.80 = 232.3 m2 (defaut)
Avec correction, avec combles : 96.8 × 3.446 × 0.80 = 266.9 m2 (si vision confirme)
10. Limites connues et precision
10.1 Precision estimee
| Mode | Precision hauteur | Precision surface |
|---|---|---|
| LiDAR + Solar | ±0.5m (LiDAR 50cm) | ±15-25% |
| Solar seul | ±2-3m (centre plan toit) | ±25-40% |
| LiDAR seul | ±0.5m | ±20-30% |
10.2 Limites connues
-
Emprise au sol : lorsque l'emprise CadGIS (SPF Finances) est disponible, la precision est cadastrale (confiance 0.95). En fallback, les polygones OSM sont utilises (contributeurs benevoles, precision variable ~5-20%, confiance 0.75). Certains polygones OSM incluent des structures annexes (garages, terrasses, chapiteaux). Le champ
footprint_sourceindique la source utilisee. Exception : pour les maisons mitoyennes/en rangee (2-3 facades), l'emprise OSM est preferee car CadGIS ne couvre que le corps principal (voir §2.4). -
LiDAR point unique : on mesure DSM et DTM en un seul point (le centre du batiment). Sur un terrain en pente, la hauteur peut varier selon le cote du batiment. Erreur potentielle : 1-3m sur terrain tres pentu.
-
Google Solar multi-batiment : Google detecte parfois des batiments voisins comme faisant partie du meme ensemble. Sa
groundAreaMeters2peut etre 50-60% plus grande que le polygone OSM. -
planeHeightAtCenterMeters= centre de la pente : pour un toit en pente, ce n'est ni le faitAge ni l'egout, mais le milieu geometrique. C'est pourquoi on utilise le LiDAR pour la hauteur et Google Solar uniquement pour la segmentation. -
Combles : desactives par defaut : le facteur combles est
0tant que l'analyse visuelle IA n'a pas confirme la presence de lucarnes ou Velux. Cela evite la surestimation (~65% des maisons belges n'ont pas de combles amenages). Quand la vision IA confirme, le facteur geometrique suppose un toit a 2 pentes symetrique ; les toits a 4 pans, asymetriques, ou en L auront un facteur reel different. -
Coefficient d'habitabilite : les valeurs sont des moyennes statistiques. Un batiment renove avec murs fins aura un coefficient reel plus eleve ; un batiment ancien avec murs epais aura un coefficient plus bas.
-
Pas de detection de sous-sol : l'algorithme ne detecte pas les etages enterres ou semi-enterres. La surface habitable estimee ne couvre que les etages au-dessus du sol.
10.3 Sources d'amelioration futures
Cadastre belge: ✅ Implemente — les emprises CadGIS (SPF Finances, ~9.6M batiments) sont desormais jointes automatiquement viaassess_point()v6 (confiance 0.95 vs 0.75 OSM)- LiDAR multi-points : mesurer DSM/DTM en 4 coins du batiment pour detecter les pentes de terrain et corriger la hauteur
- Machine learning : entrainer un modele sur les biens avec surface habitable connue (donnees notariales) pour calibrer les coefficients