Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
SimStadt
Germany Open-Data CityGML
Commits
c4a4fc12
Commit
c4a4fc12
authored
Oct 30, 2025
by
Eric Duminil
Browse files
Dataclass from Claude
parent
45363a95
Changes
1
Show whitespace changes
Inline
Side-by-side
citygml.py
0 → 100644
View file @
c4a4fc12
#!/usr/bin/env python3
"""
CityGML dataclass for German federal states building data.
"""
import
hashlib
import
os
from
abc
import
ABC
,
abstractmethod
from
dataclasses
import
dataclass
from
datetime
import
datetime
from
pathlib
import
Path
from
typing
import
Callable
from
urllib.error
import
URLError
from
urllib.request
import
urlopen
def
download_file
(
url
:
str
,
dest_path
:
Path
,
chunk_size
:
int
=
8192
)
->
bool
:
"""Download file from URL to destination path."""
try
:
with
urlopen
(
url
,
timeout
=
30
)
as
response
:
dest_path
.
parent
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
with
open
(
dest_path
,
"wb"
)
as
f
:
while
True
:
chunk
=
response
.
read
(
chunk_size
)
if
not
chunk
:
break
f
.
write
(
chunk
)
return
True
except
(
URLError
,
OSError
)
as
e
:
print
(
f
" Error downloading from
{
url
}
:
{
e
}
"
)
return
False
@
dataclass
class
CityGML
(
ABC
):
"""
Represents a CityGML file with metadata and download capabilities.
All properties must be defined - no optionals.
Subclasses or instances must implement the verify method.
"""
url
:
str
path
:
Path
coordinate_reference_system
:
str
source
:
str
bundesland
:
str
@
property
def
filename
(
self
)
->
str
:
"""Extract filename from URL."""
return
self
.
url
.
split
(
"/"
)[
-
1
]
def
size
(
self
)
->
int
:
"""Calculate file size if path exists, otherwise return 0."""
if
self
.
path
.
exists
():
return
os
.
path
.
getsize
(
self
.
path
)
return
0
def
sha256
(
self
)
->
str
:
"""Calculate SHA-256 hash if path exists, otherwise return empty string."""
if
not
self
.
path
.
exists
():
return
""
sha256_hash
=
hashlib
.
sha256
()
with
open
(
self
.
path
,
"rb"
)
as
f
:
for
chunk
in
iter
(
lambda
:
f
.
read
(
8192
),
b
""
):
sha256_hash
.
update
(
chunk
)
return
sha256_hash
.
hexdigest
()
def
date
(
self
)
->
datetime
:
"""Get file modification datetime if path exists, otherwise return epoch."""
if
self
.
path
.
exists
():
return
datetime
.
fromtimestamp
(
os
.
path
.
getmtime
(
self
.
path
))
return
datetime
.
fromtimestamp
(
0
)
@
abstractmethod
def
verify
(
self
,
file_path
:
Path
)
->
bool
:
"""
Verify that the downloaded file is valid.
Args:
file_path: Path to the file to verify
Returns:
True if file is valid, False otherwise
"""
pass
def
is_up_to_date
(
self
)
->
bool
:
"""Check if file exists and is up to date."""
if
not
self
.
path
.
exists
():
return
False
if
self
.
size
()
==
0
:
return
False
return
self
.
verify
(
self
.
path
)
def
download
(
self
,
tmp_dir
:
Path
,
max_retries
:
int
=
3
)
->
bool
:
"""
Download and verify the file.
Args:
tmp_dir: Temporary directory for downloads
max_retries: Maximum number of retry attempts
Returns:
True if download and verification successful, False otherwise
"""
# Check if already up to date
if
self
.
is_up_to_date
():
print
(
f
"✓
{
self
.
filename
}
already downloaded and verified"
)
return
True
# Remove old file if it exists but is invalid
if
self
.
path
.
exists
():
print
(
f
" Existing file is invalid, will re-download"
)
self
.
path
.
unlink
()
tmp_path
=
tmp_dir
/
self
.
filename
for
attempt
in
range
(
max_retries
):
print
(
f
"Downloading
{
self
.
filename
}
(attempt
{
attempt
+
1
}
/
{
max_retries
}
)"
)
print
(
f
" Trying
{
self
.
url
}
"
)
# Try to download
if
not
download_file
(
self
.
url
,
tmp_path
):
print
(
f
" Failed to download"
)
if
tmp_path
.
exists
():
tmp_path
.
unlink
()
continue
# Verify the downloaded file
print
(
f
" Verifying
{
self
.
filename
}
"
)
if
self
.
verify
(
tmp_path
):
# Move to final location
self
.
path
.
parent
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
tmp_path
.
rename
(
self
.
path
)
print
(
f
"✓
{
self
.
filename
}
downloaded and verified successfully"
)
return
True
else
:
print
(
f
" Verification failed"
)
if
tmp_path
.
exists
():
tmp_path
.
unlink
()
print
(
f
"✗ Failed to download
{
self
.
filename
}
after
{
max_retries
}
attempts"
)
return
False
@
dataclass
class
CityGMLWithHash
(
CityGML
):
"""CityGML file verified by SHA-256 hash."""
expected_sha256
:
str
=
""
def
verify
(
self
,
file_path
:
Path
)
->
bool
:
"""Verify file using SHA-256 hash."""
# Calculate hash for the file to verify
sha256_hash
=
hashlib
.
sha256
()
with
open
(
file_path
,
"rb"
)
as
f
:
for
chunk
in
iter
(
lambda
:
f
.
read
(
8192
),
b
""
):
sha256_hash
.
update
(
chunk
)
actual_hash
=
sha256_hash
.
hexdigest
()
if
actual_hash
!=
self
.
expected_sha256
:
print
(
f
" Hash mismatch: expected
{
self
.
expected_sha256
}
, got
{
actual_hash
}
"
)
return
False
return
True
@
dataclass
class
CityGMLWithSize
(
CityGML
):
"""CityGML file verified by file size."""
expected_size
:
int
=
0
def
verify
(
self
,
file_path
:
Path
)
->
bool
:
"""Verify file using size."""
actual_size
=
os
.
path
.
getsize
(
file_path
)
if
actual_size
!=
self
.
expected_size
:
print
(
f
" Size mismatch: expected
{
self
.
expected_size
}
, got
{
actual_size
}
"
)
return
False
return
True
@
dataclass
class
CityGMLWithDate
(
CityGML
):
"""CityGML file verified by modification date (file must be newer than source)."""
source_date
:
datetime
=
datetime
.
fromtimestamp
(
0
)
def
verify
(
self
,
file_path
:
Path
)
->
bool
:
"""Verify file is newer than the source date and non-empty."""
file_mtime
=
datetime
.
fromtimestamp
(
os
.
path
.
getmtime
(
file_path
))
if
file_mtime
<=
self
.
source_date
:
print
(
f
" File too old:
{
file_mtime
}
<=
{
self
.
source_date
}
"
)
return
False
return
True
@
dataclass
class
CityGMLWithCustomVerify
(
CityGML
):
"""
CityGML file with custom verification function.
The verify_func should be set after instantiation:
obj = CityGMLWithCustomVerify(...)
obj.verify_func = lambda path: os.path.getsize(path) > 1000
"""
verify_func
:
Callable
[[
Path
],
bool
]
=
lambda
path
:
True
def
verify
(
self
,
file_path
:
Path
)
->
bool
:
"""Verify file using custom function."""
return
self
.
verify_func
(
file_path
)
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