Commit af8f59dd authored by Eric Duminil's avatar Eric Duminil
Browse files

Claude refactor

parent 608f73c0
Showing with 127 additions and 66 deletions
+127 -66
......@@ -4,16 +4,23 @@ LoD2 CityGML tiles are available for whole Baden-Württemberg, from LGL.
https://opengeodata.lgl-bw.de/#/(sidenav:product/12)
This script downloads the requires tiles for given regions
(as WKT strings, Zipcode or Zipcodes, in *REGIONS* variable), and extracts the region.
This script downloads the required tiles for given regions
(as WKT strings, Zipcode or Zipcodes), and extracts the region.
Usage:
python download_files_from_LGL_BW.py StuttgartCenter "POLYGON((9.175287 48.780916, 9.185501 48.777522, 9.181467 48.773704, 9.174429 48.768472, 9.168807 48.773902, 9.175287 48.780916))"
python download_files_from_LGL_BW.py Freiburg "79098,79102"
python download_files_from_LGL_BW.py MyRegion "70567" --download-only
python download_files_from_LGL_BW.py CustomPath "POLYGON(...)" --simstadt-folder "/path/to/SimStadt"
Required:
* Python
* pyproj project (https://pypi.org/project/pyproj/)
* SimStadt installed on the Desktop (for RegionChooser)
* SimStadt installed on the Desktop (for RegionChooser) if extracting regions
Eric Duminil, 2025
"""
import argparse
from pathlib import Path
from math import floor
import subprocess
......@@ -21,6 +28,8 @@ import re
import urllib.request
import time
import zipfile
import logging
import sys
from pyproj import CRS
from pyproj import Transformer
......@@ -29,29 +38,23 @@ from shapely import wkt
from shapely.ops import transform
from shapely.geometry import Point
# TODO: Write tests
# TODO: Use logging
from get_coordinates_by_zipcode import get_coordinates_by_zipcode
COORDINATES_REGEX = re.compile(r"(\-?\d+\.\d*) (\-?\d+\.\d*)")
###### User input ##########
# Values can be either a WKT POLYGON or MULTIPOLYGON, a Zipcode, or Zipcodes separated by a comma.
REGIONS = {
"StuttgartCenter": "POLYGON((9.175287 48.780916, 9.185501 48.777522, 9.181467 48.773704, 9.174429 48.768472, 9.168807 48.773902, 9.175287 48.780916))",
# "Freiburg": "79098,79102",
# "AnotherRegion": "Another WKT Polygon...",
# "YetAnotherRegion": "Another ZIP code",
}
# Should RegionChooser extract the regions from multiple CityGMLs?
EXTRACT_REGIONS = True
############################
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
COORDINATES_REGEX = re.compile(r"(\-?\d+\.\d*) (\-?\d+\.\d*)")
CITYGML_SERVER = "https://opengeodata.lgl-bw.de/data/lod2"
RASTER = 2 # [km]
KILOMETER = 1000 # [m]
KILOMETER = 1000 # [m]
BUNDESLAND = 'bw'
# UTM32N, used in BW. https://epsg.io/32632
......@@ -65,16 +68,15 @@ SCRIPT_DIR = Path(__file__).parent
WAIT_BETWEEN_DOWNLOADS = 5 # [s] Be nice to LGL Server.
GML_GLOB = "LoD2_*/LoD2_*.gml"
if EXTRACT_REGIONS:
def find_simstadt_folder():
"""Find SimStadt installation on desktop"""
try:
SIMSTADT_FOLDER = next(x for x in Path.home().glob('Desktop/SimStadt*_0.*/') if x.is_dir())
print(f"RegionChooser has been found in {SIMSTADT_FOLDER}")
simstadt_folder = next(x for x in Path.home().glob('Desktop/SimStadt*_0.*/') if x.is_dir())
logger.info(f"RegionChooser has been found in {simstadt_folder}")
return simstadt_folder
except StopIteration:
exit("No SimStadt installation found!"
"\nPlease copy a SimStadt installation to the desktop,"
"\nset EXTRACT_REGIONS to False,"
"\nor set SIMSTADT_FOLDER manually: SIMSTADT_FOLDER = Path('/path/to/SimStadt')"
)
return None
def coordinates_to_grid(longitude: float, latitude: float) -> tuple[int, int]:
......@@ -87,12 +89,12 @@ def coordinates_to_grid(longitude: float, latitude: float) -> tuple[int, int]:
return (x + 1, y)
def wkt_polygon_to_grid_coords(location_name: str, wkt: str) -> tuple[int, int, int, int]:
def wkt_polygon_to_grid_coords(location_name: str, wkt_str: str) -> tuple[int, int, int, int]:
"""Returns (x, y) of lower-left and bottom-right tiles, containing a given region."""
if 'POLYGON' not in wkt:
if 'POLYGON' not in wkt_str:
raise ValueError(f"wkt for {location_name} should be a WKT POLYGON or MULTIPOLYGON")
coordinates = re.findall(r'\-?\d+\.\d+', wkt)
coordinates = re.findall(r'\-?\d+\.\d+', wkt_str)
lons = [float(lon) for lon in coordinates[::2]]
lats = [float(lat) for lat in coordinates[1::2]]
......@@ -100,8 +102,8 @@ def wkt_polygon_to_grid_coords(location_name: str, wkt: str) -> tuple[int, int,
min_lon, max_lon = min(lons), max(lons)
min_lat, max_lat = min(lats), max(lats)
print("%s (%.3f°N %.3f°E -> %.3f°N %.3f°E)" %
(location_name, max_lat, min_lon, min_lat, max_lon))
logger.info("%s (%.3f°N %.3f°E -> %.3f°N %.3f°E)" %
(location_name, max_lat, min_lon, min_lat, max_lon))
x1, y1 = coordinates_to_grid(min_lon, min_lat)
x2, y2 = coordinates_to_grid(max_lon, max_lat)
......@@ -123,43 +125,44 @@ def download_whole_region(output_dir: Path, wkt_region: str, x1: int, x2: int, y
citygml_url = f"{CITYGML_SERVER}/{citygml_zip}"
local_zip = output_dir / citygml_zip
if local_zip.exists():
print(f" {local_zip.name} already in {output_dir.name}/")
logger.info(f" {local_zip.name} already in {output_dir.name}/")
else:
print(f" Download {citygml_zip} to {output_dir.name}/ ", end='')
logger.info(f" Download {citygml_zip} to {output_dir.name}/ ")
try:
urllib.request.urlretrieve(citygml_url, local_zip)
logger.info("✅ Download successful")
except urllib.error.HTTPError as e:
print(f"❌ {e}")
logger.error(f"❌ {e}")
continue
finally:
time.sleep(WAIT_BETWEEN_DOWNLOADS)
print("✅")
print(f" Extract {citygml_zip} to {output_dir.name}/ ", end='')
print("✅")
print("")
logger.info(f" Extract {citygml_zip} to {output_dir.name}/ ")
with zipfile.ZipFile(local_zip, "r") as zip_ref:
zip_ref.extractall(output_dir)
logger.info("✅ Extraction successful")
def extract_region(output_dir: Path, location_name: str, wkt: str) -> None:
def extract_region(output_dir: Path, location_name: str, wkt_str: str, simstadt_folder: Path) -> None:
"""Uses RegionChooser to extract a given region from all the CityGML files found in subfolder."""
output_file = output_dir / (location_name + '.gml')
if output_file.exists():
print(f" {output_file} already exists. Not extracting.")
logger.info(f" {output_file} already exists. Not extracting.")
return
region_chooser_libs = Path(SIMSTADT_FOLDER).expanduser() / 'lib/*'
region_chooser_libs = simstadt_folder / 'lib/*'
gml_inputs = list(output_dir.glob(GML_GLOB))
if len(gml_inputs) == 0:
print("Error: No CityGML found. At least part of the region should be in Baden-Württemberg!")
logger.error(
"Error: No CityGML found. At least part of the region should be in Baden-Württemberg!")
return
params_path = output_dir / 'params.txt'
wkt_path = output_dir / 'region.wkt'
local_wkt = convert_wkt_to_local(wkt)
local_wkt = convert_wkt_to_local(wkt_str)
print(f" Extracting {output_file}.")
logger.info(f" Extracting {output_file}.")
with open(wkt_path, 'w') as f:
f.write(local_wkt)
......@@ -181,11 +184,11 @@ def extract_region(output_dir: Path, location_name: str, wkt: str) -> None:
capture_output=True,
check=False
)
if (result.stderr):
print(result.stderr)
if result.stderr:
logger.error(result.stderr)
if result.returncode != 0:
raise ValueError(f"RegionChooser failed with code {result.returncode}")
print(" DONE!")
logger.info(" DONE!")
def get_wkt(wkt_or_zipcode: str) -> str:
......@@ -200,30 +203,88 @@ def get_wkt(wkt_or_zipcode: str) -> str:
return get_coordinates_by_zipcode(wkt_or_zipcode.split(','))
def main(regions: dict[str, str]) -> None:
"""Downloads ZIP files, extracts CityGML files, and selects desired region."""
for location_name, wkt_or_zipcode in regions.items():
if ' ' in location_name:
raise ValueError("Location name should not contain spaces: 'Some City' -> 'SomeCity'")
output_dir = SCRIPT_DIR / (location_name + '.proj')
output_dir.mkdir(parents=True, exist_ok=True)
wkt = get_wkt(wkt_or_zipcode)
x1, x2, y1, y2 = wkt_polygon_to_grid_coords(location_name, wkt)
download_whole_region(output_dir, wkt, x1, x2, y1, y2)
if EXTRACT_REGIONS:
extract_region(output_dir, location_name, wkt)
print()
def convert_coordinates(match):
"""Convert WGS84 coordinates to UTM32N"""
longitude, latitude = match.groups()
x, y = TO_LOCAL_CRS.transform(longitude, latitude)
return f"{x} {y}"
def convert_wkt_to_local(wkt):
return COORDINATES_REGEX.sub(convert_coordinates, wkt)
def convert_wkt_to_local(wkt_str):
"""Convert WKT from WGS84 to UTM32N"""
return COORDINATES_REGEX.sub(convert_coordinates, wkt_str)
def parse_arguments():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(
description="Download LoD2 CityGML tiles from LGL Baden-Württemberg and extract specific regions",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python download_files_from_LGL_BW.py StuttgartCenter "POLYGON((9.175287 48.780916, 9.185501 48.777522, 9.181467 48.773704, 9.174429 48.768472, 9.168807 48.773902, 9.175287 48.780916))"
python download_files_from_LGL_BW.py Freiburg "79098,79102"
python download_files_from_LGL_BW.py MyRegion "70567" --download-only
"""
)
parser.add_argument('name', type=str,
help='Name of the region (no spaces allowed). Output files will use this name.')
parser.add_argument('region', type=str,
help='Region specification as WKT POLYGON/MULTIPOLYGON string or zipcode(s) (comma-separated).')
parser.add_argument('--download-only', action='store_true',
help='Only download files without extracting the region (default: False).')
parser.add_argument('--simstadt-folder', type=Path, default=None,
help='Path to SimStadt installation folder. By default, tries to find it on the Desktop.')
return parser.parse_args()
def main():
"""Main function to process arguments and run the download/extraction"""
args = parse_arguments()
location_name = args.name
wkt_or_zipcode = args.region
download_only = args.download_only
simstadt_folder = args.simstadt_folder
# Validate location name
if ' ' in location_name:
raise ValueError("Location name should not contain spaces: 'Some City' -> 'SomeCity'")
# Create output directory
output_dir = SCRIPT_DIR / (location_name + '.proj')
output_dir.mkdir(parents=True, exist_ok=True)
# Get WKT string
wkt_str = get_wkt(wkt_or_zipcode)
# Get grid coordinates
x1, x2, y1, y2 = wkt_polygon_to_grid_coords(location_name, wkt_str)
# Download region
download_whole_region(output_dir, wkt_str, x1, x2, y1, y2)
# Extract region if not download-only
if not download_only:
if not simstadt_folder:
simstadt_folder = find_simstadt_folder()
if not simstadt_folder:
logger.error(
"No SimStadt installation found! Please provide --simstadt-folder or use --download-only.")
return
extract_region(output_dir, location_name, wkt_str, simstadt_folder)
else:
logger.info("Download-only mode: Skipping region extraction.")
logger.info(f"Processing of {location_name} complete!")
if __name__ == '__main__':
main(REGIONS)
main()
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment