Skip to content

Commit

Permalink
ruff formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
fgebhart committed Oct 27, 2024
1 parent 14f45a0 commit 834f172
Show file tree
Hide file tree
Showing 19 changed files with 201 additions and 74 deletions.
9 changes: 0 additions & 9 deletions .flake8

This file was deleted.

41 changes: 31 additions & 10 deletions mapa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,19 @@ def convert_array_to_stl(
log.debug("🔍 reducing image resolution...")
array = reduce_resolution(array, bin_factor=bin_fac)

triangles = compute_all_triangles(array, desired_size, z_offset, z_scale, elevation_scale)
triangles = compute_all_triangles(
array, desired_size, z_offset, z_scale, elevation_scale
)
log.debug("💾 saving data to stl file...")

output_file = save_to_stl_file(triangles, output_file, as_ascii)
log.info(f"🎉 successfully generated STL file: {Path(output_file).absolute()}")
return Path(output_file)


def _get_desired_size(array: np.ndarray, x: float, y: float, ensure_squared: bool) -> ModelSize:
def _get_desired_size(
array: np.ndarray, x: float, y: float, ensure_squared: bool
) -> ModelSize:
if ensure_squared:
return ModelSize(x=x, y=y)
else:
Expand All @@ -82,15 +86,19 @@ def convert_tiff_to_stl(
z_scale: float,
ensure_squared: bool = False,
) -> Path:
output_file = verify_input_and_output_are_valid(input=input_file, output=output_file)
output_file = verify_input_and_output_are_valid(
input=input_file, output=output_file
)

tiff = rio.open(input_file)
elevation_scale = determine_elevation_scale(tiff, model_size)
array = tiff_to_array(tiff)

if ensure_squared:
array = cut_array_to_square(array)
desired_size = _get_desired_size(array=array, x=model_size, y=model_size, ensure_squared=ensure_squared)
desired_size = _get_desired_size(
array=array, x=model_size, y=model_size, ensure_squared=ensure_squared
)

return convert_array_to_stl(
array=array,
Expand All @@ -111,7 +119,9 @@ def _fetch_merge_and_clip_tiffs(
cache_dir: Path,
progress_bar: Union[None, ProgressBar] = None,
) -> Path:
tiffs = fetch_stac_items_for_bbox(bbox_geojson, allow_caching, cache_dir, progress_bar)
tiffs = fetch_stac_items_for_bbox(
bbox_geojson, allow_caching, cache_dir, progress_bar
)
if len(tiffs) > 1:
merged_tiff = merge_tiffs(tiffs, bbox_hash, cache_dir)
else:
Expand All @@ -120,14 +130,19 @@ def _fetch_merge_and_clip_tiffs(


def _get_tiff_for_bbox(
bbox_geojson: dict, allow_caching: bool, cache_dir: Path, progress_bar: Union[None, ProgressBar] = None
bbox_geojson: dict,
allow_caching: bool,
cache_dir: Path,
progress_bar: Union[None, ProgressBar] = None,
) -> Path:
bbox_hash = get_hash_of_geojson(bbox_geojson)
if tiff_for_bbox_is_cached(bbox_hash, cache_dir) and allow_caching:
log.info("🚀 using cached tiff!")
return path_to_clipped_tiff(bbox_hash, cache_dir)
else:
return _fetch_merge_and_clip_tiffs(bbox_geojson, bbox_hash, allow_caching, cache_dir, progress_bar)
return _fetch_merge_and_clip_tiffs(
bbox_geojson, bbox_hash, allow_caching, cache_dir, progress_bar
)


def convert_bbox_to_stl(
Expand Down Expand Up @@ -211,7 +226,9 @@ def convert_bbox_to_stl(
steps = tiles.x * tiles.y * 2 if compress else tiles.x * tiles.y
progress_bar = ProgressBar(progress_bar=progress_bar, steps=steps)

path_to_tiff = _get_tiff_for_bbox(bbox_geometry, allow_caching, Path(cache_dir), progress_bar)
path_to_tiff = _get_tiff_for_bbox(
bbox_geometry, allow_caching, Path(cache_dir), progress_bar
)
tiff = rio.open(path_to_tiff)
elevation_scale = determine_elevation_scale(tiff, model_size)
array = tiff_to_array(tiff)
Expand All @@ -237,12 +254,16 @@ def convert_bbox_to_stl(
z_offset=z_offset,
z_scale=z_scale,
elevation_scale=elevation_scale,
output_file=f"{output_file}_{i+1}.stl" if len(tiled_arrays) > 1 else f"{output_file}.stl",
output_file=f"{output_file}_{i+1}.stl"
if len(tiled_arrays) > 1
else f"{output_file}.stl",
)
)
if progress_bar:
progress_bar.step()
if compress:
return create_zip_archive(files=stl_files, output_file=f"{output_file}.zip", progress_bar=progress_bar)
return create_zip_archive(
files=stl_files, output_file=f"{output_file}.zip", progress_bar=progress_bar
)
else:
return stl_files[0] if len(stl_files) == 1 else stl_files
55 changes: 44 additions & 11 deletions mapa/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,12 @@ def _create_raster(array: npt.ArrayLike, max_x: int, max_y: int) -> np.ndarray:
raster[ix][iy] = array[ix][iy]
else:
# z value in raster is average of four neighbors
raster[ix][iy] = (array[ix][iy] + array[ix - 1][iy] + array[ix][iy - 1] + array[ix - 1][iy - 1]) / 4
raster[ix][iy] = (
array[ix][iy]
+ array[ix - 1][iy]
+ array[ix][iy - 1]
+ array[ix - 1][iy - 1]
) / 4
return raster


Expand Down Expand Up @@ -203,7 +208,9 @@ def _compute_triangles_of_3d_surface(
# first vertex
triangles[ix, iy, 2, 0, 0] = (ix + 1) * x_scale
triangles[ix, iy, 2, 0, 1] = (iy + 1) * y_scale
triangles[ix, iy, 2, 0, 2] = (raster[ix + 1, iy + 1]) * z_scale + z_offset
triangles[ix, iy, 2, 0, 2] = (
(raster[ix + 1, iy + 1]) * z_scale + z_offset
)
# second vertex
triangles[ix, iy, 2, 1, 0] = ix * x_scale
triangles[ix, iy, 2, 1, 1] = (iy + 1) * y_scale
Expand All @@ -225,13 +232,21 @@ def _compute_triangles_of_3d_surface(
# third vertex
triangles[ix, iy, 3, 2, 0] = (ix + 1) * x_scale
triangles[ix, iy, 3, 2, 1] = (iy + 1) * y_scale
triangles[ix, iy, 3, 2, 2] = (raster[ix + 1, iy + 1]) * z_scale + z_offset
triangles[ix, iy, 3, 2, 2] = (
(raster[ix + 1, iy + 1]) * z_scale + z_offset
)

return triangles.reshape((max_x * max_y * 4, 3, 3))


def _compute_triangles_of_body_side(
raster: npt.ArrayLike, max_x: int, max_y: int, x_scale: float, y_scale: float, z_scale: float, z_offset: float
raster: npt.ArrayLike,
max_x: int,
max_y: int,
x_scale: float,
y_scale: float,
z_scale: float,
z_offset: float,
) -> np.ndarray:
# loop over raster and build triangles when in first and last col and row
triangles = np.full((max_x * 4 + max_y * 4, 3, 3), -1.0, dtype=np.float64)
Expand Down Expand Up @@ -354,7 +369,9 @@ def _compute_triangles_of_body_side(
return triangles


def _compute_triangles_of_bottom(max_x: int, max_y: int, x_scale: float, y_scale: float) -> np.ndarray:
def _compute_triangles_of_bottom(
max_x: int, max_y: int, x_scale: float, y_scale: float
) -> np.ndarray:
# first row
fr_triangles = np.full((max_x - 1, 3, 3), -1.0, dtype=np.float64)
for i, cnt in enumerate(range(0, max_x - 1)):
Expand Down Expand Up @@ -428,16 +445,22 @@ def _compute_triangles_of_bottom(max_x: int, max_y: int, x_scale: float, y_scale
center_triangles[1, 2, 1] = 1 * y_scale
center_triangles[1, 2, 2] = 0

return np.vstack((fr_triangles, lr_triangles, fc_triangles, lc_triangles, center_triangles))
return np.vstack(
(fr_triangles, lr_triangles, fc_triangles, lc_triangles, center_triangles)
)


def _determine_z_offset(z_offset: Union[None, float], minimum: float, elevation_scale: float) -> float:
def _determine_z_offset(
z_offset: Union[None, float], minimum: float, elevation_scale: float
) -> float:
if z_offset is None:
# using the natural height, i.e. islands will have an z_offset of ~0 and mountains will have a larger z_offset
return minimum * elevation_scale
else:
if z_offset < 0:
log.warning("☝️ Warning: Be careful using negative z_offsets, as it might break your 3D model.")
log.warning(
"☝️ Warning: Be careful using negative z_offsets, as it might break your 3D model."
)
# subtract scaled minimum from z_offset to ensure the input z_offset will remain
return z_offset - minimum * elevation_scale

Expand Down Expand Up @@ -480,14 +503,24 @@ def compute_all_triangles(
z_scale=combined_z_scale,
z_offset=z_offset,
)
bottom_triangles = _compute_triangles_of_bottom(max_x=max_x, max_y=max_y, x_scale=x_scale, y_scale=y_scale)
bottom_triangles = _compute_triangles_of_bottom(
max_x=max_x, max_y=max_y, x_scale=x_scale, y_scale=y_scale
)
return np.vstack((dem_triangles, side_triangles, bottom_triangles))


def reduce_resolution(array: npt.ArrayLike, bin_factor: int) -> np.ndarray:
strided = as_strided(
array,
shape=(array.shape[0] // bin_factor, array.shape[1] // bin_factor, bin_factor, bin_factor),
strides=((array.strides[0] * bin_factor, array.strides[1] * bin_factor) + array.strides),
shape=(
array.shape[0] // bin_factor,
array.shape[1] // bin_factor,
bin_factor,
bin_factor,
),
strides=(
(array.strides[0] * bin_factor, array.strides[1] * bin_factor)
+ array.strides
),
)
return strided.mean(axis=-1).mean(axis=-1)
12 changes: 9 additions & 3 deletions mapa/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
@click.option("--input", help="Path to input TIFF file.")
@click.option("--output", help="Path to output STL file.")
@click.option(
"--as-ascii", is_flag=True, help="Save output STL as ascii file. If not provided, output file will be binary."
"--as-ascii",
is_flag=True,
help="Save output STL as ascii file. If not provided, output file will be binary.",
)
@click.option(
"--model-size",
Expand Down Expand Up @@ -46,7 +48,9 @@
f"Defaults to {conf.DEFAULT_Z_SCALE}."
),
)
@click.option("--demo", is_flag=True, help="Converts a demo tif of Hawaii into a STL file.")
@click.option(
"--demo", is_flag=True, help="Converts a demo tif of Hawaii into a STL file."
)
@click.option(
"--ensure-squared",
is_flag=True,
Expand Down Expand Up @@ -78,7 +82,9 @@ def dem2stl(
max_res = True
z_scale = 2.5

convert_tiff_to_stl(input, as_ascii, model_size, output, max_res, z_offset, z_scale, ensure_squared)
convert_tiff_to_stl(
input, as_ascii, model_size, output, max_res, z_offset, z_scale, ensure_squared
)


@click.command(help="🗺 Draw a bounding box on a map and turn it into a STL file 🗺")
Expand Down
11 changes: 9 additions & 2 deletions mapa/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@


def show_map(center: List[float] = CENTER, zoom: int = ZOOM) -> Tuple[Map, DrawControl]:
m = Map(center=center, zoom=zoom, scroll_wheel_zoom=True, layout=Layout(height="600px"))
m = Map(
center=center, zoom=zoom, scroll_wheel_zoom=True, layout=Layout(height="600px")
)
m.add_control(ScaleControl(position="bottomleft"))
m.add_layer(basemap_to_tiles(basemaps.OpenTopoMap))

dc = DrawControl(rectangle={"shapeOptions": {"color": "#0000FF"}}, polyline={}, polygon={}, circlemarker={})
dc = DrawControl(
rectangle={"shapeOptions": {"color": "#0000FF"}},
polyline={},
polygon={},
circlemarker={},
)

def handle_draw(target, action, geo_json):
print("Rectangle detected, execute next cells to continue!")
Expand Down
2 changes: 1 addition & 1 deletion mapa/mapa.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
" ensure_squared=False,\n",
" split_area_in_tiles=\"1x1\",\n",
" compress=False,\n",
" cache_dir=\"/home/fabian/make/mapa/cache/\"\n",
" cache_dir=\"/home/fabian/make/mapa/cache/\",\n",
")"
]
},
Expand Down
4 changes: 3 additions & 1 deletion mapa/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
log = logging.getLogger(__name__)


def clip_tiff_to_bbox(input_tiff: Path, bbox_geometry: dict, bbox_hash: str, cache_dir: Path) -> Path:
def clip_tiff_to_bbox(
input_tiff: Path, bbox_geometry: dict, bbox_hash: str, cache_dir: Path
) -> Path:
log.debug("🔪 clipping region of interest...")
data = rio.open(input_tiff)
out_img, out_transform = mask(data, shapes=[bbox_geometry], crop=True)
Expand Down
4 changes: 3 additions & 1 deletion mapa/tiling.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ class TileFormat:
y: int


def split_array_into_tiles(array: np.ndarray, tiles_format: TileFormat) -> List[np.ndarray]:
def split_array_into_tiles(
array: np.ndarray, tiles_format: TileFormat
) -> List[np.ndarray]:
x, y = array.shape
if tiles_format.x > x or tiles_format.y > y:
raise ValueError("Input array is too small to be split into tiles.")
Expand Down
4 changes: 3 additions & 1 deletion mapa/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
def verify_input_and_output_are_valid(input: str, output: str) -> Path:
_verify_input_is_valid(input)
if output is None:
output = Path.home() / str(Path(input).name).replace(".tiff", ".stl").replace(".tif", ".stl")
output = Path.home() / str(Path(input).name).replace(".tiff", ".stl").replace(
".tif", ".stl"
)
_verify_output_is_valid(output)
return output

Expand Down
4 changes: 3 additions & 1 deletion mapa/zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@


def create_zip_archive(
files: List[Path], output_file: Union[str, Path], progress_bar: Union[ProgressBar, None] = None
files: List[Path],
output_file: Union[str, Path],
progress_bar: Union[ProgressBar, None] = None,
) -> Path:
log.info(f"📦 compressing stl files: {[f.name for f in files]}")
with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as zip_file:
Expand Down
9 changes: 0 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,3 @@ pre-commit = "^3.2.1"
[tool.poetry.scripts]
dem2stl = "mapa.cli:dem2stl"
mapa = "mapa.cli:mapa"

[tool.black]
target_version = ["py38"]
line-length = 121

[tool.isort]
multi_line_output = 3
line_length = 121
include_trailing_comma = true
18 changes: 16 additions & 2 deletions tests/regenerate_test_stls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,22 @@

ascii_output = Path(__file__).parent / "stl" / "hawaii_ascii.stl"
print(f"regenerating ascii stl file: {ascii_output}")
cli.invoke(dem2stl, ["--input", str(test_tiff), "--as-ascii", "--output", ascii_output, "--model-size", 200])
cli.invoke(
dem2stl,
[
"--input",
str(test_tiff),
"--as-ascii",
"--output",
ascii_output,
"--model-size",
200,
],
)

binary_output = Path(__file__).parent / "stl" / "hawaii_binary.stl"
print(f"regenerating binary stl file: {binary_output}")
cli.invoke(dem2stl, ["--input", str(test_tiff), "--output", binary_output, "--model-size", 200])
cli.invoke(
dem2stl,
["--input", str(test_tiff), "--output", binary_output, "--model-size", 200],
)
Loading

0 comments on commit 834f172

Please sign in to comment.