Skip to content

Commit 0f69b2e

Browse files
Merge pull request #158 from 3DBAG/validation-resources
Validation resources
2 parents b582636 + 1c0d3d0 commit 0f69b2e

File tree

11 files changed

+152
-29
lines changed

11 files changed

+152
-29
lines changed

docker/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,4 @@ EXE_PATH_LAS2LAS="/opt/3dbag-pipeline/tools/bin/las2las64"
5252
EXE_PATH_LASINDEX="/opt/3dbag-pipeline/tools/bin/lasindex64"
5353
EXE_PATH_VAL3DITY="/opt/3dbag-pipeline/tools/bin/val3dity"
5454
EXE_PATH_CJVAL="/opt/3dbag-pipeline/tools/bin/cjval"
55+
EXE_PATH_CJIO="/root/.local/bin/cjio"

docker/pipeline/bag3d-core.dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM 3dgi/3dbag-pipeline-tools:2024.12.14 AS develop
1+
FROM 3dgi/3dbag-pipeline-tools:2025.03.14 AS develop
22
ARG VERSION=develop
33
ARG BAG3D_PIPELINE_LOCATION=/opt/3dbag-pipeline
44

docker/pipeline/bag3d-floors-estimation.dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM 3dgi/3dbag-pipeline-tools:2024.12.14 AS develop
1+
FROM 3dgi/3dbag-pipeline-tools:2025.03.14 AS develop
22
ARG VERSION=develop
33
ARG BAG3D_PIPELINE_LOCATION=/opt/3dbag-pipeline
44

docker/pipeline/bag3d-party-walls.dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM 3dgi/3dbag-pipeline-tools:2024.12.14 AS develop
1+
FROM 3dgi/3dbag-pipeline-tools:2025.03.14 AS develop
22
ARG VERSION=develop
33
ARG BAG3D_PIPELINE_LOCATION=/opt/3dbag-pipeline
44

makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ include .env
8888
download:
8989
rm -rf $(BAG3D_TEST_DATA)
9090
mkdir -p $(BAG3D_TEST_DATA)
91-
cd $(BAG3D_TEST_DATA) ; curl -O https://data.3dbag.nl/testdata/pipeline/test_data_v8.zip ; unzip -q test_data_v8.zip ; rm test_data_v8.zip
91+
cd $(BAG3D_TEST_DATA) ; curl -O https://data.3dbag.nl/testdata/pipeline/test_data_v9.zip ; unzip -q test_data_v9.zip ; rm test_data_v9.zip
9292

9393

9494
install_uv:

packages/common/src/bag3d/common/resources/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
TylerResource,
99
RooferResource,
1010
GeoflowResource,
11+
ValidationResource,
1112
)
1213
from bag3d.common.resources.files import FileStoreResource
1314
from bag3d.common.resources.database import DatabaseResource
@@ -74,6 +75,12 @@
7475
flowchart=os.getenv("FLOWCHART_PATH_RECONSTRUCT"),
7576
)
7677

78+
validation = ValidationResource(
79+
exe_val3dity=os.getenv("EXE_PATH_VAL3DITY"),
80+
exe_cjval=os.getenv("EXE_PATH_CJVAL"),
81+
exe_cjio=os.getenv("EXE_PATH_CJIO"),
82+
)
83+
7784

7885
resource_defs = {
7986
"gdal": gdal,
@@ -84,6 +91,7 @@
8491
"lastools": lastools,
8592
"tyler": tyler,
8693
"geoflow": geoflow,
94+
"validation": validation,
8795
"roofer": roofer,
8896
"version": version,
8997
}
@@ -98,6 +106,7 @@
98106
# "lastools": lastools,
99107
# "tyler": tyler,
100108
# "geoflow": geoflow,
109+
# "validation": validation,
101110
# "roofer": roofer,
102111
# "version": version,
103112
# }
@@ -111,6 +120,7 @@
111120
# "lastools": lastools,
112121
# "tyler": tyler,
113122
# "geoflow": geoflow,
123+
# "validation": validation,
114124
# "roofer": roofer,
115125
# "version": version,
116126
# }
@@ -124,6 +134,7 @@
124134
# "lastools": LASToolsResource(),
125135
# "tyler": TylerResource(),
126136
# "geoflow": GeoflowResource(),
137+
# "validation": ValidationResource(),
127138
# "roofer": RooferResource(),
128139
# "version": VersionResource(),
129140
# }

packages/common/src/bag3d/common/resources/executables.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,44 @@ def app(self) -> AppImage:
426426
return AppImage(exes=self.exes, with_docker=self.with_docker)
427427

428428

429+
class ValidationResource(ConfigurableResource):
430+
"""
431+
A ValidationResource can be configured by providing the paths to
432+
the val3dity, cjval and cjio executables on the local system.
433+
434+
For the local exes you can use:
435+
436+
validation_resource = ValidationResource(exe_val3dity=os.getenv("EXE_PATH_VAL3DITY"),
437+
exe_cjval=os.getenv("EXE_PATH_CJVAL"),
438+
exe_cjio=os.getenv("EXE_PATH_CJIO"))
439+
440+
After the resource has been instantiated, val3dity (AppImage) can
441+
be acquired with the `app` property:
442+
443+
validation = validation_resource.app
444+
"""
445+
446+
exe_val3dity: Optional[str] = None
447+
exe_cjval: Optional[str] = None
448+
exe_cjio: Optional[str] = None
449+
450+
@property
451+
def exes(self) -> Dict[str, str]:
452+
return {
453+
"val3dity": self.exe_val3dity,
454+
"cjval": self.exe_cjval,
455+
"cjio": self.exe_cjio,
456+
}
457+
458+
@property
459+
def with_docker(self) -> bool:
460+
return False
461+
462+
@property
463+
def app(self) -> AppImage:
464+
return AppImage(exes=self.exes, with_docker=self.with_docker)
465+
466+
429467
class RooferResource(ConfigurableResource):
430468
"""
431469
A RooferResource can be configured by providing the paths to

packages/core/src/bag3d/core/assets/export/validate.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class CityJSONFileResults:
1818
1919
Attributes:
2020
zip_ok (bool): Whether the file is successfully compressed.
21+
file_ok (bool): Whether the CityJSON file itself is valid.
2122
nr_building (int): Number of building features.
2223
nr_buildingpart (int): Number of building part features.
2324
nr_invalid_building (int): Number of invalid building features. If any of the
@@ -45,6 +46,7 @@ class CityJSONFileResults:
4546
"""
4647

4748
zip_ok: bool = None
49+
file_ok: bool = None
4850
nr_building: int = None
4951
nr_buildingpart: int = None
5052
nr_invalid_building: int = None
@@ -73,6 +75,7 @@ class OBJFileResults:
7375
7476
Attributes:
7577
zip_ok (bool): Whether the file is successfully compressed.
78+
file_ok (bool): Whether the OBJ file itself is valid.
7679
nr_building (int): Number of building features.
7780
nr_buildingpart (int): Number of building part features.
7881
nr_invalid_building (int): Number of invalid building features. If any of the
@@ -88,6 +91,7 @@ class OBJFileResults:
8891
"""
8992

9093
zip_ok: bool = None
94+
file_ok: bool = None
9195
nr_building: int = None
9296
nr_buildingpart: int = None
9397
nr_invalid_building: int = None
@@ -162,6 +166,7 @@ def asdict(self) -> dict:
162166

163167

164168
def cityjson(
169+
validation: AppImage,
165170
dirpath: Path,
166171
file_id: str,
167172
planarity_n_tol: float,
@@ -212,14 +217,14 @@ def cityjson(
212217
try:
213218
cmd = " ".join(
214219
[
215-
"/home/bdukai/software/3dbag-pipeline/venvs/venv_core/bin/cjio",
220+
"{exe}",
216221
str(inputfile),
217222
"info",
218223
"--long",
219224
]
220225
)
221-
output, returncode = execute_shell_command_silent(
222-
shell_command=cmd, cwd=str(dirpath)
226+
output, returncode = validation.execture(
227+
"cjio", command=cmd, local_path=str(dirpath)
223228
)
224229
try:
225230
results.nr_building = int(
@@ -254,7 +259,7 @@ def cityjson(
254259
try:
255260
cmd = " ".join(
256261
[
257-
"/opt/bin/val3dity",
262+
"{exe}",
258263
"--planarity_n_tol",
259264
str(planarity_n_tol),
260265
"--planarity_d2p_tol",
@@ -264,7 +269,13 @@ def cityjson(
264269
str(inputfile),
265270
]
266271
)
267-
execute_shell_command_silent(shell_command=cmd, cwd=str(dirpath))
272+
273+
returncode, output = validation.execute(
274+
"val3dity", command=cmd, local_path=str(dirpath)
275+
)
276+
results.file_ok = (
277+
False if returncode != 0 or "error" in output.lower() else True
278+
)
268279
with reportfile.open("r") as fo:
269280
report = json.load(fo)
270281
nr_invalid_building = 0
@@ -317,7 +328,7 @@ def cityjson(
317328
results.nr_mismatch_errors_lod13 = nr_mismatch_errors_lod13
318329
results.nr_mismatch_errors_lod22 = nr_mismatch_errors_lod22
319330
reportfile.unlink()
320-
logfile.unlink()
331+
logfile.unlink(missing_ok=True)
321332
except Exception:
322333
reportfile.unlink(missing_ok=True)
323334
logfile.unlink(missing_ok=True)
@@ -326,9 +337,9 @@ def cityjson(
326337

327338
# cjval
328339
try:
329-
cmd = " ".join(["/opt/bin/cjval", str(inputfile)])
330-
output, returncode = execute_shell_command_silent(
331-
shell_command=cmd, cwd=str(dirpath)
340+
cmd = " ".join(["{exe}", str(inputfile)])
341+
returncode, output = validation.execute(
342+
"cjval", command=cmd, local_path=str(dirpath)
332343
)
333344
pos = output.find("SUMMARY")
334345
summary = output[pos:]
@@ -344,6 +355,7 @@ def cityjson(
344355

345356

346357
def obj(
358+
validation: AppImage,
347359
dirpath: Path,
348360
file_id: str,
349361
planarity_n_tol: float,
@@ -437,7 +449,7 @@ def obj(
437449
try:
438450
cmd = " ".join(
439451
[
440-
"/opt/bin/val3dity",
452+
"{exe}",
441453
"--planarity_n_tol",
442454
str(planarity_n_tol),
443455
"--planarity_d2p_tol",
@@ -447,7 +459,13 @@ def obj(
447459
str(inputfile),
448460
]
449461
)
450-
execute_shell_command_silent(shell_command=cmd, cwd=str(dirpath))
462+
463+
returncode, output = validation.execute(
464+
"val3dity", command=cmd, local_path=str(dirpath)
465+
)
466+
results.file_ok = (
467+
False if returncode != 0 or "error" in output.lower() else True
468+
)
451469
with reportfile.open("r") as fo:
452470
report = json.load(fo)
453471

@@ -487,7 +505,7 @@ def obj(
487505
results.nr_invalid_buildingpart_lod22 = nr_invalid_lod22
488506
results.errors_lod22 = list(errors_lod22)
489507
reportfile.unlink()
490-
logfile.unlink()
508+
logfile.unlink(missing_ok=True)
491509
except Exception:
492510
reportfile.unlink(missing_ok=True)
493511
logfile.unlink(missing_ok=True)
@@ -509,13 +527,6 @@ def gpkg(
509527
url_root: str,
510528
version: str,
511529
) -> GPKGFileResults:
512-
results = {
513-
"gpkg_zip_ok": None,
514-
"gpkg_ok": None,
515-
"gpkg_nr_features": None,
516-
"gpkg_sha256": None,
517-
"gpkg_download": None,
518-
}
519530
results = GPKGFileResults()
520531
inputzipfile = dirpath.joinpath(file_id).with_suffix(".gpkg.gz")
521532
inputfile = dirpath.joinpath(file_id).with_suffix(".gpkg")
@@ -573,9 +584,6 @@ def gpkg(
573584
f"/vsigzip//{inputzipfile}",
574585
]
575586
)
576-
output, returncode = execute_shell_command_silent(
577-
shell_command=cmd, cwd=str(dirpath)
578-
)
579587
returncode, output = gdal.execute(
580588
"ogrinfo", command=cmd, local_path=str(dirpath)
581589
)
@@ -636,7 +644,7 @@ def create_download_link(url_root: str, format: str, file_id: str, version: str)
636644

637645

638646
def check_formats(input) -> TileResults:
639-
gdal, dirpath, tile_id, url_root, version = input
647+
gdal, validation, dirpath, tile_id, url_root, version = input
640648
file_id = tile_id.replace("/", "-")
641649
planarity_n_tol = 20.0
642650
planarity_d2p_tol = 0.001
@@ -649,6 +657,7 @@ def check_formats(input) -> TileResults:
649657
version=version,
650658
)
651659
obj_results = obj(
660+
validation,
652661
dirpath,
653662
file_id,
654663
planarity_n_tol=planarity_n_tol,
@@ -666,7 +675,7 @@ def check_formats(input) -> TileResults:
666675
"metadata": AssetIn(key_prefix="export"),
667676
},
668677
deps=[AssetKey(("export", "compressed_tiles"))],
669-
required_resource_keys={"file_store", "version", "gdal"},
678+
required_resource_keys={"file_store", "version", "gdal", "validation"},
670679
)
671680
def compressed_tiles_validation(
672681
context: OpExecutionContext, export_index: Path, metadata: Path
@@ -700,12 +709,14 @@ def compressed_tiles_validation(
700709
version = metadata_json["identificationInfo"]["citation"]["edition"]
701710
context.log.debug(f"{version=}")
702711
gdal = context.resources.gdal.app
712+
validation = context.resources.validation.app
703713
with export_index.open("r") as fo:
704714
csvreader = csv.reader(fo)
705715
_ = next(csvreader) # header
706716
tileids = [
707717
(
708718
gdal,
719+
validation,
709720
path_export_dir.joinpath("tiles", row[0]),
710721
row[0],
711722
url_root,

packages/core/tests/conftest.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from bag3d.common.resources.database import DatabaseResource
66
from bag3d.common.resources.executables import (
77
GDALResource,
8+
ValidationResource,
89
)
910

1011
from bag3d.common.resources.files import FileStoreResource
@@ -32,6 +33,18 @@ def gdal():
3233
)
3334

3435

36+
@pytest.fixture(scope="session")
37+
def validation():
38+
exe_val3dity = os.getenv("EXE_PATH_VAL3DITY")
39+
exe_cjval = os.getenv("EXE_PATH_CJVAL")
40+
exe_cjio = os.getenv("EXE_PATH_CJIO")
41+
yield ValidationResource(
42+
exe_val3dity=exe_val3dity,
43+
exe_cjval=exe_cjval,
44+
exe_cjio=exe_cjio,
45+
)
46+
47+
3548
@pytest.fixture(scope="function")
3649
def wkt_testarea():
3750
"""A small test area in the oldtown of Utrecht, incl. the Oudegracht."""
@@ -52,7 +65,7 @@ def file_store(tmp_path):
5265

5366

5467
@pytest.fixture
55-
def context(database, wkt_testarea, file_store, gdal):
68+
def context(database, wkt_testarea, file_store, gdal, validation):
5669
yield build_op_context(
5770
partition_key="01cz1",
5871
op_config={
@@ -64,6 +77,7 @@ def context(database, wkt_testarea, file_store, gdal):
6477
},
6578
resources={
6679
"gdal": gdal,
80+
"validation": validation,
6781
"db_connection": database,
6882
"file_store": file_store,
6983
"version": "test_version",

0 commit comments

Comments
 (0)