Source code for dtcc_builder.building.modify

from dtcc_model import Building, GeometryType, MultiSurface, Surface, GeometryType
from dtcc_builder.polygons.polygons import (
    polygon_merger,
    simplify_polygon,
    remove_slivers,
    fix_clearance,
    split_polygon_sides,
)

from dtcc_builder.polygons.surface import clean_multisurface, clean_surface

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, Union
from statistics import mean
import numpy as np


@register_model_method
def get_footprint(building: Building, geom_type: GeometryType = None) -> Surface:
    geom = None
    if geom_type is not None:
        geom = building.flatten_geometry(geom_type)
    else:
        lod_levels = [
            GeometryType.LOD0,
            GeometryType.LOD1,
            GeometryType.LOD2,
            GeometryType.LOD3,
        ]
        for lod in lod_levels:
            geom = building.flatten_geometry(lod)
            if geom is not None:
                break

    if geom is None:
        warning(f"Building {building.id} has no LOD geometry.")
        return None
    height = geom.bounds.zmax
    footprint = geom.to_polygon()
    if footprint.geom_type == "MultiPolygon":
        geom.to_polygon()
    # print(f"get footprint has {len(footprint.exterior.coords)} vertices")
    s = Surface()
    s.from_polygon(footprint, height)
    return s


[docs] def merge_building_footprints( buildings: List[Building], lod: GeometryType = None, max_distance: float = 0.5, min_area=10, ) -> List[Building]: buildings_geom = [building.flatten_geometry(lod) for building in buildings] # print(buildings_geom) buildings_geom = [geom for geom in buildings_geom if geom is not None] building_heights = [geom.zmax for geom in buildings_geom] footprints = [geom.to_polygon(max_distance / 4) for geom in buildings_geom] merged_footprint, merged_indices = polygon_merger( footprints, max_distance, min_area=min_area ) merged_buildings = [] for idx, footprint in enumerate(merged_footprint): if footprint.geom_type == "MultiPolygon": ValueError("Merged footprint is a MultiPolygon") footprint = footprint.simplify(1e-2, True) if footprint.geom_type == "MultiPolygon": ValueError("simplified footprint is a MultiPolygon") footprint = remove_slivers(footprint, max_distance / 2) 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] ) building_surface = Surface() building_surface.from_polygon(footprint, height) 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
[docs] def simplify_building_footprints( buildings: List[Building], tolerance: float = 0.5, lod: GeometryType = GeometryType.LOD0, ) -> List[Building]: simplified_buildings = [] for building in buildings: lod0 = building.lod0 if lod0 is None: continue footprint = lod0.to_polygon() footprint = footprint.simplify(tolerance, True) building_surface = Surface() building_surface.from_polygon(footprint, lod0.zmax) simplified_building = building.copy() simplified_building.add_geometry(building_surface, GeometryType.LOD0) simplified_building.calculate_bounds() simplified_buildings.append(simplified_building) return simplified_buildings
[docs] def fix_building_footprint_clearance( buildings: List[Building], clearance: float = 0.5, lod: GeometryType = GeometryType.LOD0, ) -> List[Building]: fixed_buildings = [] for building in buildings: lod0 = building.lod0 if lod0 is None: continue footprint = lod0.to_polygon() footprint = fix_clearance(footprint, clearance) building_surface = Surface() building_surface.from_polygon(footprint, lod0.zmax) fixed_building = building.copy() fixed_building.add_geometry(building_surface, GeometryType.LOD0) fixed_building.calculate_bounds() fixed_buildings.append(fixed_building) return fixed_buildings
[docs] 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
def clean_building_geometry( building: Building, lod=GeometryType.LOD2, tol=1e-2 ) -> Building: building_geom = building.geometry.get(lod, None) if building_geom is None: return building if isinstance(building_geom, MultiSurface): cleaned_geom = clean_multisurface(building_geom, tol) elif isinstance(building_geom, Surface): cleaned_geom = clean_surface(building_geom, tol) else: warning(f"Unsupported geometry type: {type(building_geom)}") return building building.add_geometry(cleaned_geom, lod) return building