From f6938e7081ddd8f256e9d5a98566eba636fa7172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag=20W=C3=A4stberg?= Date: Wed, 8 May 2024 14:46:58 +0200 Subject: [PATCH] split building walls --- src/dtcc_builder/__init__.py | 10 +++- src/dtcc_builder/building/modify.py | 59 ++++++++++++++++++- src/dtcc_builder/geometry_builders/terrain.py | 3 +- src/dtcc_builder/polygons/polygons.py | 51 ++++++++++++++++ 4 files changed, 117 insertions(+), 6 deletions(-) diff --git a/src/dtcc_builder/__init__.py b/src/dtcc_builder/__init__.py index 05f4ff5..0f96ebd 100644 --- a/src/dtcc_builder/__init__.py +++ b/src/dtcc_builder/__init__.py @@ -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 @@ -48,5 +53,6 @@ "flat_terrain", "merge_building_footprints", "simplify_building_footprints", - "fix_building_footprint_clearance" + "fix_building_footprint_clearance", + "split_footprint_walls" ] diff --git a/src/dtcc_builder/building/modify.py b/src/dtcc_builder/building/modify.py index b945f76..76fb029 100644 --- a/src/dtcc_builder/building/modify.py +++ b/src/dtcc_builder/building/modify.py @@ -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 @@ -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] ) @@ -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: @@ -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: @@ -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 diff --git a/src/dtcc_builder/geometry_builders/terrain.py b/src/dtcc_builder/geometry_builders/terrain.py index b7144ca..f046b80 100644 --- a/src/dtcc_builder/geometry_builders/terrain.py +++ b/src/dtcc_builder/geometry_builders/terrain.py @@ -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, @@ -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, diff --git a/src/dtcc_builder/polygons/polygons.py b/src/dtcc_builder/polygons/polygons.py index 1070f64..09d7ddc 100644 --- a/src/dtcc_builder/polygons/polygons.py +++ b/src/dtcc_builder/polygons/polygons.py @@ -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