Commit 0fa02682 authored by Rick Gruber-Riemer's avatar Rick Gruber-Riemer

* Small refactoring of names

* Correction for levels if CityBlock has levels=0
parent acc916a4
......@@ -342,7 +342,10 @@ class Building(object):
self.polygon = utilities.simplify_balconies(self.polygon, parameters.BUILDING_SIMPLIFY_TOLERANCE_LINE,
parameters.BUILDING_SIMPLIFY_TOLERANCE_AWAY, self.refs_shared)
simplified_number = len(self.polygon.exterior.coords)
return original_number - simplified_number
difference = original_number - simplified_number
if difference > 0:
self.geometry = self.polygon
return difference
def _set_polygon(self, outer: shg.LinearRing, inner: List[shg.LinearRing]=list()) -> None:
self.polygon = shg.Polygon(outer, inner)
......@@ -700,7 +703,7 @@ class Building(object):
def _calculate_levels(self) -> int:
import owbb.models
if isinstance(self.zone, owbb.models.CityBlock):
if isinstance(self.zone, owbb.models.CityBlock) and self.zone.building_levels > 0:
return self.zone.building_levels
else:
building_class = get_building_class(self.tags)
......@@ -725,20 +728,19 @@ class Building(object):
# no complex roof on buildings with inner rings
if self.polygon.interiors:
if len(self.polygon.interiors) == 1:
self.roof_shape = roofs.RoofShape.skillion
self.roof_shape = roofs.RoofShape.skeleton
else:
allow_complex_roofs = False
# no complex roof on large buildings
elif self.area > parameters.BUILDING_COMPLEX_ROOFS_MAX_AREA:
allow_complex_roofs = False
# if area between thresholds, then have a look at the ratio between area and circumference
# the smaller the ratio, the less deep the building is compared to its length
# it is more common to have long houses with complex roofs than a square once it is a big building
# the formula basically states that if it was a rectangle, then the ratio between the long side length
# and the short side length should be at least 2.
elif (parameters.BUILDING_COMPLEX_ROOFS_MIN_RATIO_AREA < self.area <
parameters.BUILDING_COMPLEX_ROOFS_MAX_AREA) and (self.circumference > 3 * sqrt(2 * self.area)):
allow_complex_roofs = False
# if the area is between thresholds, then have a look at the ratio between area and circumference:
# the smaller the ratio, the less deep the building is compared to its length.
# It is more common to have long houses with complex roofs than a square once it is a big building.
elif parameters.BUILDING_COMPLEX_ROOFS_MIN_RATIO_AREA < self.area < \
parameters.BUILDING_COMPLEX_ROOFS_MAX_AREA:
if roofs.roof_looks_square(self.circumference, self.area):
allow_complex_roofs = False
# no complex roof on tall buildings
elif self.levels > parameters.BUILDING_COMPLEX_ROOFS_MAX_LEVELS and s.K_ROOF_SHAPE not in self.tags:
allow_complex_roofs = False
......@@ -1011,23 +1013,22 @@ class Building(object):
"""Writes the roof vertices and faces to an ac3d object."""
if self.roof_shape is roofs.RoofShape.flat:
roofs.flat(ac_object, index_first_node_in_ac_obj, self, roof_mgr, roof_mat_idx, stats)
else:
# -- pitched roof for > 4 ground nodes
if self.pts_all_count > 4:
if self.roof_shape is roofs.RoofShape.skillion:
roofs.separate_skillion(ac_object, self, roof_mat_idx)
elif self.roof_shape is roofs.RoofShape.pyramidal:
roofs.separate_pyramidal(ac_object, self, roof_mat_idx, roofs.RoofShape.pyramidal)
roofs.separate_pyramidal(ac_object, self, roof_mat_idx)
elif self.roof_shape is roofs.RoofShape.dome:
roofs.separate_pyramidal(ac_object, self, roof_mat_idx, roofs.RoofShape.dome)
roofs.separate_pyramidal(ac_object, self, roof_mat_idx)
elif self.roof_shape is roofs.RoofShape.onion:
roofs.separate_pyramidal(ac_object, self, roof_mat_idx, roofs.RoofShape.onion)
roofs.separate_pyramidal(ac_object, self, roof_mat_idx)
else:
skeleton = myskeleton.myskel(ac_object, self, stats, offset_xy=cluster_offset,
offset_z=self.beginning_of_roof_above_sea_level,
max_height=parameters.BUILDING_SKEL_ROOF_MAX_HEIGHT)
if skeleton:
skeleton_possible = myskeleton.myskel(ac_object, self, stats, offset_xy=cluster_offset,
offset_z=self.beginning_of_roof_above_sea_level,
max_height=parameters.BUILDING_SKEL_ROOF_MAX_HEIGHT)
if skeleton_possible:
stats.have_complex_roof += 1
else: # something went wrong - fall back to flat roof
......@@ -1040,18 +1041,19 @@ class Building(object):
elif self.roof_shape is roofs.RoofShape.hipped:
roofs.separate_hipped(ac_object, self, roof_mat_idx)
elif self.roof_shape is roofs.RoofShape.pyramidal:
roofs.separate_pyramidal(ac_object, self, roof_mat_idx, roofs.RoofShape.pyramidal)
roofs.separate_pyramidal(ac_object, self, roof_mat_idx)
elif self.roof_shape is roofs.RoofShape.dome:
roofs.separate_pyramidal(ac_object, self, roof_mat_idx, roofs.RoofShape.dome)
roofs.separate_pyramidal(ac_object, self, roof_mat_idx)
elif self.roof_shape is roofs.RoofShape.onion:
roofs.separate_pyramidal(ac_object, self, roof_mat_idx, roofs.RoofShape.onion)
roofs.separate_pyramidal(ac_object, self, roof_mat_idx)
elif self.roof_shape is roofs.RoofShape.skillion:
roofs.separate_skillion(ac_object, self, roof_mat_idx)
else:
logging.warning("Roof type %s seems to be unsupported, but is mapped ", self.roof_shape.name)
roofs.flat(ac_object, index_first_node_in_ac_obj, self, roof_mgr, roof_mat_idx, stats)
else: # fall back to pyramidal
roofs.separate_pyramidal(ac_object, self, roof_mat_idx, roofs.RoofShape.pyramidal)
self.roof_shape = roofs.RoofShape.pyramidal
roofs.separate_pyramidal(ac_object, self, roof_mat_idx)
def __str__(self):
return "<OSM_ID %d at %s>" % (self.osm_id, hex(id(self)))
......
......@@ -120,7 +120,7 @@ NO_ELEV Boolean False The only re
Buildings
=========
.. _chapter-parameters-buildings-diverse
.. _chapter-parameters-buildings-diverse:
------------------
Diverse Parameters
......@@ -163,15 +163,12 @@ In order to reduce the total number of nodes of the buildings mesh and thereby r
============================================= ======== ======= ==============================================================================
Parameter Type Default Description / Example
============================================= ======== ======= ==============================================================================
BUILDING_SIMPLIFY_TOLERANCE_LINE Number 1.0 The point on the base line may at most be this value away from the straight
line between the node before the balcony and the node after the balcony.
This in order to prevent that e.g. a stair-case feature is removed.
BUILDING_SIMPLIFY_TOLERANCE_AWAY Number 2.5 The 2 points sticking out (or in) may not be more than this value away from
the straight line between the node before the balcony and the node after the
balcony. This in order to prevent clearly visible "balconies" to be removed.
============================================= ======== ======= ==============================================================================
......@@ -317,6 +314,9 @@ BUILDING_ROOF_SHAPE_RATIO Dict . If the ``ro
The sum of the ratios must give 1.0 and the values must be one of the values
defined for RoofShapes in roof.py.
BUILDING_ROOF_SIMPLIFY_TOLERANCE Number 0.5 All points in the simplified roof will be within the tolerance distance of
the original geometry.
============================================= ======== ======= ==============================================================================
......
......@@ -131,10 +131,11 @@ BUILDING_SKEL_MAX_NODES = 10 # -- max number of nodes for which we genera
BUILDING_SKILLION_ROOF_MAX_HEIGHT = 2.
BUILDING_SKEL_ROOF_MAX_HEIGHT = 6. # -- skip skeleton roofs (gabled, pyramidal, ..) if the roof height is larger than this
BUILDING_SKEL_ROOF_DEFAULT_HEIGHT = 2.5 # if the roof_height is not given what we use to calculate real building heihgt temporarily
BUILDING_ROOF_SIMPLIFY_TOLERANCE = .5
# If the roof_type is missing, what shall be the distribution of roof_types (must sum up to 1.0)
# The keys are the shapes and must correspond to valid RoofShape values in roofs.py
BUILDING_ROOF_SHAPE_RATIO = {'flat': 0.1, 'skillion': 0.05, 'gabled': 0.75, 'hipped': 0.1}
BUILDING_ROOF_SHAPE_RATIO = {'flat': 0.1, 'gabled': 0.8, 'hipped': 0.1}
# ==================== RECTIFY BUILDINGS ============
RECTIFY_ENABLED = True
......
import copy
from enum import IntEnum, unique
import logging
from math import sin, cos, atan2, radians, tan
from math import sin, cos, atan2, radians, tan, sqrt
from typing import List
import unittest
import numpy as np
......@@ -16,6 +17,16 @@ GAMBREL_ANGLE_LOWER_PART = 70
GAMBREL_HEIGHT_RATIO_LOWER_PART = 0.75
def roof_looks_square(circumference: float, area: float) -> bool:
"""Determines if a roof's floor plan looks square.
The formula basically states that if it was a rectangle, then the ratio between the long side length
and the short side length should be at least 2.
"""
if circumference < 3 * sqrt(2 * area):
return True
return False
@unique
class RoofShape(IntEnum):
"""Matches the roof:shape in OSM, see http://wiki.openstreetmap.org/wiki/Simple_3D_buildings.
......@@ -33,6 +44,7 @@ class RoofShape(IntEnum):
dome = 5
onion = 6
gambrel = 7
skeleton = 99 # does not exist in OSM
def map_osm_roof_shape(osm_roof_shape: str) -> RoofShape:
......@@ -63,7 +75,7 @@ def map_osm_roof_shape(osm_roof_shape: str) -> RoofShape:
# probably if someone actually has tried to specify a shape, then 'flat' is unliekly to be misspelled and
# most probably a form with a ridge was meant.
logging.debug('Not handled roof shape found: %s. Therefore transformed to "hipped".', _shape)
return RoofShape.hipped
return RoofShape.skeleton
def flat(ac_object: ac.Object, index_first_node_in_ac_obj: int, b, roof_mgr: RoofManager, roof_mat_idx: int,
......@@ -322,8 +334,9 @@ def separate_gable(ac_object, b, roof_mat_idx: int, facade_mat_idx: int, inward_
mat_idx=roof_mat_idx)
def separate_pyramidal(ac_object: ac.Object, b, roof_mat_idx: int, shape: RoofShape) -> None:
def separate_pyramidal(ac_object: ac.Object, b, roof_mat_idx: int) -> None:
"""Pyramidal, dome or onion roof."""
shape = b.roof_shape
roof_texture = b.roof_texture
# -- get roof height
......@@ -553,3 +566,22 @@ def _face_uv_flat_roof(nodes: List[int], pts_all, texture: Texture):
uv[:, 0] = texture.x(uv[:, 0] / (texture.h_size_meters * scale_factor))
uv[:, 1] = texture.y(uv[:, 1] / (texture.v_size_meters * scale_factor))
return uv
# ================ UNITTESTS =======================
class TestRoofs(unittest.TestCase):
def test_roof_looks_square(self):
long_side = 1
short_side = 1
self.assertTrue(roof_looks_square(2*long_side + 2*short_side, long_side*short_side), "square")
long_side = 1.5
short_side = 1
self.assertTrue(roof_looks_square(2*long_side + 2*short_side, long_side*short_side), "almost square")
long_side = 2
short_side = 1
self.assertFalse(roof_looks_square(2*long_side + 2*short_side, long_side*short_side), "1:2 ratio")
long_side = 2.1
short_side = 1
self.assertFalse(roof_looks_square(2*long_side + 2*short_side, long_side*short_side), "ratio larger than 1:2")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment