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

184 lines
7.3 KiB
Python

import json
import re
import ssl
import urllib.request
from django.contrib.gis.geos import Point
from django.contrib.gis.gdal import CoordTransform, SpatialReference
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q
from django.utils import timezone
from assets.models.its import ITSGeoAsset
def clean_code(code):
if not code:
return ''
c = code.upper().strip()
c = c.replace('RAD_PREV_', '').replace('RAD_', '').replace('CAM_', '').replace('CNT_', '').replace('PMV_', '')
c = c.replace('GATSO', '').replace('GAT', '').strip()
c = re.sub(r'\s+', '', c)
c = re.sub(r'_[0-9]+$', '', c)
c = re.sub(r'-[0-9]+$', '', c)
if c.startswith('Y_'):
c = c[2:]
if c and c[0] in ('S', 'R', 'C'):
c = c[1:]
return c
def get_keys_to_try(code):
cleaned = clean_code(code)
keys = [code, cleaned]
if cleaned and cleaned[0] in ('S', 'R', 'C'):
suffix = cleaned[1:]
keys.extend([suffix, 'S' + suffix, 'R' + suffix, 'C' + suffix])
return list(dict.fromkeys(keys)) # remove duplicates keeping order
def find_coords(code, coords_map):
# Try exact / core variations
for k in get_keys_to_try(code):
if k in coords_map:
return coords_map[k]
# Try trailing '0' normalization
cleaned = clean_code(code)
if cleaned.endswith('0'):
stripped = cleaned[:-1]
for k in get_keys_to_try(stripped):
if k in coords_map:
return coords_map[k]
# If WFS key in coords_map ends with '0'
for wfs_key, coords in coords_map.items():
if wfs_key.endswith('0'):
stripped_wfs = wfs_key[:-1]
# Check if our code core matches stripped WFS key
for k in get_keys_to_try(code):
if k == stripped_wfs:
return coords
return None
class Command(BaseCommand):
help = "Update speed cameras (ITS radars) objects where geometry is null using Brussels Mobility WFS GeoJSON speedcameras and traffic lights data."
def handle(self, *args, **options):
# 1. Fetch speedcameras WFS
url_speed = (
"https://data.mobility.brussels/geoserver/bm_security/wfs?"
"service=wfs&version=1.1.0&request=GetFeature&"
"typeName=bm_security:speedcameras&outputFormat=json&srsName=EPSG:4326"
)
# 2. Fetch traffic lights WFS
url_tl = (
"https://data.mobility.brussels/geoserver/bm_equipment/wfs?"
"service=wfs&version=1.1.0&request=GetFeature&"
"typeName=bm_equipment:traffic_lights&outputFormat=json&srsName=EPSG:4326"
)
context = ssl._create_unverified_context()
# Fetch speed cameras
self.stdout.write(f"Fetching speed cameras from WFS: {url_speed}")
try:
req_speed = urllib.request.Request(url_speed, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req_speed, context=context) as response:
data_speed = json.loads(response.read().decode('utf-8'))
features_speed = data_speed.get('features', [])
self.stdout.write(f"Successfully fetched {len(features_speed)} speed cameras.")
except Exception as e:
self.stdout.write(self.style.ERROR(f"Failed to fetch speed cameras data: {e}"))
return
# Fetch traffic lights
self.stdout.write(f"Fetching traffic lights from WFS: {url_tl}")
try:
req_tl = urllib.request.Request(url_tl, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req_tl, context=context) as response:
data_tl = json.loads(response.read().decode('utf-8'))
features_tl = data_tl.get('features', [])
self.stdout.write(f"Successfully fetched {len(features_tl)} traffic lights.")
except Exception as e:
self.stdout.write(self.style.ERROR(f"Failed to fetch traffic lights data: {e}"))
return
# 3. Map coordinates
coords_map = {}
# First map from speed cameras
for feature in features_speed:
props = feature.get('properties', {})
radar_id = props.get('radar_id') or ''
fr_key = props.get('fr_key') or ''
geom = feature.get('geometry')
if geom and geom.get('type') == 'Point':
coordinates = geom.get('coordinates')
if len(coordinates) == 2:
for val in [radar_id, fr_key]:
cleaned = clean_code(val)
if cleaned:
coords_map[cleaned] = coordinates
# Second, map from traffic lights if not already mapped
for feature in features_tl:
props = feature.get('properties', {})
key = props.get('key') or ''
geom = feature.get('geometry')
if geom and geom.get('type') == 'Point':
coordinates = geom.get('coordinates')
if len(coordinates) == 2:
cleaned = clean_code(key)
if cleaned and cleaned not in coords_map:
coords_map[cleaned] = coordinates
self.stdout.write(f"Mapped coordinates for {len(coords_map)} unique identifiers.")
# 4. Setup coordinate transformation to EPSG:3812 (Belgian Lambert 72)
srs_3812 = SpatialReference(3812)
srs_4326 = SpatialReference(4326)
ct_to_3812 = CoordTransform(srs_4326, srs_3812)
# 5. Find all ITSGeoAsset (Radars) where geom is null
null_radars = ITSGeoAsset.objects.filter(
Q(category__name_fr='Radars') | Q(code__startswith='RAD_'),
geom__isnull=True
)
self.stdout.write(f"Found {null_radars.count()} radars with null geometry in the database.")
updated_radars_count = 0
# We perform updates in a transaction block
with transaction.atomic():
for radar in null_radars:
code = radar.code.strip()
coords = find_coords(code, coords_map)
if coords:
lon, lat = coords
try:
# Create EPSG:4326 point and transform to EPSG:3812
geom_wgs84 = Point(lon, lat, srid=4326)
geom_3812 = geom_wgs84.clone()
geom_3812.transform(ct_to_3812)
geojson_str = json.dumps(
{"type": "Point", "coordinates": [round(lon, 8), round(lat, 8)]}
)
radar.geom = geom_3812
radar.lon = lon
radar.lat = lat
radar.geojson = geojson_str
radar.save()
updated_radars_count += 1
except Exception as e:
self.stdout.write(
self.style.WARNING(f"Error transforming coordinates for radar {code}: {e}")
)
self.stdout.write(
self.style.SUCCESS(f"Updated {updated_radars_count} radars from WFS speedcameras/traffic-lights data.")
)