Skip to content

Commit 18145df

Browse files
authored
Merge pull request #250 from 3DBAG/249-fix-ahn5-laz-tiles-input-for-roofer
249 fix ahn5 laz tiles input for roofer
2 parents c97e2dc + 6dcbfa4 commit 18145df

File tree

4 files changed

+132
-60
lines changed

4 files changed

+132
-60
lines changed

packages/core/src/bag3d/core/assets/ahn/tile.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from concurrent.futures import ThreadPoolExecutor, as_completed
2+
from pathlib import Path
23

34
from dagster import asset, AssetKey, Output, Field, get_dagster_logger
45

56
from pgutils import PostgresTableIdentifier, inject_parameters
67
from psycopg.sql import SQL
78
from psycopg import Connection
89

10+
from bag3d.common.types import LocalPath
911
from bag3d.common.utils.database import create_schema, load_sql
1012
from bag3d.common.utils.geodata import wkt_from_bbox
1113
from bag3d.core.assets.ahn.core import (
@@ -92,9 +94,9 @@ def regular_grid_200m(context):
9294
deps={AssetKey(["ahn", "metadata_ahn3"])},
9395
required_resource_keys={"file_store", "lastools", "db_connection"},
9496
)
95-
def laz_tiles_ahn3_200m(context, regular_grid_200m, metadata_table_ahn3):
97+
def laz_tiles_ahn3_200m(context, regular_grid_200m, metadata_table_ahn3) -> LocalPath:
9698
"""AHN3 partitioned by a grid of 200m cells on the extent of the AHN PDOK tiles."""
97-
partition_laz_with_grid(
99+
return partition_laz_with_grid(
98100
context,
99101
metadata_table_ahn3,
100102
regular_grid_200m,
@@ -123,9 +125,9 @@ def laz_tiles_ahn3_200m(context, regular_grid_200m, metadata_table_ahn3):
123125
deps={AssetKey(["ahn", "metadata_ahn4"])},
124126
required_resource_keys={"file_store", "lastools", "db_connection"},
125127
)
126-
def laz_tiles_ahn4_200m(context, regular_grid_200m, metadata_table_ahn4):
128+
def laz_tiles_ahn4_200m(context, regular_grid_200m, metadata_table_ahn4) -> LocalPath:
127129
"""AHN4 partitioned by a grid of 200m cells on the extent of the AHN PDOK tiles."""
128-
partition_laz_with_grid(
130+
return partition_laz_with_grid(
129131
context,
130132
metadata_table_ahn4,
131133
regular_grid_200m,
@@ -154,9 +156,9 @@ def laz_tiles_ahn4_200m(context, regular_grid_200m, metadata_table_ahn4):
154156
deps={AssetKey(["ahn", "metadata_ahn5"])},
155157
required_resource_keys={"file_store", "lastools", "db_connection"},
156158
)
157-
def laz_tiles_ahn5_200m(context, regular_grid_200m, metadata_table_ahn5):
159+
def laz_tiles_ahn5_200m(context, regular_grid_200m, metadata_table_ahn5) -> LocalPath:
158160
"""AHN5 partitioned by a grid of 200m cells on the extent of the AHN PDOK tiles."""
159-
partition_laz_with_grid(
161+
return partition_laz_with_grid(
160162
context,
161163
metadata_table_ahn5,
162164
regular_grid_200m,
@@ -175,7 +177,7 @@ def partition_laz_with_grid(
175177
cellsize,
176178
max_workers,
177179
verbose: bool = False,
178-
):
180+
) -> Path:
179181
"""
180182
Partitions LAZ files into tiles based on a regular grid.
181183
Queries the PostgreSQL database to find the grid cells that overlap the AHN tile.
@@ -192,7 +194,7 @@ def partition_laz_with_grid(
192194
verbose: Whether to suppress output from the subprocesses.
193195
194196
Returns:
195-
None
197+
The Path to the directory with the output 200m tiles
196198
"""
197199
conn = context.resources.db_connection.connect
198200
query_params = {
@@ -262,3 +264,4 @@ def partition_laz_with_grid(
262264
logger.warning(f"Tile {tile} raised an exception: {e}")
263265
if len(failed) > 0:
264266
logger.error(f"Failed {len(failed)} tiles. Failed tiles: {failed}")
267+
return out_dir

packages/core/src/bag3d/core/assets/reconstruction/reconstruction.py

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def __init__(self, schema: str, table_tiles: str, wkt: str = None):
5656
),
5757
ins={
5858
"regular_grid_200m": AssetIn(key_prefix="ahn"),
59+
"laz_tiles_ahn3_200m": AssetIn(key_prefix="ahn"),
60+
"laz_tiles_ahn4_200m": AssetIn(key_prefix="ahn"),
61+
"laz_tiles_ahn5_200m": AssetIn(key_prefix="ahn"),
5962
"tiles": AssetIn(key_prefix="input"),
6063
"index": AssetIn(key_prefix="input"),
6164
"reconstruction_input": AssetIn(key_prefix="input"),
@@ -80,25 +83,17 @@ def __init__(self, schema: str, table_tiles: str, wkt: str = None):
8083
is_required=False,
8184
default_value="info",
8285
),
83-
"dir_tiles_200m_ahn3": Field(
84-
str,
85-
description="Directory of the 200m tiles of AHN3. Used if the tiles are stored in a non-standard location.",
86-
is_required=False,
87-
),
88-
"dir_tiles_200m_ahn4": Field(
89-
str,
90-
description="Directory of the 200m tiles of AHN4. Used if the tiles are stored in a non-standard location.",
91-
is_required=False,
92-
),
93-
"dir_tiles_200m_ahn5": Field(
94-
str,
95-
description="Directory of the 200m tiles of AHN5. Used if the tiles are stored in a non-standard location.",
96-
is_required=False,
97-
),
9886
},
9987
)
10088
def reconstructed_building_models_nl(
101-
context, regular_grid_200m, tiles, index, reconstruction_input
89+
context,
90+
regular_grid_200m,
91+
tiles,
92+
index,
93+
reconstruction_input,
94+
laz_tiles_ahn3_200m,
95+
laz_tiles_ahn4_200m,
96+
laz_tiles_ahn5_200m,
10297
):
10398
"""Generate the 3D building models by running the reconstruction sequentially
10499
within one partition.
@@ -110,15 +105,9 @@ def reconstructed_building_models_nl(
110105
reconstruction_input,
111106
regular_grid_200m,
112107
tiles,
113-
dir_tiles_200m_ahn3=context.op_execution_context.op_config.get(
114-
"dir_tiles_200m_ahn3"
115-
),
116-
dir_tiles_200m_ahn4=context.op_execution_context.op_config.get(
117-
"dir_tiles_200m_ahn4"
118-
),
119-
dir_tiles_200m_ahn5=context.op_execution_context.op_config.get(
120-
"dir_tiles_200m_ahn5"
121-
),
108+
dir_tiles_200m_ahn3=laz_tiles_ahn3_200m,
109+
dir_tiles_200m_ahn4=laz_tiles_ahn4_200m,
110+
dir_tiles_200m_ahn5=laz_tiles_ahn5_200m,
122111
)
123112

124113
context.log.info(f"{roofer_toml=}")
@@ -152,6 +141,19 @@ def create_roofer_config(
152141
dir_tiles_200m_ahn4=None,
153142
dir_tiles_200m_ahn5=None,
154143
):
144+
def laz_filepaths_generator(
145+
file_store_dir: Path, resultset: list[tuple], dir_200m_laz=None
146+
):
147+
"""Generator for the full 200m laz file paths that only emits existing paths."""
148+
if dir_200m_laz is not None:
149+
lazdir = Path(dir_200m_laz)
150+
else:
151+
lazdir = ahn_dir(file_store_dir, ahn_version=5).joinpath("tiles_200m")
152+
for tile_id_ahn in resultset:
153+
p = lazdir / f"t_{tile_id_ahn[0]}.laz"
154+
if p.is_file():
155+
yield str(p)
156+
155157
toml_template = """
156158
polygon-source = "{footprint_file}"
157159
id-attribute = "identificatie"
@@ -237,26 +239,30 @@ def create_roofer_config(
237239
"tile_id": tile_id,
238240
},
239241
)
240-
if dir_tiles_200m_ahn3 is not None:
241-
out_dir_ahn3 = Path(dir_tiles_200m_ahn3)
242-
else:
243-
out_dir_ahn3 = ahn_dir(
244-
context.resources.file_store.file_store.data_dir, ahn_version=3
245-
).joinpath("tiles_200m")
246-
laz_files_ahn3 = [
247-
str(out_dir_ahn3 / f"t_{tile_id_ahn[0]}.laz") for tile_id_ahn in res
248-
]
249-
# TODO: probably should take the tiles_200m directory from the asset output
250-
if dir_tiles_200m_ahn4 is not None:
251-
out_dir_ahn4 = Path(dir_tiles_200m_ahn4)
252-
else:
253-
out_dir_ahn4 = ahn_dir(
254-
context.resources.file_store.file_store.data_dir, ahn_version=4
255-
).joinpath("tiles_200m")
256-
# TODO: same with the laz filename pattern
257-
laz_files_ahn4 = [
258-
str(out_dir_ahn4 / f"t_{tile_id_ahn[0]}.laz") for tile_id_ahn in res
259-
]
242+
laz_files_ahn3 = list(
243+
laz_filepaths_generator(
244+
file_store_dir=context.resources.file_store.file_store.data_dir,
245+
resultset=res,
246+
dir_200m_laz=dir_tiles_200m_ahn3,
247+
)
248+
)
249+
250+
laz_files_ahn4 = list(
251+
laz_filepaths_generator(
252+
file_store_dir=context.resources.file_store.file_store.data_dir,
253+
resultset=res,
254+
dir_200m_laz=dir_tiles_200m_ahn4,
255+
)
256+
)
257+
258+
laz_files_ahn5 = list(
259+
laz_filepaths_generator(
260+
file_store_dir=context.resources.file_store.file_store.data_dir,
261+
resultset=res,
262+
dir_200m_laz=dir_tiles_200m_ahn5,
263+
)
264+
)
265+
260266
# Would be neater if we could use -sql in the OGR connection to do this query,
261267
# instead of creating a view.
262268
tile_view = PostgresTableIdentifier(tiles.schema, f"t_{tile_id}")
@@ -284,13 +290,7 @@ def create_roofer_config(
284290
footprint_file=f"PG:{context.resources.db_connection.connect.dsn} tables={tile_view}",
285291
ahn3_files=laz_files_ahn3,
286292
ahn4_files=laz_files_ahn4,
287-
ahn5_files=[
288-
str(
289-
ahn_dir(
290-
context.resources.file_store.file_store.data_dir, ahn_version=5
291-
).joinpath("as_downloaded/LAZ")
292-
)
293-
],
293+
ahn5_files=laz_files_ahn5,
294294
output_path=output_dir,
295295
)
296296
path_toml = output_dir / "roofer.toml"

packages/core/tests/conftest.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,66 @@ def handle_output(self, context, obj): # pragma: no cover
329329
key=AssetKey(["ahn", "regular_grid_200m"]),
330330
io_manager_def=MockIOManager(),
331331
)
332+
333+
334+
@pytest.fixture(scope="session")
335+
def mock_asset_laz_tiles_ahn3_200m(test_data_dir):
336+
class MockIOManager(IOManager):
337+
def load_input(self, context):
338+
return (
339+
test_data_dir
340+
/ "reconstruction_input"
341+
/ "pointcloud"
342+
/ "AHN3"
343+
/ "tiles_200m"
344+
)
345+
346+
def handle_output(self, context, obj): # pragma: no cover
347+
raise NotImplementedError()
348+
349+
return SourceAsset(
350+
key=AssetKey(["ahn", "laz_tiles_ahn3_200m"]),
351+
io_manager_def=MockIOManager(),
352+
)
353+
354+
355+
@pytest.fixture(scope="session")
356+
def mock_asset_laz_tiles_ahn4_200m(test_data_dir):
357+
class MockIOManager(IOManager):
358+
def load_input(self, context):
359+
return (
360+
test_data_dir
361+
/ "reconstruction_input"
362+
/ "pointcloud"
363+
/ "AHN4"
364+
/ "tiles_200m"
365+
)
366+
367+
def handle_output(self, context, obj): # pragma: no cover
368+
raise NotImplementedError()
369+
370+
return SourceAsset(
371+
key=AssetKey(["ahn", "laz_tiles_ahn4_200m"]),
372+
io_manager_def=MockIOManager(),
373+
)
374+
375+
376+
@pytest.fixture(scope="session")
377+
def mock_asset_laz_tiles_ahn5_200m(test_data_dir):
378+
class MockIOManager(IOManager):
379+
def load_input(self, context):
380+
return (
381+
test_data_dir
382+
/ "reconstruction_input"
383+
/ "pointcloud"
384+
/ "AHN5"
385+
/ "tiles_200m"
386+
)
387+
388+
def handle_output(self, context, obj): # pragma: no cover
389+
raise NotImplementedError()
390+
391+
return SourceAsset(
392+
key=AssetKey(["ahn", "laz_tiles_ahn5_200m"]),
393+
io_manager_def=MockIOManager(),
394+
)

packages/core/tests/test_integration.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ def test_integration_reconstruction_and_export(
3535
mock_asset_reconstruction_input,
3636
mock_asset_tiles,
3737
mock_asset_index,
38+
mock_asset_laz_tiles_ahn3_200m,
39+
mock_asset_laz_tiles_ahn4_200m,
40+
mock_asset_laz_tiles_ahn5_200m,
3841
):
3942
# update quadtree
4043
og_quadtree = test_data_dir / "quadtree.tsv"
@@ -104,6 +107,9 @@ def test_integration_reconstruction_and_export(
104107
mock_asset_reconstruction_input,
105108
mock_asset_tiles,
106109
mock_asset_index,
110+
mock_asset_laz_tiles_ahn3_200m,
111+
mock_asset_laz_tiles_ahn4_200m,
112+
mock_asset_laz_tiles_ahn5_200m,
107113
*reconstruction_assets,
108114
*all_export_assets,
109115
],

0 commit comments

Comments
 (0)