loko/streetup/assets/management/commands/import_rwi.py
2026-07-22 14:48:40 +02:00

291 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from django.core.management.base import BaseCommand
from django.conf import settings
from django.contrib.gis.gdal import DataSource
from django.contrib.gis.geos import GEOSGeometry
from django.utils.dateparse import parse_date
from django.db import transaction
from assets.models import NatureRWIAsset
# Dutch translations for type_ouvrage
TYPE_OUVRAGE_NL_MAP = {
# Points
'avaloir': 'afvoerput',
'bordure sequencee': 'rand met sequentie',
'bouche injection': 'injectieopening',
'clapet anti-retour': 'terugslagklep',
'empierrement sedimentation': 'verstening sedimentatie',
'kerb cell': 'kerbcel',
'puits infiltration': 'infiltratieput',
'surverse': 'overloop',
'vanne isolation': 'afsluitklep',
'vanne regulation': 'regelklep',
# Lines
'gargouille de trottoir': 'stoepgootsteen',
'filet eau': 'druppel water',
'avaloir de rail': 'spoorafvoer',
'bordure arasee': 'afgewerkte rand',
'caniveau infiltrant': 'infiltratiegoot',
'drain': 'afvoer',
'trop plein': 'overvol',
'reseau egout separatif': 'gescheiden rioolnetwerk',
# Polygons
'bassin sec/infiltration': 'droog bassin / infiltratiebekken',
'bassin en eau': 'waterbak',
'fosse': 'greppel',
'jardin de pluie': 'regentuin',
'noue': 'knoop',
'massif drainant': 'drainageblok',
'tranchee drainante': 'afwateringsgreppel',
'toiture stockante': 'stokkend dak',
'toiture verte': 'groendak',
'bassin orage': 'regenwaterreservoir',
'SAUL-vide': 'ultralichte holle structuur',
'SAUL-terre': 'ultralichte honingraatstructuur aarde',
'citerne': 'tankwagen',
'espace vert sur dalle': 'groene ruimte op betonnen plaat',
'bande filtrante': 'filterband',
'collecteur en beton poreux': 'poreuze betonnen collector',
}
def get_conn(feature, keys):
for k in keys:
if k in feature.fields:
val = feature.get(k)
if val is not None:
return str(val).strip()
return None
def get_field_if_exists(feature, name):
if name in feature.fields:
return feature.get(name)
return None
def get_rwi_asset_acronym(geometry_type, structure_type, code_name):
struct_type_lower = str(structure_type).strip().lower() if structure_type else None
if code_name:
return str(code_name).strip().upper()
mappings = {
'avaloir': 'AV',
'bordure sequencee': 'BS',
'bouche injection': 'BI',
'clapet anti-retour': 'CA',
'empierrement sedimentation': 'ES',
'kerb cell': 'KC',
'puits infiltration': 'PI',
'surverse': 'SU',
'vanne isolation': 'VI',
'vanne regulation': 'VR',
'gargouille de trottoir': 'GT',
'filet eau': 'FE',
'avaloir de rail': 'AR',
'bordure arasee': 'BA',
'caniveau infiltrant': 'CI',
'drain': 'D',
'trop plein': 'TP',
'reseau egout separatif': 'RES',
'bassin sec/infiltration': 'OED',
'bassin en eau': 'OED',
'fosse': 'EVC',
'jardin de pluie': 'EVC',
'noue': 'EVC',
'massif drainant': 'OED',
'tranchee drainante': 'OED',
'toiture stockante': 'OSI',
'toiture verte': 'OSI',
'bassin orage': 'OED',
'SAUL-vide': 'OED',
'SAUL-terre': 'TA',
'citerne': 'OED',
'espace vert sur dalle': 'EVC',
'bande filtrante': 'EVC',
'collecteur en beton poreux': 'OED',
'chambre de visite': 'CV',
}
if struct_type_lower in mappings:
return mappings[struct_type_lower]
if geometry_type:
return geometry_type.upper()
return 'UNK'
class Command(BaseCommand):
help = "Imports RWI (Rain Water Integration) assets from the GeoPackage database."
def add_arguments(self, parser):
parser.add_argument(
"gpkg_path",
nargs="?",
type=str,
help="Path to the GeoPackage file. Defaults to external_files/giep/inventaire_giep.gpkg if not provided.",
)
parser.add_argument(
"--dry-run",
action="store_true",
default=False,
help="Simulates the import process without writing changes to the database.",
)
def handle(self, *args, **options):
dry_run = options["dry_run"]
gpkg_path = options.get("gpkg_path")
if not gpkg_path:
gpkg_path = os.path.abspath(os.path.join(settings.BASE_DIR, "../external_files/giep/inventaire_giep.gpkg"))
if not os.path.exists(gpkg_path):
self.stderr.write(self.style.ERROR(f"Error: GeoPackage file not found at: {gpkg_path}"))
return
self.stdout.write(f"→ Opening GeoPackage file at: {gpkg_path}")
ds = DataSource(gpkg_path)
if dry_run:
self.stdout.write(self.style.WARNING("⚠ Dry-run mode active – no database changes will be saved."))
try:
with transaction.atomic():
layer_mappings = [
('point', 'point'),
('ligne', 'line'),
('polygone', 'polygon'),
]
total_created = 0
total_updated = 0
for layer_name, geometry_type in layer_mappings:
layer = ds[layer_name]
self.stdout.write(f"→ Importing layer: {layer_name} ({len(layer)} features)...")
created_count = 0
updated_count = 0
for feature in layer:
# Defensive geometry access
try:
if not feature.geom:
continue
ogr_geom = feature.geom.clone()
except Exception as ge:
self.stdout.write(self.style.WARNING(f" [Warning] Skipping feature {feature.fid} due to geometry error: {ge}"))
continue
# Force geometry to 2D
ogr_geom.set_3d(False)
# EPSG 31370 (Lambert 72) -> EPSG 3812 (Lambert 2008)
geom = GEOSGeometry(ogr_geom.wkb, srid=31370)
if geom.empty:
continue
geom.transform(3812)
# Coordinate transformation to EPSG 4326 (WGS 84) to extract lon/lat
geom_wgs84 = geom.clone()
geom_wgs84.transform(4326)
if geom_wgs84.empty:
continue
centroid = geom_wgs84.centroid
lon = centroid.x
lat = centroid.y
type_ouvrage = get_field_if_exists(feature, 'type_ouvra')
code_name = get_field_if_exists(feature, 'nom_code')
# Format standard RWI codes with acronym
acronym = get_rwi_asset_acronym(geometry_type, type_ouvrage, code_name)
code = f"RWI_{acronym}_{feature.fid:03d}"
name_fr = str(type_ouvrage).capitalize() if type_ouvrage else code
name_nl = TYPE_OUVRAGE_NL_MAP.get(type_ouvrage, "").capitalize() or name_fr
# Connection strings
sewer_conn = get_conn(feature, ['connexion_egout', 'connexion egout', 'Conexion egout'])
separate_conn = get_conn(feature, ['connexion_r.separatif', 'connexion r.separatif', 'connexion_r_separatif'])
hydro_conn = get_conn(feature, ['connexion_r.hydro', 'connexion r.hydro', 'connexion_r_hydro'])
# Filter mapping
has_filter = None
if 'filtre' in feature.fields:
filtre_val = feature.get('filtre')
if filtre_val is not None:
has_filter = True if str(filtre_val).lower().strip() in ('oui', '1', 'true') else False
# Commissioning dates parsing
comm_date_str = None
for k in ['date_mise_en_service', 'mise_serv', 'mise_servi']:
if k in feature.fields:
val = feature.get(k)
if val:
comm_date_str = str(val).strip()
break
commissioning_date = None
if comm_date_str:
commissioning_date = parse_date(comm_date_str)
# Dimensional metrics
surface_area = get_field_if_exists(feature, 'surf_m2')
length = get_field_if_exists(feature, 'length_m')
# Fetch existing or instantiate a new asset using external_id and geometry_type
asset = NatureRWIAsset.objects.filter(external_id=feature.fid, geometry_type=geometry_type).first()
is_new = asset is None
if is_new:
asset = NatureRWIAsset(code=code)
else:
# Update code to the new format if it changed
asset.code = code
# Populate attributes
asset.name_fr = name_fr
asset.name_nl = name_nl
asset.geom = geom
asset.lon = lon
asset.lat = lat
asset.geojson = geom.json
asset.geometry_type = geometry_type
asset.external_id = feature.fid
asset.code_name = get_field_if_exists(feature, 'nom_code')
asset.structure_type = type_ouvrage
asset.project_manager = get_field_if_exists(feature, 'chef_proj')
asset.construction_status = str(get_field_if_exists(feature, 'travaux')).strip() if get_field_if_exists(feature, 'travaux') else None
asset.commissioning_date = commissioning_date
asset.condition = get_field_if_exists(feature, 'etat')
asset.administrator = get_field_if_exists(feature, 'admin')
asset.monitoring = get_field_if_exists(feature, 'suivi')
asset.comment = get_field_if_exists(feature, 'comment') or get_field_if_exists(feature, 'commentaire')
asset.has_filter = has_filter
asset.sewer_connection = sewer_conn
asset.separate_network_connection = separate_conn
asset.hydro_network_connection = hydro_conn
asset.surface_area = surface_area
asset.length = length
asset.coating_category = get_field_if_exists(feature, 'cat_rev')
asset.coating_type = get_field_if_exists(feature, 'type_revetement')
asset.status = 'active'
asset.save()
if is_new:
created_count += 1
else:
updated_count += 1
self.stdout.write(self.style.SUCCESS(f"✓ Layer '{layer_name}': {created_count} created, {updated_count} updated."))
total_created += created_count
total_updated += updated_count
if dry_run:
# Roll back changes so database remains unmodified
transaction.set_rollback(True)
self.stdout.write(self.style.WARNING("✓ Dry-run completed. All changes rolled back successfully."))
else:
self.stdout.write(self.style.SUCCESS(
f"✓ Import completed! Total: {total_created} assets created, {total_updated} assets updated in the database."
))
except Exception as e:
self.stderr.write(self.style.ERROR(f"Error during import: {e}"))