Commit c084e3d2 authored by Rick Gruber-Riemer's avatar Rick Gruber-Riemer

* Making sure generated buildings are linked to the zones

* Re-organize logging
parent d6fd5f5d
......@@ -15,6 +15,7 @@ import logging
import os
import parameters
import utils.logging as ulog
import utils.utilities
from utils.vec2d import Vec2d
from utils.stg_io2 import STGVerbType
......@@ -117,7 +118,7 @@ class ClusterContainer(object):
return the_cluster
def write_statistics_for_buildings(self, clusters_name: str) -> None:
if utils.utilities.log_level_debug_or_lower() and parameters.WRITE_CLUSTER_STATS:
if ulog.log_level_debug_or_lower() and parameters.WRITE_CLUSTER_STATS:
my_file = open(os.path.join(parameters.PREFIX, clusters_name + ".dat"), "w")
for j in range(self.max_grid.iy):
for i in range(self.max_grid.ix):
......
......@@ -13,7 +13,7 @@ import numpy as np
import parameters
import pySkeleton.polygon as polygon
import utils.utilities
import utils.logging as ulog
from utils import utilities
from utils.vec2d import Vec2d
......@@ -49,7 +49,7 @@ def myskel(out, b, stats: utilities.Stats, offset_xy=Vec2d(0, 0), offset_z=0., h
logging.debug("ERROR: while creating 3d roof (OSM_ID %s, %s)" % (b.osm_id, reason))
stats.roof_errors += 1
gp = parameters.get_repl_prefix() + '_roof-error-%04i' % stats.roof_errors
if utils.utilities.log_level_debug_or_lower():
if ulog.log_level_debug_or_lower():
_write_one_gp(b.pts_outer, b.osm_id, gp)
return False
......
......@@ -173,7 +173,7 @@ def _assign_city_blocks(building_zone: m.BuildingZone, highways_dict: Dict[int,
if intersecting_highways:
buffers = list()
for highway in intersecting_highways:
buffers.append(highway.geometry.buffer(2, cap_style=CAP_STYLE.square,
buffers.append(highway.geometry.buffer(parameters.OWBB_CITY_BLOCK_HIGHWAY_BUFFER, cap_style=CAP_STYLE.square,
join_style=JOIN_STYLE.bevel))
geometry_difference = building_zone.geometry.difference(unary_union(buffers))
if isinstance(geometry_difference, Polygon) and geometry_difference.is_valid and \
......
......@@ -289,7 +289,7 @@ def parse_building_tags_for_type(tags_dict: KeyValueDict) -> Union[None, Buildin
def get_building_class(building: building_lib.Building) -> BuildingClass:
type_ = parse_building_tags_for_type(building.tags)
if type_ in [BuildingType.apartments, BuildingType.house, BuildingType.detached,
BuildingType.residential, BuildingType.dormitory, BuildingType.terrace]:
BuildingType.residential, BuildingType.dormitory, BuildingType.terrace]:
return BuildingClass.residential
elif type_ in [BuildingType.bungalow, BuildingType.static_caravan, BuildingType.cabin, BuildingType.hut]:
return BuildingClass.residential_small
......@@ -389,10 +389,17 @@ class BuildingZone(OSMFeatureArea):
def get_osm_value(self) -> str:
return self.type_.name
def commit_temp_gen_buildings(self, temp_buildings) -> None: # temp_buildings: TempGenBuildings circular
def commit_temp_gen_buildings(self, temp_buildings, highway, is_reverse) -> None:
"""Commits a set of generated buildings to be definitively be part of a BuildingZone"""
self.linked_blocked_areas.extend(temp_buildings.generated_blocked_areas)
self.generated_buildings.extend(temp_buildings.generated_buildings)
for building in temp_buildings.generated_buildings:
self.generated_buildings.append(building)
building.zone = self
if is_reverse and highway.reversed_city_block:
highway.reversed_city_block.relate_building(building)
elif not is_reverse:
if highway.along_city_block:
highway.along_city_block.relate_building(building)
def relate_building(self, building: building_lib.Building) -> None:
"""Link the building to this zone and link this zone to the building."""
......@@ -411,6 +418,34 @@ class BuildingZone(OSMFeatureArea):
city_block.geometry):
city_block.relate_building(osm_building)
def link_city_blocks_to_highways(self) -> None:
"""Tries to link city blocks to highways for building generation.
Using the middle point of the highway and just the overall angle should give correct result in most of the
cases. And if not then no big harm, as city blocks close to each other will have similar types."""
linked = 0
for highway in self.linked_genways:
coords = list(highway.geometry.coords)
angle = co.calc_angle_of_line_local(coords[0][0], coords[0][1], coords[-1][0], coords[-1][1])
middle_point = highway.geometry.interpolate(highway.geometry.length / 2)
my_point = Point(co.calc_point_angle_away(middle_point.x, middle_point.y,
2 * parameters.OWBB_CITY_BLOCK_HIGHWAY_BUFFER, angle + 90))
for city_block in self.linked_city_blocks:
if my_point.within(city_block.geometry):
highway.along_city_block = city_block
linked += 1
break
my_point = Point(co.calc_point_angle_away(middle_point.x, middle_point.y,
2 * parameters.OWBB_CITY_BLOCK_HIGHWAY_BUFFER, angle - 90))
for city_block in self.linked_city_blocks:
if my_point.within(city_block.geometry):
highway.reversed_city_block = city_block
linked += 1
break
logging.debug('Linked around &i out of %i high ways to city blocks in building zone %i', linked,
int(linked/len(self.linked_genways)), self.osm_id)
class GeneratedBuildingZone(BuildingZone):
"""A fake OSM Land-use for buildings based on heuristics"""
......@@ -712,6 +747,8 @@ class Highway(OSMFeatureLinearWithTunnel):
self.is_oneway = self._parse_tags_oneway(tags_dict)
self.lanes = self._parse_tags_lanes(tags_dict)
self.refs = refs
self.along_city_block = None # for building generation city block along line on right side
self.reversed_city_block = None # ditto reversed line
@classmethod
def create_from_scratch(cls, pseudo_id: int, existing_highway: 'Highway', linear: LineString) -> 'Highway':
......
......@@ -168,14 +168,14 @@ def _generate_extra_buildings_residential(building_zone: m.BuildingZone, highway
_generate_buildings_along_highway(building_zone, highway, row_houses_list, is_reverse, temp_buildings)
if 0 < temp_buildings.validate_uninterrupted_sequence(parameters.OWBB_RESIDENTIAL_HIGHWAY_MIN_GEN_SHARE,
parameters.OWBB_RESIDENTIAL_TERRACE_MIN_NUMBER):
building_zone.commit_temp_gen_buildings(temp_buildings)
building_zone.commit_temp_gen_buildings(temp_buildings, highway, is_reverse)
return # we do not want to spoil row houses with other houses to fill up
# start from scratch - either because terrace not chosen or not successfully validated
temp_buildings = m.TempGenBuildings(bounding_box)
_generate_buildings_along_highway(building_zone, highway, detached_houses_list, is_reverse, temp_buildings)
if temp_buildings.validate_min_share_generated(parameters.OWBB_RESIDENTIAL_HIGHWAY_MIN_GEN_SHARE):
building_zone.commit_temp_gen_buildings(temp_buildings)
building_zone.commit_temp_gen_buildings(temp_buildings, highway, is_reverse)
def _generate_extra_buildings_industrial(building_zone: m.BuildingZone, highway: m.Highway,
......@@ -188,7 +188,7 @@ def _generate_extra_buildings_industrial(building_zone: m.BuildingZone, highway:
shared_models_list = shared_models_library.industrial_buildings_small
_generate_buildings_along_highway(building_zone, highway, shared_models_list, is_reverse, temp_buildings)
building_zone.commit_temp_gen_buildings(temp_buildings)
building_zone.commit_temp_gen_buildings(temp_buildings, highway, is_reverse)
def _generate_buildings_along_highway(building_zone: m.BuildingZone, highway: m.Highway,
......@@ -235,6 +235,7 @@ def _generate_buildings_along_highway(building_zone: m.BuildingZone, highway: m.
my_gen_building.set_location(point_on_line, angle, area_polygon, buffer_polygon)
temp_buildings.add_generated(my_gen_building, m.BlockedArea(m.BlockedAreaType.gen_building,
area_polygon))
# prepare a new building, which might get added in the next loop
my_gen_building = m.GenBuilding(op.get_next_pseudo_osm_id(op.OSMFeatureType.building_owbb),
random.choice(shared_models_list), highway.get_width())
......@@ -354,6 +355,7 @@ def process(transformer: co.Transformation, building_zones: List[m.BuildingZone]
for b_zone in used_zones:
_prepare_building_zone_for_building_generation(b_zone, open_spaces_dict,
waterways_dict, railways_dict, highways_dict)
b_zone.link_city_blocks_to_highways()
last_time = time_logging("Time used in seconds for preparing building zones for building generation", last_time)
building_zones = list() # will be filled again with used_zones out of the parallel processes
......
......@@ -18,11 +18,13 @@ import re
import sys
import traceback
import types
import typing
import unittest
import textures.road
from utils import vec2d as v
import utils.vec2d as v
import utils.calc_tile as ct
import utils.utilities as utils
import utils.logging as ulog
# default_args_start # DO NOT MODIFY THIS LINE
# -*- coding: utf-8 -*-
......@@ -152,11 +154,11 @@ BUILDING_CITY_LEVEL_HEIGHT_LOW = 3.1
BUILDING_CITY_LEVEL_HEIGHT_MODE = 3.3
BUILDING_CITY_LEVEL_HEIGHT_HIGH = 3.6
# FIXME: above should be removed after FLAG_2018_3
BUILDING_NUMBER_LEVELS_CENTRE = [5.0, 5.4, 6.0]
BUILDING_NUMBER_LEVELS_BLOCK = [4.5, 5.0, 5.1]
BUILDING_NUMBER_LEVELS_DENSE = [3.0, 3.6, 4.0]
BUILDING_NUMBER_LEVELS_PERIPHERY = [1.0, 1.9, 3.0]
BUILDING_NUMBER_LEVELS_RURAL = [1.0, 1.5, 3.0]
BUILDING_NUMBER_LEVELS_CENTRE = {4: 0.2, 5: 0.7, 6: 0.1}
BUILDING_NUMBER_LEVELS_BLOCK = {4: 0.4, 5: 0.6}
BUILDING_NUMBER_LEVELS_DENSE = {3: 0.2, 4: 0.6, 5: 0.15, 6: 0.05}
BUILDING_NUMBER_LEVELS_PERIPHERY = {1: 0.3, 2: 0.65, 3: 0.05}
BUILDING_NUMBER_LEVELS_RURAL = {1: 0.3, 2: 0.7}
BUILDING_LEVEL_HEIGHT_URBAN = 3.5 # this value should not be changed unless special textures are used
BUILDING_LEVEL_HEIGHT_RURAL = 2.5 # ditto
......@@ -321,6 +323,7 @@ OWBB_GENERATE_BUILDINGS = False
OWBB_STEP_DISTANCE = 2 # in meters
OWBB_MIN_STREET_LENGTH = 10 # in meters
OWBB_MIN_CITY_BLOCK_AREA = 200 # square meters
OWBB_CITY_BLOCK_HIGHWAY_BUFFER = 3 # in metres buffer around highways to find city blocks
OWBB_RESIDENTIAL_HIGHWAY_MIN_GEN_SHARE = 0.3
OWBB_INDUSTRIAL_HIGHWAY_MIN_GEN_SHARE = 0.3 # FIXME: not yet used
......@@ -399,11 +402,29 @@ def get_clipping_border():
return rect
def _check_ratio_dict_parameter(ratio_dict: typing.Optional[typing.Dict], name: str) -> None:
if ratio_dict is None:
raise ValueError('Parameter {} must not be None'.format(name))
if not isinstance(ratio_dict, dict):
raise ValueError('Parameter {} must be a dict'.format(name))
if len(ratio_dict) == 0:
raise ValueError('Parameter %s must not be an empty dict'.format(name))
total = 0.
for key, ratio in ratio_dict.items():
if not isinstance(key, int):
raise ValueError('key {} in parameter {} must be an int'.format(str(key), name))
if not isinstance(ratio, float):
raise ValueError('ratio {} for key {} in param {} must be a float'.format(str(ratio), str(key), name))
total += ratio
if abs(total - 1) > 0.001:
raise ValueError('The total of all ratios in param {} must be 1'.format(name))
def show():
"""
Prints all parameters as key = value if log level is INFO or lower
"""
if utils.log_level_info_or_lower():
if ulog.log_level_info_or_lower():
print('--- Using the following parameters: ---')
my_globals = globals()
for k in sorted(my_globals.keys()):
......@@ -454,6 +475,18 @@ def read_from_file(filename):
else:
PATH_TO_SCENERY_OPT = [PATH_TO_SCENERY_OPT]
# check the ratios in specific parameters
global BUILDING_NUMBER_LEVELS_CENTRE
global BUILDING_NUMBER_LEVELS_BLOCK
global BUILDING_NUMBER_LEVELS_DENSE
global BUILDING_NUMBER_LEVELS_PERIPHERY
global BUILDING_NUMBER_LEVELS_RURAL
_check_ratio_dict_parameter(BUILDING_NUMBER_LEVELS_CENTRE, 'BUILDING_NUMBER_LEVELS_CENTRE')
_check_ratio_dict_parameter(BUILDING_NUMBER_LEVELS_BLOCK, 'BUILDING_NUMBER_LEVELS_BLOCK')
_check_ratio_dict_parameter(BUILDING_NUMBER_LEVELS_DENSE, 'BUILDING_NUMBER_LEVELS_DENSE')
_check_ratio_dict_parameter(BUILDING_NUMBER_LEVELS_PERIPHERY, 'BUILDING_NUMBER_LEVELS_PERIPHERY')
_check_ratio_dict_parameter(BUILDING_NUMBER_LEVELS_RURAL, 'BUILDING_NUMBER_LEVELS_RURAL')
def show_default():
"""show default parameters by printing all params defined above between
......@@ -505,3 +538,30 @@ if __name__ == "__main__":
show()
if args.show_default:
show_default()
# ================ UNITTESTS =======================
class TestParameters(unittest.TestCase):
def test_check_ratio_dict_parameter(self):
my_ratio_dict = None
with self.assertRaises(ValueError):
_check_ratio_dict_parameter(my_ratio_dict, 'my_ratio_dict')
my_ratio_dict = list()
with self.assertRaises(ValueError):
_check_ratio_dict_parameter(my_ratio_dict, 'my_ratio_dict')
my_ratio_dict = dict()
with self.assertRaises(ValueError):
_check_ratio_dict_parameter(my_ratio_dict, 'my_ratio_dict')
my_ratio_dict = {'A': 'B'}
with self.assertRaises(ValueError):
_check_ratio_dict_parameter(my_ratio_dict, 'my_ratio_dict')
my_ratio_dict = {1: 'b'}
with self.assertRaises(ValueError):
_check_ratio_dict_parameter(my_ratio_dict, 'my_ratio_dict')
my_ratio_dict = {1: 0.01, 2: 1.}
with self.assertRaises(ValueError):
_check_ratio_dict_parameter(my_ratio_dict, 'my_ratio_dict')
my_ratio_dict = {1: 0.01, 2: 0.99}
self.assertEqual(2, len(my_ratio_dict), 'Length correct and no exception')
......@@ -314,7 +314,7 @@ def calc_point_angle_away(x: float, y: float, added_distance: float, angle: floa
return new_x, new_y
def calc_point_on_line_local(x1: float, y1: float, x2:float, y2:float, factor: float) -> Tuple[float, float]:
def calc_point_on_line_local(x1: float, y1: float, x2: float, y2: float, factor: float) -> Tuple[float, float]:
"""Returns the x,y coordinates of a point along the line defined by the input coordinates factor away from first.
"""
angle = calc_angle_of_line_local(x1, y1, x2, y2)
......
import logging
def log_level_info_or_lower():
return logging.getLogger().level <= logging.INFO
def log_level_debug_or_lower():
return logging.getLogger().level <= logging.DEBUG
......@@ -23,6 +23,7 @@ import shapely.geometry as shg
import parameters
import utils.coordinates as co
import utils.logging as ulog
import utils.osmparser as op
import utils.vec2d as ve
......@@ -110,14 +111,6 @@ def replace_with_os_separator(path: str) -> str:
return my_string
def log_level_info_or_lower():
return logging.getLogger().level <= logging.INFO
def log_level_debug_or_lower():
return logging.getLogger().level <= logging.DEBUG
def match_local_coords_with_global_nodes(local_list: List[Tuple[float, float]], ref_list: List[int],
all_nodes: Dict[int, op.Node],
coords_transform: co.Transformation, osm_id: int,
......@@ -276,7 +269,7 @@ class Stats(object):
logging.info(" %5g m^2 %5i |%s" % (self.area_levels[i], self.area_above[i],
"#" * int(56. * self.area_above[i] / max_area_above)))
if log_level_debug_or_lower():
if ulog.log_level_debug_or_lower():
for name in sorted(textures_used):
logging.info("%s" % name)
......@@ -475,7 +468,7 @@ class FGElev(object):
def progress(i, max_i):
"""progress indicator"""
if sys.stdout.isatty() and log_level_info_or_lower():
if sys.stdout.isatty() and ulog.log_level_info_or_lower():
try:
if i % (max_i / 100) > 0:
return
......
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