233 lines
10 KiB
Python
233 lines
10 KiB
Python
import json
|
|
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.trafficlights import (
|
|
TrafficLightIntersection,
|
|
TrafficLightPole,
|
|
TrafficLightController,
|
|
TrafficLightControllerHardware,
|
|
TrafficLightDetector,
|
|
TrafficLightNetworkHardware,
|
|
TrafficLightVoltageHardware,
|
|
TrafficLightElectricalCabinet,
|
|
TrafficLightElectricalCabinetContent,
|
|
TrafficLightAccessory,
|
|
)
|
|
|
|
def clean_code(code):
|
|
c = code.upper().strip()
|
|
if c.startswith('Y_'):
|
|
c = c[2:]
|
|
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
|
|
# If code ends with '0'
|
|
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 TrafficLightIntersection and equipment objects where geometry is null using Brussels Mobility WFS GeoJSON data."
|
|
|
|
def handle(self, *args, **options):
|
|
# 1. Fetch WFS GeoJSON
|
|
url = (
|
|
"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"
|
|
)
|
|
self.stdout.write(f"Fetching GeoJSON from: {url}")
|
|
|
|
try:
|
|
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
|
context = ssl._create_unverified_context()
|
|
with urllib.request.urlopen(req, context=context) as response:
|
|
data = json.loads(response.read().decode('utf-8'))
|
|
except Exception as e:
|
|
self.stdout.write(self.style.ERROR(f"Failed to fetch GeoJSON data: {e}"))
|
|
return
|
|
|
|
features = data.get('features', [])
|
|
self.stdout.write(f"Successfully fetched {len(features)} features.")
|
|
|
|
# 2. Map coordinates by intersection code (key)
|
|
# Coordinates in GeoJSON are [lon, lat] (EPSG:4326)
|
|
coords_map = {}
|
|
for feature in features:
|
|
props = feature.get('properties', {})
|
|
key = (props.get('key') or '').strip().upper()
|
|
geom = feature.get('geometry')
|
|
if key and geom and geom.get('type') == 'Point':
|
|
coordinates = geom.get('coordinates')
|
|
if len(coordinates) == 2:
|
|
coords_map[key] = coordinates
|
|
|
|
self.stdout.write(f"Mapped coordinates for {len(coords_map)} intersections.")
|
|
|
|
# 3. 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)
|
|
|
|
# 4. Find all TrafficLightIntersection where geom is null
|
|
null_intersections = TrafficLightIntersection.objects.filter(geom__isnull=True)
|
|
self.stdout.write(f"Found {null_intersections.count()} intersections with null geometry in the database.")
|
|
|
|
updated_intersections_count = 0
|
|
now = timezone.now()
|
|
|
|
# We perform updates in a transaction block
|
|
with transaction.atomic():
|
|
for intersection in null_intersections:
|
|
code = intersection.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)]}
|
|
)
|
|
|
|
intersection.geom = geom_3812
|
|
intersection.lon = lon
|
|
intersection.lat = lat
|
|
intersection.geojson = geojson_str
|
|
# updated_at will be set automatically by save() auto_now=True
|
|
intersection.save()
|
|
updated_intersections_count += 1
|
|
except Exception as e:
|
|
self.stdout.write(
|
|
self.style.WARNING(f"Error transforming coordinates for {code}: {e}")
|
|
)
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f"Updated {updated_intersections_count} intersections from WFS data.")
|
|
)
|
|
|
|
# 5. Update child equipment where geometry is null
|
|
self.stdout.write("Updating equipment with null geometry based on their parent intersections...")
|
|
|
|
# Get all intersections that now have a valid geometry
|
|
valid_intersections = TrafficLightIntersection.objects.filter(geom__isnull=False)
|
|
|
|
equipment_stats = {
|
|
"poles": 0,
|
|
"controllers": 0,
|
|
"hardwares": 0,
|
|
"detectors": 0,
|
|
"network_hardwares": 0,
|
|
"voltage_hardwares": 0,
|
|
"cabinets": 0,
|
|
"cabinet_contents": 0,
|
|
"accessories": 0,
|
|
}
|
|
|
|
with transaction.atomic():
|
|
for intersection in valid_intersections:
|
|
# Cache geometry fields
|
|
geom = intersection.geom
|
|
lon = intersection.lon
|
|
lat = intersection.lat
|
|
geojson = intersection.geojson
|
|
|
|
# Update TrafficLightPole
|
|
poles = TrafficLightPole.objects.filter(intersection=intersection, geom__isnull=True)
|
|
if poles.exists():
|
|
p_count = poles.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["poles"] += p_count
|
|
|
|
# Update TrafficLightController
|
|
controllers = TrafficLightController.objects.filter(intersection=intersection, geom__isnull=True)
|
|
if controllers.exists():
|
|
c_count = controllers.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["controllers"] += c_count
|
|
|
|
# Update TrafficLightControllerHardware
|
|
hardwares = TrafficLightControllerHardware.objects.filter(controller__intersection=intersection, geom__isnull=True)
|
|
if hardwares.exists():
|
|
h_count = hardwares.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["hardwares"] += h_count
|
|
|
|
# Update TrafficLightDetector
|
|
detectors = TrafficLightDetector.objects.filter(intersection=intersection, geom__isnull=True)
|
|
if detectors.exists():
|
|
d_count = detectors.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["detectors"] += d_count
|
|
|
|
# Update TrafficLightNetworkHardware
|
|
network_hardwares = TrafficLightNetworkHardware.objects.filter(intersection=intersection, geom__isnull=True)
|
|
if network_hardwares.exists():
|
|
nh_count = network_hardwares.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["network_hardwares"] += nh_count
|
|
|
|
# Update TrafficLightVoltageHardware
|
|
voltage_hardwares = TrafficLightVoltageHardware.objects.filter(intersection=intersection, geom__isnull=True)
|
|
if voltage_hardwares.exists():
|
|
vh_count = voltage_hardwares.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["voltage_hardwares"] += vh_count
|
|
|
|
# Update TrafficLightElectricalCabinet
|
|
cabinets = TrafficLightElectricalCabinet.objects.filter(intersection=intersection, geom__isnull=True)
|
|
if cabinets.exists():
|
|
cab_count = cabinets.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["cabinets"] += cab_count
|
|
|
|
# Update TrafficLightElectricalCabinetContent
|
|
cabinet_contents = TrafficLightElectricalCabinetContent.objects.filter(cabinet__intersection=intersection, geom__isnull=True)
|
|
if cabinet_contents.exists():
|
|
cc_count = cabinet_contents.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["cabinet_contents"] += cc_count
|
|
|
|
# Update TrafficLightAccessory
|
|
accessories = TrafficLightAccessory.objects.filter(
|
|
Q(intersection=intersection) | Q(pole__intersection=intersection),
|
|
geom__isnull=True
|
|
)
|
|
if accessories.exists():
|
|
acc_count = accessories.update(geom=geom, lon=lon, lat=lat, geojson=geojson, updated_at=now)
|
|
equipment_stats["accessories"] += acc_count
|
|
|
|
self.stdout.write(self.style.SUCCESS("Equipment update complete. Summary of updated equipment:"))
|
|
for eq_type, count in equipment_stats.items():
|
|
self.stdout.write(f" - {eq_type}: {count}")
|