Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Eric Duminil
Get Baden-Württemberg CityGML Opendata
Commits
b951138d
Commit
b951138d
authored
Sep 12, 2025
by
Eric Duminil
Browse files
Format
parent
ed51d96f
Changes
1
Hide whitespace changes
Inline
Side-by-side
download_files_from_LGL_BW.py
View file @
b951138d
...
...
@@ -16,27 +16,24 @@ Data license: https://www.govdata.de/dl-de/by-2-0
Eric Duminil, 2025
"""
from
pathlib
import
Path
from
math
import
floor
import
subprocess
import
re
import
urllib.request
import
subprocess
import
time
import
urllib.request
import
zipfile
from
math
import
floor
from
pathlib
import
Path
from
pyproj
import
CRS
from
pyproj
import
Transformer
from
pyproj
import
CRS
,
Transformer
from
shapely
import
wkt
from
shapely.ops
import
transform
from
shapely.geometry
import
Point
from
shapely.ops
import
transform
# TODO: Write tests
# TODO: Use logging
from
german_laender_wkt
import
german_laender
from
get_coordinates_by_zipcode
import
get_coordinates_by_zipcode
from
german_laender_wkt
import
german_laender
# TODO: Write tests
# TODO: Use logging
COORDINATES_REGEX
=
re
.
compile
(
r
"(\-?\d+\.\d*) (\-?\d+\.\d*)"
)
...
...
@@ -44,7 +41,7 @@ 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
=
{
"BW"
:
german_laender
[
'Baden-Württemberg'
],
"BW"
:
"POLYGON((9.211240 48.769258, 9.209040 48.770220, 9.208547 48.770708, 9.207206 48.771295, 9.206390 48.771521, 9.204834 48.771528, 9.204405 48.771775, 9.204019 48.772249, 9.203708 48.772907, 9.203981 48.774104, 9.203949 48.774485, 9.204378 48.774994, 9.206169 48.774994, 9.207478 48.775843, 9.208637 48.775899, 9.213776 48.776981, 9.215085 48.775610, 9.216072 48.773736, 9.217350 48.770910, 9.217801 48.769326, 9.214365 48.767755, 9.211240 48.769258))"
# "Freiburg": "79098,79102",
# "AnotherRegion": "Another WKT Polygon...",
# "YetAnotherRegion": "Another ZIP code",
...
...
@@ -56,13 +53,11 @@ EXTRACT_REGIONS = True
CITYGML_SERVER
=
"https://opengeodata.lgl-bw.de/data/lod2"
RASTER
=
2
# [km]
KILOMETER
=
1000
# [m]
BUNDESLAND
=
'
bw
'
KILOMETER
=
1000
# [m]
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
...
...
@@ -72,14 +67,15 @@ GML_GLOB = "LoD2_*/LoD2_*.gml"
if
EXTRACT_REGIONS
:
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
())
print
(
f
"RegionChooser has been found in
{
SIMSTADT_FOLDER
}
"
)
except
StopIteration
:
exit
(
"No SimStadt installation found!"
"
\n
Please copy a SimStadt installation to the desktop,"
"
\n
set EXTRACT_REGIONS to False,"
"
\n
or set SIMSTADT_FOLDER manually: SIMSTADT_FOLDER = Path('/path/to/SimStadt')"
)
exit
(
"No SimStadt installation found!"
"
\n
Please copy a SimStadt installation to the desktop,"
"
\n
set EXTRACT_REGIONS to False,"
"
\n
or set SIMSTADT_FOLDER manually: SIMSTADT_FOLDER = Path('/path/to/SimStadt')"
)
def
coordinates_to_grid
(
longitude
:
float
,
latitude
:
float
)
->
tuple
[
int
,
int
]:
...
...
@@ -94,10 +90,10 @@ def coordinates_to_grid(longitude: float, latitude: float) -> tuple[int, int]:
def
wkt_polygon_to_grid_coords
(
location_name
:
str
,
wkt
:
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
:
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
)
lons
=
[
float
(
lon
)
for
lon
in
coordinates
[::
2
]]
lats
=
[
float
(
lat
)
for
lat
in
coordinates
[
1
::
2
]]
...
...
@@ -105,8 +101,7 @@ 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
))
print
(
"%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
)
...
...
@@ -131,7 +126,7 @@ def download_whole_region(output_dir: Path, wkt_region: str, x1: int, x2: int, y
if
local_zip
.
exists
():
print
(
f
"
{
local_zip
.
name
}
already in
{
output_dir
.
name
}
/"
)
else
:
print
(
f
" Download
{
citygml_zip
}
to
{
output_dir
.
name
}
/ "
,
end
=
''
)
print
(
f
" Download
{
citygml_zip
}
to
{
output_dir
.
name
}
/ "
,
end
=
""
)
try
:
urllib
.
request
.
urlretrieve
(
citygml_url
,
local_zip
)
except
urllib
.
error
.
HTTPError
as
e
:
...
...
@@ -141,7 +136,7 @@ def download_whole_region(output_dir: Path, wkt_region: str, x1: int, x2: int, y
time
.
sleep
(
WAIT_BETWEEN_DOWNLOADS
)
print
(
"✅"
)
print
(
f
" Extract
{
citygml_zip
}
to
{
output_dir
.
name
}
/ "
,
end
=
''
)
print
(
f
" Extract
{
citygml_zip
}
to
{
output_dir
.
name
}
/ "
,
end
=
""
)
print
(
"✅"
)
print
(
""
)
with
zipfile
.
ZipFile
(
local_zip
,
"r"
)
as
zip_ref
:
...
...
@@ -150,44 +145,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
)
->
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
():
print
(
f
"
{
output_file
}
already exists. Not extracting."
)
return
region_chooser_libs
=
Path
(
SIMSTADT_FOLDER
).
expanduser
()
/
'
lib/*
'
region_chooser_libs
=
Path
(
SIMSTADT_FOLDER
).
expanduser
()
/
"
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!"
)
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
)
print
(
f
" Extracting
{
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'
)
as
f
:
with
open
(
params_path
,
"w"
)
as
f
:
f
.
write
(
"--input
\n
"
)
f
.
write
(
','
.
join
(
f
"
{
gml
.
as_posix
()
}
"
for
gml
in
gml_inputs
))
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
)
if
(
result
.
stderr
):
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
.
stderr
:
print
(
result
.
stderr
)
if
result
.
returncode
!=
0
:
raise
ValueError
(
f
"RegionChooser failed with code
{
result
.
returncode
}
"
)
...
...
@@ -201,17 +200,18 @@ 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
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
:
if
" "
in
location_name
:
raise
ValueError
(
"Location name should not contain spaces: 'Some City' -> 'SomeCity'"
)
output_dir
=
SCRIPT_DIR
/
(
location_name
+
'
.proj
'
)
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
)
...
...
@@ -231,5 +231,5 @@ def convert_wkt_to_local(wkt):
return
COORDINATES_REGEX
.
sub
(
convert_coordinates
,
wkt
)
if
__name__
==
'
__main__
'
:
if
__name__
==
"
__main__
"
:
main
(
REGIONS
)
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment