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

Formatting

parent 1a46804f
......@@ -20,29 +20,28 @@ Required:
Eric Duminil, 2025
"""
import argparse
from pathlib import Path
from math import floor
import subprocess
import logging
import re
import urllib.request
import subprocess
import time
import urllib.request
import zipfile
import logging
from pyproj import CRS
from pyproj import Transformer
from math import floor
from pathlib import Path
from pyproj import CRS, Transformer
from shapely import wkt
from shapely.ops import transform
from shapely.geometry import Point
from shapely.ops import transform
from get_coordinates_by_zipcode import get_coordinates_by_zipcode
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(levelname)s - %(message)s',
format="%(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
......@@ -51,12 +50,10 @@ COORDINATES_REGEX = re.compile(r"(\-?\d+\.\d*) (\-?\d+\.\d*)")
CITYGML_SERVER = "https://opengeodata.lgl-bw.de/data/lod2"
RASTER = 2 # [km]
KILOMETER = 1000 # [m]
BUNDESLAND = 'bw'
BUNDESLAND = "bw"
# UTM32N, used in BW. https://epsg.io/32632
TO_LOCAL_CRS = Transformer.from_crs(CRS.from_epsg(4326),
CRS.from_epsg(32632),
always_xy=True)
TO_LOCAL_CRS = Transformer.from_crs(CRS.from_epsg(4326), CRS.from_epsg(32632), always_xy=True)
UTM = 32
......@@ -68,7 +65,7 @@ GML_GLOB = "LoD2_*/LoD2_*.gml"
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())
simstadt_folder = next(x for x in Path.home().glob("Desktop/SimStadt*_0.*/") if x.is_dir())
logger.info("RegionChooser has been found in %s", simstadt_folder)
return simstadt_folder
except StopIteration:
......@@ -87,10 +84,10 @@ def coordinates_to_grid(longitude: float, latitude: float) -> tuple[int, int]:
def wkt_polygon_to_bounding_box(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_str:
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_str)
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]]
......@@ -98,8 +95,7 @@ def wkt_polygon_to_bounding_box(location_name: str, wkt_str: str) -> tuple[int,
min_lon, max_lon = min(lons), max(lons)
min_lat, max_lat = min(lats), max(lats)
logger.info("%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)
......@@ -142,46 +138,48 @@ def download_whole_region(output_dir: Path, wkt_region: str, x1: int, x2: int, y
def extract_region(output_dir: Path, location_name: str, wkt_str: str, simstadt_folder: Path) -> Path | None:
"""Uses RegionChooser to extract a given region from all the CityGML files found in subfolder."""
output_file = output_dir / (location_name + '.gml')
output_file = output_dir / (location_name + ".gml")
if output_file.exists():
logger.info(" %s already exists. Not extracting.", output_file)
return output_file
region_chooser_libs = simstadt_folder / 'lib/*'
region_chooser_libs = simstadt_folder / "lib/*"
gml_inputs = list(output_dir.glob(GML_GLOB))
if len(gml_inputs) == 0:
logger.error(
"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'
params_path = output_dir / "params.txt"
wkt_path = output_dir / "region.wkt"
local_wkt = convert_wkt_to_local(wkt_str)
logger.info(" Extracting %s.", output_file)
with open(wkt_path, 'w') as f:
with open(wkt_path, "w") as f:
f.write(local_wkt)
with open(params_path, 'w', encoding='utf-8') as f:
f.write("--input\n\"")
f.write(','.join(f'{gml.as_posix()}' for gml in gml_inputs))
f.write("\"\n")
with open(params_path, "w", encoding="utf-8") as f:
f.write('--input\n"')
f.write(",".join(f"{gml.as_posix()}" for gml in gml_inputs))
f.write('"\n')
f.write("--output\n")
f.write(f'"{output_file.as_posix()}"\n')
f.write('--local\n')
f.write("--local\n")
f.write("--wkt\n")
f.write(f'"{wkt_path.as_posix()}"\n')
result = subprocess.run(['java', '-classpath', f'{region_chooser_libs}',
'eu.simstadt.regionchooser.RegionChooserCLI',
f'@{params_path}'
],
text=True,
capture_output=True,
check=False
)
result = subprocess.run(
[
"java",
"-classpath",
f"{region_chooser_libs}",
"eu.simstadt.regionchooser.RegionChooserCLI",
f"@{params_path}",
],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
if result.stderr:
logger.error("%s", result.stderr)
......@@ -197,10 +195,10 @@ def get_wkt(wkt_or_zipcode: str) -> str:
"70567"
"70567,70569"
"""
if 'POLYGON' in wkt_or_zipcode:
if "POLYGON" in wkt_or_zipcode:
return wkt_or_zipcode
return get_coordinates_by_zipcode(wkt_or_zipcode.split(','))
return get_coordinates_by_zipcode(wkt_or_zipcode.split(","))
def convert_coordinates(match):
......@@ -225,40 +223,56 @@ Examples:
python download_LoD2_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_LoD2_from_LGL_BW.py Freiburg "79098,79102" --output-folder="/path/to/FreiburgFolder"
python download_LoD2_from_LGL_BW.py Möhringen "70567" --download-only
"""
""",
)
parser.add_argument('name', type=str,
help='Name of the region. Output files will use this name.')
parser.add_argument("name", type=str, help="Name of the region. Output files will use this name.")
parser.add_argument('region', type=str,
help='Desired region as as WKT POLYGON/MULTIPOLYGON string or zipcode(s) (comma-separated).')
parser.add_argument(
"region", type=str, help="Desired region as 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(
"--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.')
parser.add_argument(
"--simstadt-folder",
type=Path,
default=None,
help="Path to SimStadt installation folder. By default, tries to find it on the Desktop.",
)
parser.add_argument('--output-folder', type=Path, default=None,
help='Folder in which the tiles should be downloaded and extracted. By default, use the folder of the current script / name.proj.')
parser.add_argument(
"--output-folder",
type=Path,
default=None,
help="Folder in which the tiles should be downloaded and extracted. By default, use the folder of the current script / name.proj.",
)
return parser.parse_args()
def main(location_name: str, wkt_or_zipcode: str, download_only: bool = False,
simstadt_folder: Path | str | None = None, output_folder: Path | str | None = None) -> Path | None:
def main(
location_name: str,
wkt_or_zipcode: str,
download_only: bool = False,
simstadt_folder: Path | str | None = None,
output_folder: Path | str | None = None,
) -> Path | None:
"""Main function to process arguments and run the download/extraction"""
# Validate location name
if ' ' in location_name:
if " " in location_name:
logger.warning("Location contains spaces, some workflows might fail.")
if output_folder:
output_folder = Path(output_folder)
else:
# Create output directory
output_folder = SCRIPT_DIR / (location_name + '.proj')
output_folder = SCRIPT_DIR / (location_name + ".proj")
output_folder.mkdir(parents=True, exist_ok=True)
......@@ -279,8 +293,7 @@ def main(location_name: str, wkt_or_zipcode: str, download_only: bool = False,
if simstadt_folder:
simstadt_folder = Path(simstadt_folder)
else:
logger.error(
"No SimStadt installation found! Please provide --simstadt-folder or use --download-only.")
logger.error("No SimStadt installation found! Please provide --simstadt-folder or use --download-only.")
return
gml_path = extract_region(output_folder, location_name, wkt_str, simstadt_folder)
......@@ -291,6 +304,6 @@ def main(location_name: str, wkt_or_zipcode: str, download_only: bool = False,
return gml_path
if __name__ == '__main__':
if __name__ == "__main__":
args = parse_arguments()
main(args.name, args.region, args.download_only, args.simstadt_folder, args.output_folder)
......@@ -27,11 +27,12 @@ import argparse
import json
import re
from pathlib import Path
from shapely.geometry import shape
from shapely.ops import unary_union
INPUT_FOLDER = Path('plz')
PLZ_FILENAME: str = 'plz-5stellig.geojson'
INPUT_FOLDER = Path("plz")
PLZ_FILENAME: str = "plz-5stellig.geojson"
PLZ_SHAPE_FILE = INPUT_FOLDER / PLZ_FILENAME
PRECISION: float = 10 # [m]
ONE_DEGREE: float = 40e6 / 360 # [m]
......@@ -42,16 +43,17 @@ CACHED: bool = False
def _download_plz_shapes_if_needed() -> None:
if not PLZ_SHAPE_FILE.exists():
from tqdm import tqdm
import requests
from tqdm import tqdm
print("Downloading %s..." % PLZ_FILENAME)
URL = "https://downloads.suche-postleitzahl.org/v2/public/" + PLZ_FILENAME
response = requests.get(URL, stream=True)
INPUT_FOLDER.mkdir(exist_ok=True)
with open(PLZ_SHAPE_FILE, "wb") as handle:
for data in tqdm(response.iter_content(chunk_size=1024), unit='kB'):
for data in tqdm(response.iter_content(chunk_size=1024), unit="kB"):
handle.write(data)
print(' Done')
print(" Done")
def _get_plz_shapes() -> dict:
......@@ -63,7 +65,7 @@ def _get_plz_shapes() -> dict:
try:
print("Parsing %s..." % PLZ_FILENAME)
with open(PLZ_SHAPE_FILE) as f:
print(' Done')
print(" Done")
PLZ_SHAPES = json.load(f)
CACHED = True
return PLZ_SHAPES
......@@ -77,21 +79,21 @@ def get_coordinates_by_zipcode(plz_patterns: list[str], precision: float = PRECI
geometries = []
for plz_pattern in plz_patterns:
found = False
for plz_geojson in plz_shapes['features']:
if re.match(plz_pattern, plz_geojson['properties']['plz']):
for plz_geojson in plz_shapes["features"]:
if re.match(plz_pattern, plz_geojson["properties"]["plz"]):
found = True
properties = plz_geojson['properties']
properties = plz_geojson["properties"]
print('## %s' % properties['note'])
print('Population : %d' % properties['einwohner'])
print('Area : %.2f km²' % properties['qkm'])
print("## %s" % properties["note"])
print("Population : %d" % properties["einwohner"])
print("Area : %.2f km²" % properties["qkm"])
# NOTE : Geometry can be either a polygon,
# a MultiPolygon : 98694 Ilmenau
# or a polygon with holes : 31860 Emmerthal
print('WKT Polygon : ')
print("WKT Polygon : ")
geometries.append(shape(plz_geojson['geometry']))
geometries.append(shape(plz_geojson["geometry"]))
if not found:
raise AttributeError(f"Sorry, no information could be found for PLZ={plz_pattern}")
......@@ -101,6 +103,7 @@ def get_coordinates_by_zipcode(plz_patterns: list[str], precision: float = PRECI
print(wkt_polygon)
try:
import pyperclip
pyperclip.copy(wkt_polygon)
print("WKT Polygon copied to clipboard.")
except (ModuleNotFoundError, RuntimeError):
......@@ -112,11 +115,9 @@ def get_coordinates_by_zipcode(plz_patterns: list[str], precision: float = PRECI
return wkt_polygon
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Get WKT geometry for desired PLZs')
parser.add_argument('plzs', metavar='PLZ', type=str, nargs='+',
help='desired PLZs')
parser.add_argument('-p', '--precision', default=PRECISION, type=int,
help='precision of returned polygon [m]')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Get WKT geometry for desired PLZs")
parser.add_argument("plzs", metavar="PLZ", type=str, nargs="+", help="desired PLZs")
parser.add_argument("-p", "--precision", default=PRECISION, type=int, help="precision of returned polygon [m]")
args = parser.parse_args()
get_coordinates_by_zipcode(args.plzs, args.precision)
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