Commit 4aea135c authored by Rick Gruber-Riemer's avatar Rick Gruber-Riemer

Inner points in roof hints now for 4, 5, and 6 points

parent b49f9053
......@@ -130,6 +130,15 @@ Node numbering for ``gambrel`` roof type:
.. _Key Place: https://wiki.openstreetmap.org/wiki/Key:place
There are three situations, where a gabled roof is done. See also class RoofHints in roofs.py:
* A 4-sided building with 4 nodes and 2 adjacent buildings share 1 common node with the building (if there is only one neighbour building or if the 2 neighbours are on each side, then a gabled roof is done). If more than 2 neighbours we would not know what to do.
* A somewhat square-shaped building with 5 nodes and 5 sides, where the inner node is shared with a neighbour building. It is a requirement that the inner node is making an angle between 170 and 190 degrees with the previous/next node. Otherwise the roof just gets a skeleton roof.
* A L-shaped building with 6 nodes and 6 sides where the inner node is not shared with a neighbour building. If is a requirement that the inner node is making an angle between 80 and 100 degrees with the previous/next node. Otherwise the roof just gets a skeleton roof.
.. image:: roof_inner.jpg
.. _chapter-aerodromes:
----------
......
......@@ -234,14 +234,6 @@ BUILDING_NUMBER_LEVELS_* Dict . A dictionar
settlement type is not ``centre`` or ``block``, then specific parameters
are used for apartments, industrial/warehouse and others.
BUILDING_LEVEL_HEIGHT_URBAN Number 3.5 The height per level. This value should not be changed unless special textures
are used. For settlement types ``centre``, ``block`` and ``dense``.
If a building is of class ``commercial``, ``retail``, ``public`` or
``parking_house``, then this height is always used.
BUILDING_LEVEL_HEIGHT_RURAL Number 2.5 Ditto for settlement types ``periphery`` and ``rural``.
BUILDING_LEVEL_HEIGHT_INDUSTRIAL Number 4.0 Ditto for buildings of class ``industrial`` or ``warehouse``.
============================================= ======== ======= ==============================================================================
......
This diff is collapsed.
......@@ -29,6 +29,7 @@ import osm2city.utils.osmparser as op
from osm2city.utils import utilities, coordinates, stg_io2
from osm2city.owbb import plotting as p
from osm2city.types import osmstrings as s
from osm2city.types import enumerations as enu
from osm2city.utils import vec2d as v
OUR_MAGIC = "osm2city" # Used in e.g. stg files to mark edits by osm2city
......@@ -703,8 +704,8 @@ def _debug_building_list_lsme(coords_transform: coordinates.Transformation, file
width = building_lib.BUILDING_LIST_SMALL_MIN_SIDE * 3
depth = building_lib.BUILDING_LIST_SMALL_MIN_SIDE
levels = 2
body_height = parameters.BUILDING_LEVEL_HEIGHT_RURAL * levels
roof_height = parameters.BUILDING_LEVEL_HEIGHT_RURAL
body_height = enu.BUILDING_LEVEL_HEIGHT_RURAL * levels
roof_height = enu.BUILDING_LEVEL_HEIGHT_RURAL
roof_shape = 1
wall_tex_idx = 0
roof_tex_idx = 0
......@@ -821,7 +822,7 @@ def _debug_building_list_lsme(coords_transform: coordinates.Transformation, file
roof_shape = 0
roof_height = 0
levels = building_lib.BUILDING_LIST_SMALL_MAX_LEVELS
body_height = parameters.BUILDING_LEVEL_HEIGHT_URBAN * levels
body_height = enu.BUILDING_LEVEL_HEIGHT_URBAN * levels
line += ' {:.1f} {:.1f} {:.1f} {:.1f} {} {} {} {} {}'.format(width, depth, body_height,
roof_height, roof_shape,
roof_orientation, levels,
......@@ -844,7 +845,7 @@ def _debug_building_list_lsme(coords_transform: coordinates.Transformation, file
anchor = coords_transform.to_local((8.3061, 47.0918))
list_type = building_lib.BuildingListType.medium
line = '{:.1f} {:.1f} {:.1f} {:.0f} {}'.format(-anchor[1], anchor[0], elev, street_angle, list_type.value)
roof_height = parameters.BUILDING_LEVEL_HEIGHT_URBAN
roof_height = enu.BUILDING_LEVEL_HEIGHT_URBAN
roof_shape = 2
roof_orientation = 0
line += ' {:.1f} {:.1f} {:.1f} {:.1f} {} {} {} {} {}'.format(width, depth, body_height,
......@@ -858,7 +859,7 @@ def _debug_building_list_lsme(coords_transform: coordinates.Transformation, file
list_type = building_lib.BuildingListType.medium
width = depth
levels = building_lib.BUILDING_LIST_MEDIUM_MAX_LEVELS
body_height = levels * parameters.BUILDING_LEVEL_HEIGHT_URBAN
body_height = levels * enu.BUILDING_LEVEL_HEIGHT_URBAN
roof_height = 0
roof_shape = 0
line = '{:.1f} {:.1f} {:.1f} {:.0f} {}'.format(-anchor[1], anchor[0], elev, street_angle, list_type.value)
......@@ -874,7 +875,7 @@ def _debug_building_list_lsme(coords_transform: coordinates.Transformation, file
width = building_lib.BUILDING_LIST_LARGE_MIN_SIDE
depth = 4 * width
levels = building_lib.BUILDING_LIST_LARGE_MAX_LEVELS
body_height = levels * parameters.BUILDING_LEVEL_HEIGHT_URBAN
body_height = levels * enu.BUILDING_LEVEL_HEIGHT_URBAN
line = '{:.1f} {:.1f} {:.1f} {:.0f} {}'.format(-anchor[1], anchor[0], elev, street_angle, list_type.value)
line += ' {:.1f} {:.1f} {:.1f} {:.1f} {} {} {} {} {}'.format(width, depth, body_height,
roof_height, roof_shape,
......
......@@ -905,7 +905,6 @@ def process(transformer: Transformation, airports: List[aptdat_io.Airport]) -> T
for building in osm_buildings:
building.calc_roof_hints(building_nodes_dict, transformer)
last_time = time_logging('Time used in seconds for simplifying and calculating roof hints', last_time)
# FIXME: simplify stuff
# update the geometry a final time based on node references before we loose it
for building in osm_buildings:
building.update_geometry_from_refs(building_nodes_dict, transformer)
......
......@@ -171,10 +171,6 @@ BUILDING_NUMBER_LEVELS_APARTMENTS = {2: 0.05, 3: 0.45, 4: 0.4, 5: 0.08, 6: 0.02}
BUILDING_NUMBER_LEVELS_INDUSTRIAL = {1: 0.3, 2: 0.6, 3: 0.1} # for both industrial and warehouse
BUILDING_NUMBER_LEVELS_OTHER = {1: 0.2, 2: 0.4, 3: 0.3, 4: 0.1} # e.g. commercial, public, retail
BUILDING_LEVEL_HEIGHT_URBAN = 3.5 # this value should not be changed unless special textures are used
BUILDING_LEVEL_HEIGHT_RURAL = 2.5 # ditto including periphery
BUILDING_LEVEL_HEIGHT_INDUSTRIAL = 4. # for industrial and warehouse
# make sure not visible buildings or building parts are excluded
BUILDING_UNDERGROUND_LOCATION = True
BUILDING_UNDERGROUND_INDOOR = True
......
......@@ -20,11 +20,19 @@ GAMBREL_HEIGHT_RATIO_LOWER_PART = 0.75
class RoofHint:
"""As set of hints for placing or constructing the roof.
Not all buildings have a RoofHint - and most often only one of the fields will be available.
See description in the 'how it works' section of the manual.
If a building has an inner node, then no more simplifications should be done.
Fields:
* ridge_orientation: the orientation in degrees of the ridge - for gabled roofs with 4 edges. Only set
if there are neighbours and therefore the ridge should be aligned.
* inner_node: in an L-shaped building or a 4 edge building with two neighbours around the corner:
the node which is at the inner side in teh ca. 90 deg corner
* inner_node: for L-shaped roofs due to neighbours or L-shaped building:
the node which is at the inner side in the ca. 90 deg corner as follows:
Tuple of tuple(lon, lat) in local coordinates.
The lon/lat instead of a node position is kept because due to geometry changes
the sequence of the outer ring could change. This way we can test.
"""
__slots__ = ('ridge_orientation', 'inner_node')
......@@ -96,6 +104,30 @@ def flat(ac_object: ac.Object, index_first_node_in_ac_obj: int, b, roof_mgr: Roo
ac_object.face(nodes_uv_list, mat_idx=roof_mat_idx)
def sanity_roof_height_complex(b, roof_type: str) -> float:
if b.roof_height:
return b.roof_height
else:
logging.warning("no roof_height in %s for building %i", roof_type, b.osm_id)
return enu.BUILDING_LEVEL_HEIGHT_RURAL
def separate_gable_with_corner(ac_object, b, roof_mat_idx: int, facade_mat_idx: int) -> None:
"""By convention counting of nodes starts at the inner node."""
t = b.roof_texture
roof_height = sanity_roof_height_complex(b, 'gable_with_corner')
# find the point closest to the RoofHint.inner_node
shortest_dist = 9999999
shortest_index = 0
num_nodes = len(b.pts_all) # pts_all works because there are no inner points in a complex roof
for index, pt in b.pts_all.enumerate():
dist = coord.calc_distance_local(pt.x, pt.y, b.roof_hint.inner_node.x, b.roof_hint.inner_node.x)
if dist < shortest_dist:
shortest_dist = dist
shortest_index = index
def separate_hipped(ac_object: ac.Object, b, roof_mat_idx: int) -> None:
return separate_gable(ac_object, b, roof_mat_idx, roof_mat_idx, inward_meters=2.)
......@@ -103,16 +135,12 @@ def separate_hipped(ac_object: ac.Object, b, roof_mat_idx: int) -> None:
def separate_gable(ac_object, b, roof_mat_idx: int, facade_mat_idx: int, inward_meters=0.) -> None:
"""Gabled or gambrel roof (or hipped if inward_meters > 0) with 4 nodes."""
t = b.roof_texture
if b.roof_height:
roof_height = b.roof_height
else:
my_type = 'separate_gable'
if inward_meters > 0:
my_type = 'separate_hipped'
logging.warning("no roof_height in %s for building %i", my_type, b.osm_id)
roof_height = 2.0
my_type = 'separate_gable'
if inward_meters > 0:
my_type = 'separate_hipped'
roof_height = sanity_roof_height_complex(b, my_type)
# get orientation if exits:
osm_roof_orientation_exists = False
if s.K_ROOF_ORIENTATION in b.tags:
......@@ -322,11 +350,7 @@ def separate_pyramidal(ac_object: ac.Object, b, roof_mat_idx: int) -> None:
shape = b.roof_shape
roof_texture = b.roof_texture
# -- get roof height
if b.roof_height:
roof_height = b.roof_height
else:
return
roof_height = sanity_roof_height_complex(b, 'pyramidal')
bottom = b.beginning_of_roof_above_sea_level
......
......@@ -270,3 +270,15 @@ class BuildingZoneType(IntEnum): # element names must match OSM values apart fr
btg_construction = 221
btg_industrial = 222
btg_port = 223
# ================================ CONSTANTS =========================================
# Should not be changed unless all dependencies have been thoroughly checked.
# The height per level. This value should not be changed unless special textures are used.
# For settlement types ``centre``, ``block`` and ``dense``.
# If a building is of class ``commercial``, ``retail``, ``public`` or
# ``parking_house``, then this height is always used.
BUILDING_LEVEL_HEIGHT_URBAN = 3.5
BUILDING_LEVEL_HEIGHT_RURAL = 2.5 # ditto including periphery and rural
BUILDING_LEVEL_HEIGHT_INDUSTRIAL = 4. # for industrial and warehouse
......@@ -228,6 +228,7 @@ def calc_point_on_line_local(x1: float, y1: float, x2: float, y2: float, factor:
def calc_angle_of_corner_local(prev_point_x: float, prev_point_y: float,
corner_point_x: float, corner_point_y,
next_point_x: float, next_point_y) -> float:
"""The angle seen from the corner looking at the prev_point and then the next point."""
first_angle = calc_angle_of_line_local(corner_point_x, corner_point_y, prev_point_x, prev_point_y)
second_angle = calc_angle_of_line_local(corner_point_x, corner_point_y, next_point_x, next_point_y)
final_angle = fabs(first_angle - second_angle)
......@@ -236,6 +237,24 @@ def calc_angle_of_corner_local(prev_point_x: float, prev_point_y: float,
return final_angle
def calc_delta_bearing(bearing1: float, bearing2: float) -> float:
"""Calculates the difference between two bearings. If positive with clock, if negative against the clock sense.
I.e. from bearing1 to bearing2 you need to turn delta with or against the clock."""
if bearing1 == 360.:
bearing1 = 0.
if bearing2 == 360.:
bearing2 = 0.
delta = bearing2 - bearing1
if delta > 180:
delta = delta - 360
elif delta < -180:
delta = 360 + delta
return delta
def calc_distance_local(x1, y1, x2, y2):
"""Returns the distance between two points based on local coordinates (x,y)."""
return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2))
......@@ -361,6 +380,39 @@ class TestCoordinates(unittest.TestCase):
self.assertAlmostEqual(45, calc_angle_of_corner_local(-1, 1, 0, 0, 0, 1), 2)
self.assertAlmostEqual(45, calc_angle_of_corner_local(-1, 1, 0, 0, -1, 0), 2)
def test_calc_delta_bearing(self):
bearing1 = 0.
bearing2 = 0.
self.assertEqual(0., calc_delta_bearing(bearing1, bearing2), 'No difference 0')
bearing1 = 0.
bearing2 = 360.
self.assertEqual(0., calc_delta_bearing(bearing1, bearing2), 'No difference 0/360')
bearing1 = 360.
bearing2 = 0.
self.assertEqual(0., calc_delta_bearing(bearing1, bearing2), 'No difference 360/0')
bearing1 = 20
bearing2 = 200
self.assertEqual(180., fabs(calc_delta_bearing(bearing1, bearing2)), '180 degrees')
bearing1 = 10
bearing2 = 20
self.assertEqual(10., calc_delta_bearing(bearing1, bearing2), '10 with clock')
bearing1 = 20
bearing2 = 10
self.assertEqual(-10., calc_delta_bearing(bearing1, bearing2), '10 against clock')
bearing1 = 350
bearing2 = 10
self.assertEqual(20., calc_delta_bearing(bearing1, bearing2), 'Through 0: 20 with clock')
bearing1 = 10
bearing2 = 350
self.assertEqual(-20., calc_delta_bearing(bearing1, bearing2), 'Through 0: 20 against clock')
def test_calc_point_on_line_local(self):
# straight up
x, y = calc_point_on_line_local(0, 1, 0, 2, 0.5)
......
......@@ -105,6 +105,10 @@ class Node(OSMElement):
return -1
@property
def lon_lat_tuple(self) -> Tuple[float, float]:
return self.lon, self.lat
def __str__(self):
return 'Node with osm_id: {}'.format(self.osm_id)
......
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