Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Commit

Permalink
split building walls
Browse files Browse the repository at this point in the history
  • Loading branch information
dwastberg committed May 8, 2024
1 parent aa1fa7d commit f6938e7
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 6 deletions.
10 changes: 8 additions & 2 deletions src/dtcc_builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@
build_lod1_buildings,
)

from .building.modify import merge_building_footprints, simplify_building_footprints, fix_building_footprint_clearance

from .building.modify import (
merge_building_footprints,
simplify_building_footprints,
fix_building_footprint_clearance,
split_footprint_walls
)

from .geometry_builders.meshes import build_surface_mesh

Expand All @@ -48,5 +53,6 @@
"flat_terrain",
"merge_building_footprints",
"simplify_building_footprints",
"fix_building_footprint_clearance"
"fix_building_footprint_clearance",
"split_footprint_walls"
]
59 changes: 56 additions & 3 deletions src/dtcc_builder/building/modify.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
simplify_polygon,
remove_slivers,
fix_clearance,
split_polygon_sides,
)
from dtcc_builder.register import register_model_method
from shapely.geometry import Polygon
from dtcc_builder.logging import debug, info, warning, error

from typing import List, Tuple
from typing import List, Tuple, Union
from statistics import mean
import numpy as np

Expand Down Expand Up @@ -67,6 +68,14 @@ def merge_building_footprints(
if footprint.geom_type == "MultiPolygon":
ValueError("de-slivered footprint is a MultiPolygon")
indices = merged_indices[idx]

original_buildings = [buildings[i] for i in indices]
original_footprints = [footprints[i] for i in indices]
area_argsort = np.argsort([footprint.area for footprint in original_footprints])

original_buildings = [original_buildings[i] for i in area_argsort]
building_attributes = merge_building_attributes(original_buildings)

height = sum([building_heights[i] * footprints[i].area for i in indices]) / sum(
[footprints[i].area for i in indices]
)
Expand All @@ -76,11 +85,24 @@ def merge_building_footprints(

building = Building()
building.add_geometry(building_surface, GeometryType.LOD0)
building.attributes = building_attributes
merged_buildings.append(building)
return merged_buildings


def merge_building_attributes(buildings: List[Building]) -> dict:
attributes = {}
for building in buildings:
for k, v in building.attributes.items():
if v:
attributes[k] = v
return attributes


def simplify_building_footprints(
buildings: List[Building], tolerance: float = 0.5, lod: GeometryType = GeometryType.LOD0
buildings: List[Building],
tolerance: float = 0.5,
lod: GeometryType = GeometryType.LOD0,
) -> List[Building]:
simplified_buildings = []
for building in buildings:
Expand All @@ -97,8 +119,11 @@ def simplify_building_footprints(
simplified_buildings.append(simplified_building)
return simplified_buildings


def fix_building_footprint_clearance(
buildings: List[Building], clearance: float = 0.5, lod: GeometryType = GeometryType.LOD0
buildings: List[Building],
clearance: float = 0.5,
lod: GeometryType = GeometryType.LOD0,
) -> List[Building]:
fixed_buildings = []
for building in buildings:
Expand All @@ -114,3 +139,31 @@ def fix_building_footprint_clearance(
fixed_building.calculate_bounds()
fixed_buildings.append(fixed_building)
return fixed_buildings


def split_footprint_walls(
buildings: List[Building], max_wall_length: Union[float, List[float]] = 10
) -> List[Building]:
split_buildings = []
if isinstance(max_wall_length, (int, float)):
max_wall_length = [max_wall_length] * len(buildings)
elif len(max_wall_length) != len(buildings):
error(
"max_wall_length must be a single value or a list of values for each building."
)
return
for building, wall_length in zip(buildings, max_wall_length):
lod0 = building.lod0
if lod0 is None:
continue

footprint = lod0.to_polygon()
footprint = split_polygon_sides(footprint, wall_length)
building_surface = Surface()
building_surface.from_polygon(footprint, lod0.zmax)
split_building = building.copy()
split_building.add_geometry(building_surface, GeometryType.LOD0)
split_building.calculate_bounds()
split_buildings.append(split_building)

return split_buildings
3 changes: 2 additions & 1 deletion src/dtcc_builder/geometry_builders/terrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def build_terrain_mesh(
dem: Raster = None,
pointcloud: PointCloud = None,
subdomains: [Surface] = None,
subdomain_resolution=None,
max_mesh_size=10,
min_mesh_angle=25,
smoothing=3,
Expand All @@ -28,7 +29,7 @@ def build_terrain_mesh(
if subdomains is None:
subdomains = []
else:
subdomains = [create_builder_polygon(sub.to_polygon) for sub in subdomains]
subdomains = [create_builder_polygon(sub.to_polygon()) for sub in subdomains]
terrain_mesh = _dtcc_builder.build_terrain_mesh(
subdomains,
_builder_gridfield,
Expand Down
51 changes: 51 additions & 0 deletions src/dtcc_builder/polygons/polygons.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,54 @@ def fix_clearance(
return polygon
polygon = shapely.convex_hull(original_polygon)
return polygon


def _split_line(line, max_length):
# Calculate the length of the line
length = line.length
if length <= max_length:
return [line]

# Number of parts to split into
num_parts = int(np.ceil(length / max_length))
new_length = length / num_parts
points = []

# Generate points along the line at intervals of new_length
for i in range(num_parts + 1):
point = line.interpolate(new_length * i)
points.append(point)

# Create new lines from these points
new_lines = [LineString([points[i], points[i + 1]]) for i in range(len(points) - 1)]
return new_lines


def _split_boundary(boundary, max_length):
new_points = []
for i, (p1, p2) in enumerate(zip(boundary.coords[:-1], boundary.coords[1:])):
line = LineString([p1, p2])
split_lines = _split_line(line, max_length)
# Add the start point of the first segment
if i == 0:
new_points.append(split_lines[0].coords[0])
# Add the end points of each segment
for split_line in split_lines:
new_points.append(split_line.coords[1])
return new_points


def split_polygon_sides(polygon, max_length):
# Handle exterior
new_exterior_points = _split_boundary(polygon.exterior, max_length)
new_exterior = Polygon(new_exterior_points)

# Handle interiors (holes)
new_interiors = []
for interior in polygon.interiors:
new_interior_points = _split_boundary(interior, max_length)
new_interiors.append(new_interior_points)

# Create new polygon with holes
new_polygon = Polygon(new_exterior_points, [interior for interior in new_interiors])
return new_polygon

0 comments on commit f6938e7

Please sign in to comment.