Commit 12f1b877 authored by Rick Gruber-Riemer's avatar Rick Gruber-Riemer

Adding buffer for lighting to built-up areas.

Check each face on road whether inside or outside of built-up area (buffer).
parent b56ceef3
......@@ -2,16 +2,18 @@
import copy
import logging
import math
from typing import List, Dict
from typing import List, Dict, Optional
import matplotlib.pyplot as plt
import numpy as np
import parameters
import shapely.geometry as shg
import parameters
import textures.road
from utils.utilities import FGElev
from utils.vec2d import Vec2d
import utils.ac3d
from utils import osmparser
def probe_ground(fg_elev: FGElev, line_string):
......@@ -67,7 +69,7 @@ class LinearObject(object):
- if yes, use stored node indices
"""
def __init__(self, transform, osm_id: int, tags: Dict[str, str], refs: List[int], nodes_dict, width: float=9.,
tex=textures.road.EMBANKMENT_1, AGL: float=0.5, lit: bool=False):
tex=textures.road.EMBANKMENT_1, AGL: float=0.5):
self.width = width
self.AGL = AGL # drape distance above terrain
self.osm_id = osm_id
......@@ -84,7 +86,6 @@ class LinearObject(object):
except Warning as reason:
logging.warning("Warning in OSM_ID %i: %s", self.osm_id, reason)
self.tex = tex # determines which part of texture we use
self.lit = lit # whether the related road/railway is lit at night or not
def compute_offset(self, offset):
offset += 1.
......@@ -180,7 +181,8 @@ class LinearObject(object):
self.normals[-1] = self.normals[-2]
self.angle[-1] = self.angle[-2]
def write_nodes(self, obj: utils.ac3d.Object, line_string, z, cluster_elev, offset=None, join=False, is_left=False):
def write_nodes(self, obj: utils.ac3d.Object, line_string: shg.LineString, z, cluster_elev: float,
offset: Optional[Vec2d]=None, join: bool=False, is_left: bool=False) -> List[int]:
"""given a LineString and z, write nodes to .ac.
Return nodes_list
"""
......@@ -224,9 +226,11 @@ class LinearObject(object):
return nodes_list
def write_quads(self, obj: utils.ac3d.Object, left_nodes_list, right_nodes_list, tex_y0, tex_y1,
accept_lit: bool=False):
lighting_nodes_list: Optional[List[bool]]) -> None:
"""Write a series of quads bound by left and right.
Left/right are lists of node indices which will be used to form a series of quads.
lighting_nodes_list tells whether a given node is lit. If the list is None or empty, then none will be lit.
If either the start or the end node is lit, then the face will get a lit material.
"""
scale = 32. # length of texture in meters
# 2 lanes * 4m per lane = 128 px wide. 512px long = 32 m
......@@ -234,12 +238,13 @@ class LinearObject(object):
# Schmalstrich 0,15 m 0,12 m
# Breitstrich 0,30 m 0,25 m
# Leitlinie Schmalstrich, 3m innerorts, 6m BAB. Verhältnis Strich:Lücke = 1:2
mat_idx = utils.ac3d.MAT_IDX_UNLIT
if accept_lit and self.lit:
mat_idx = utils.ac3d.MAT_IDX_LIT
n_nodes = len(left_nodes_list)
assert(len(left_nodes_list) == len(right_nodes_list))
for i in range(n_nodes-1):
mat_idx = utils.ac3d.MAT_IDX_UNLIT
if lighting_nodes_list:
if lighting_nodes_list[i] or lighting_nodes_list[i+1]:
mat_idx = utils.ac3d.MAT_IDX_LIT
xl = self.dist[i]/scale
xr = self.dist[i+1]/scale
face = [(left_nodes_list[i], xl, tex_y0),
......@@ -437,7 +442,8 @@ class LinearObject(object):
e = z[i] - elev_offset
ac.add_label('<' + str(self.osm_id) + '> add %5.2f' % h_add[i], -(anchor[1] - offset.y), e+0.5, -(anchor[0] - offset.x), scale=1)
def write_to(self, obj: utils.ac3d.Object, fg_elev: FGElev, elev_offset, offset=None):
def write_to(self, obj: utils.ac3d.Object, fg_elev: FGElev, elev_offset, built_up_areas: List[shg.Polygon],
offset=None):
"""
assume we are a street: flat (or elevated) on terrain, left and right edges
#need adjacency info
......@@ -452,7 +458,8 @@ class LinearObject(object):
offset, join=True, is_left=True)
right_nodes_list = self.write_nodes(obj, self.edge[1], right_z, elev_offset,
offset, join=True, is_left=False)
self.write_quads(obj, left_nodes_list, right_nodes_list, self.tex[0], self.tex[1], True)
lighting_nodes_list = self.calc_lit_nodes(self.edge[0], built_up_areas)
self.write_quads(obj, left_nodes_list, right_nodes_list, self.tex[0], self.tex[1], lighting_nodes_list)
if h_add is not None:
# -- side walls of embankment
if h_add.max() > 0.1:
......@@ -462,9 +469,9 @@ class LinearObject(object):
left_ground_nodes = self.write_nodes(obj, self.edge[0], left_ground_z, elev_offset, offset=offset)
right_ground_nodes = self.write_nodes(obj, self.edge[1], right_ground_z, elev_offset, offset=offset)
self.write_quads(obj, left_ground_nodes, left_nodes_list, parameters.EMBANKMENT_TEXTURE[0],
parameters.EMBANKMENT_TEXTURE[1])
parameters.EMBANKMENT_TEXTURE[1], None)
self.write_quads(obj, right_nodes_list, right_ground_nodes, parameters.EMBANKMENT_TEXTURE[0],
parameters.EMBANKMENT_TEXTURE[1])
parameters.EMBANKMENT_TEXTURE[1], None)
return True
# options:
......@@ -564,3 +571,27 @@ class LinearObject(object):
logging.error("error in osm_id", self.osm_id)
return True
def calc_lit_nodes(self, line_string: shg.LineString, built_up_areas: List[shg.Polygon]) -> List[bool]:
"""Determines whether a given node on a line should be lit by looking at the tag and intersection.
Railways will always be non-lit.
The tag of the underlying road can contain key=lit.
Otherwise check whether the node is inside a built-up area.
"""
# exclude railway
if osmparser.has_railway_tag(self.tags):
return [False] * len(line_string.coords)
# check tag lit
if 'lit' in self.tags:
if self.tags['lit'] == 'yes':
return [True] * len(line_string.coords)
# check built-up area
lit_nodes = [False] * len(line_string.coords)
for i in range(len(lit_nodes)):
for area in built_up_areas:
if area.contains(shg.Point(line_string.coords[i][0], line_string.coords[i][1])):
lit_nodes[i] = True
break
return lit_nodes
......@@ -6,10 +6,13 @@ TODO: linear deck: limit slope
limit transverse slope of road
if h_add too high, continue/insert bridge
"""
from typing import List
import numpy as np
import scipy.interpolate
import shapely.geometry as shg
import linear
import parameters
import textures.road
......@@ -47,8 +50,8 @@ class DeckShapePoly(object):
class LinearBridge(linear.LinearObject):
def __init__(self, transform, fg_elev: FGElev, osm_id, tags, refs, nodes_dict, width=9,
tex=textures.road.EMBANKMENT_2, AGL=0.5, lit: bool=False):
super().__init__(transform, osm_id, tags, refs, nodes_dict, width, tex, AGL, lit)
tex=textures.road.EMBANKMENT_2, AGL=0.5):
super().__init__(transform, osm_id, tags, refs, nodes_dict, width, tex, AGL)
# -- prepare elevation spline
# probe elev at n_probes locations
n_probes = max(int(self.center.length / 5.), 3)
......@@ -195,7 +198,8 @@ class LinearBridge(linear.LinearObject):
return ofs + 2*self.pillar_nnodes, vert, nodes_list
def write_to(self, obj: utils.ac3d.Object, fg_elev: FGElev, elev_offset, offset=None):
def write_to(self, obj: utils.ac3d.Object, fg_elev: FGElev, elev_offset, built_up_areas: List[shg.Polygon],
offset=None):
"""
write
- deck
......@@ -227,16 +231,20 @@ class LinearBridge(linear.LinearObject):
right_bottom_nodes = self.write_nodes(obj, right_bottom_edge, z-parameters.BRIDGE_BODY_HEIGHT,
elev_offset, offset)
# -- top
self.write_quads(obj, left_top_nodes, right_top_nodes, self.tex[0], self.tex[1], True)
lighting_nodes_list = self.calc_lit_nodes(self.edge[0], built_up_areas)
self.write_quads(obj, left_top_nodes, right_top_nodes, self.tex[0], self.tex[1], lighting_nodes_list)
# -- right
self.write_quads(obj, right_top_nodes, right_bottom_nodes, textures.road.BRIDGE_1[1], textures.road.BRIDGE_1[0])
self.write_quads(obj, right_top_nodes, right_bottom_nodes, textures.road.BRIDGE_1[1], textures.road.BRIDGE_1[0],
None)
# -- left
self.write_quads(obj, left_bottom_nodes, left_top_nodes, textures.road.BRIDGE_1[0], textures.road.BRIDGE_1[1])
self.write_quads(obj, left_bottom_nodes, left_top_nodes, textures.road.BRIDGE_1[0], textures.road.BRIDGE_1[1],
None)
# -- bottom
self.write_quads(obj, right_bottom_nodes, left_bottom_nodes, textures.road.BOTTOM[0], textures.road.BOTTOM[1])
self.write_quads(obj, right_bottom_nodes, left_bottom_nodes, textures.road.BOTTOM[0], textures.road.BOTTOM[1],
None)
# -- end wall 1
the_node = self.edge[0].coords[0]
......
......@@ -246,6 +246,7 @@ MIN_ABOVE_GROUND_LEVEL = 0.01 # how much a highway / railway is at least hove
HIGHWAY_TYPE_MIN = 4 # The lower the number, the more ways are added. See roads.HighwayType
HIGHWAY_TYPE_MIN_ROUGH_LOD = 6 # the minimum type tobe added to the rough LOD clusters
POINTS_ON_LINE_DISTANCE_MAX = 1000 # the maximum distance between two points on a line. If longer, then new points are added
BUILT_UP_AREA_LIT_BUFFER = 20 # the buffer around built-up land-use areas to be used for lighting of streets
# =============================================================================
......
......@@ -127,17 +127,13 @@ def _is_replaced_bridge(way: osmparser.Way) -> bool:
return REPLACED_BRIDGE_KEY in way.tags
def _is_railway(way):
return "railway" in way.tags
def _is_processed_railway(way):
"""Check whether this is not only a railway, but one that gets processed.
E.g. funiculars are currently not processed.
Must be aligned with accepted railways in Roads._create_linear_objects.
"""
if not _is_railway(way):
if not osmparser.is_railway(way):
return False
if way.tags['railway'] in ['rail', 'disused', 'preserved', 'subway', 'narrow_gauge', 'tram', 'light_rail']:
return True
......@@ -162,7 +158,7 @@ def _is_highway(way):
def _compatible_ways(way1, way2):
"""Returns True if both ways are either a railway, a bridge or a highway."""
logging.debug("trying join %i %i", way1.osm_id, way2.osm_id)
if _is_railway(way1) != _is_railway(way2):
if osmparser.is_railway(way1) != osmparser.is_railway(way2):
logging.debug("Nope, either both or none must be railway")
return False
elif _is_bridge(way1) != _is_bridge(way2):
......@@ -177,7 +173,7 @@ def _compatible_ways(way1, way2):
if highway_type1 != highway_type2:
logging.debug("Nope, both must be of same highway type")
return False
elif _is_railway(way1) and _is_railway(way2):
elif osmparser.is_railway(way1) and osmparser.is_railway(way2):
if way1.tags['railway'] != way2.tags['railway']:
logging.debug("Nope, both must be of same railway type")
return False
......@@ -362,8 +358,7 @@ class Roads(object):
return "%i ways, %i roads, %i railways, %i bridges" % (len(self.ways_list), len(self.roads_list),
len(self.railway_list), len(self.bridges_list))
def process(self, blocked_areas: List[shg.Polygon], landuse_areas: List[shg.Polygon],
stg_entries: List[stg_io2.STGEntry],
def process(self, blocked_areas: List[shg.Polygon], stg_entries: List[stg_io2.STGEntry],
stats: utilities.Stats) -> None:
"""Processes the OSM data until data can be clusterized.
"""
......@@ -380,7 +375,7 @@ class Roads(object):
# -- no change in topology beyond create_linear_objects() !
logging.debug("before linear " + str(self))
self._create_linear_objects(landuse_areas)
self._create_linear_objects()
self._propagate_h_add()
logging.debug("after linear " + str(self))
......@@ -647,7 +642,7 @@ class Roads(object):
the_way.refs = my_new_refs
def _create_linear_objects(self, landuse_areas: List[shg.Polygon]) -> None:
def _create_linear_objects(self) -> None:
"""Creates the linear objects, which will be created as scenery objects.
Not processing parking for now (the_way.tags['amenity'] in ['parking'])
......@@ -659,7 +654,6 @@ class Roads(object):
priority = 0 # Used both to indicate whether it should be drawn and the priority when crossing
for the_way in self.ways_list:
lit = False
if _is_highway(the_way):
if "access" in the_way.tags:
if not (the_way.tags["access"] == 'no'):
......@@ -667,19 +661,9 @@ class Roads(object):
highway_type = highway_type_from_osm_tags(the_way.tags["highway"])
# in method Roads.store_way smaller highways already got removed
# lighting
if 'lit' in the_way.tags:
if the_way.tags['lit'] == 'yes':
lit = True
else: # OSM tag has precedence. Then check if within urban landuse
for landuse in landuse_areas:
my_line_string = self._line_string_from_way(the_way)
if my_line_string.intersects(landuse):
lit = True
priority, tex, width = get_highway_attributes(highway_type)
elif _is_railway(the_way):
elif osmparser.is_railway(the_way):
if the_way.tags['railway'] in ['rail', 'disused', 'preserved', 'subway']:
priority = 20
tex = textures.road.TRACK
......@@ -708,14 +692,14 @@ class Roads(object):
obj = linear_bridge.LinearBridge(self.transform, self.fg_elev, the_way.osm_id,
the_way.tags, the_way.refs, self.nodes_dict,
width=width, tex=tex,
AGL=above_ground_level, lit=lit)
AGL=above_ground_level)
self.bridges_list.append(obj)
else:
obj = linear.LinearObject(self.transform, the_way.osm_id,
the_way.tags, the_way.refs,
self.nodes_dict, width=width, tex=tex,
AGL=above_ground_level, lit=lit)
if _is_railway(the_way):
AGL=above_ground_level)
if osmparser.is_railway(the_way):
self.railway_list.append(obj)
else:
self.roads_list.append(obj)
......@@ -1041,7 +1025,7 @@ class Roads(object):
self.railways_clusters = ClusterContainer(lmin, lmax)
for the_object in self.bridges_list + self.roads_list + self.railway_list:
if _is_railway(the_object):
if osmparser.is_railway(the_object):
cluster_ref = self.railways_clusters.append(Vec2d(the_object.center.centroid.coords[0]),
the_object, stats)
else:
......@@ -1075,7 +1059,7 @@ def process_osm_ways(nodes_dict: Dict[int, osmparser.Node], ways_dict: Dict[int,
continue
elif highway_type.value < parameters.HIGHWAY_TYPE_MIN:
continue
elif _is_railway(way):
elif osmparser.is_railway(way):
if not _is_processed_railway(way):
continue
......@@ -1087,7 +1071,8 @@ def process_osm_ways(nodes_dict: Dict[int, osmparser.Node], ways_dict: Dict[int,
def _process_clusters(clusters, replacement_prefix, fg_elev: utilities.FGElev, stg_manager, stg_paths, is_railway,
coords_transform: coordinates.Transformation, stats: utilities.Stats, is_rough_LOD: bool):
coords_transform: coordinates.Transformation, built_up_areas: List[shg.Polygon],
stats: utilities.Stats, is_rough_LOD: bool):
for cl in clusters:
if len(cl.objects) < parameters.CLUSTER_MIN_OBJECTS:
continue # skip almost empty clusters
......@@ -1112,7 +1097,7 @@ def _process_clusters(clusters, replacement_prefix, fg_elev: utilities.FGElev, s
texture_string = 'Textures/osm2city/roads.png'
ac3d_obj = ac.new_object(file_name, texture_string, default_swap_uv=True, default_mat_idx=ac3d.MAT_IDX_UNLIT)
for rd in cl.objects:
rd.write_to(ac3d_obj, fg_elev, cluster_elev, offset=offset_local)
rd.write_to(ac3d_obj, fg_elev, cluster_elev, built_up_areas, offset=offset_local)
suffix = ".xml"
if is_railway or parameters.FLAG_2017_2:
......@@ -1241,13 +1226,12 @@ def process(coords_transform: coordinates.Transformation, fg_elev: utilities.FGE
landuse_result = osmparser.fetch_osm_db_data_ways_keys(['landuse'])
landuse_nodes_dict = landuse_result.nodes_dict
landuse_ways_dict = landuse_result.ways_dict
landuse_areas = landuse.process_osm_landuse_as_areas(landuse_nodes_dict, landuse_ways_dict, coords_transform)
built_up_areas = landuse.process_osm_landuse_as_areas(landuse_nodes_dict, landuse_ways_dict, coords_transform)
path_to_output = parameters.get_output_path()
logging.debug("before linear " + str(roads))
roads.process(blocked_areas, landuse_areas,
stg_entries, stats) # does the heavy lifting based on OSM data including clustering
roads.process(blocked_areas, stg_entries, stats) # does the heavy lifting based on OSM data including clustering
replacement_prefix = parameters.get_repl_prefix()
stg_manager = stg_io2.STGManager(path_to_output, stg_io2.SceneryType.roads, OUR_MAGIC, replacement_prefix)
......@@ -1256,11 +1240,11 @@ def process(coords_transform: coordinates.Transformation, fg_elev: utilities.FGE
stg_paths = set()
_process_clusters(roads.railways_clusters, replacement_prefix, fg_elev, stg_manager, stg_paths, True,
coords_transform, stats, True)
coords_transform, built_up_areas, stats, True)
_process_clusters(roads.roads_clusters, replacement_prefix, fg_elev, stg_manager, stg_paths, False,
coords_transform, stats, False)
coords_transform, built_up_areas, stats, False)
_process_clusters(roads.roads_rough_clusters, replacement_prefix, fg_elev, stg_manager, stg_paths, False,
coords_transform, stats, True)
coords_transform, built_up_areas, stats, True)
roads.debug_plot(show=True, plot_junctions=False, clusters=roads.roads_clusters)
......
......@@ -3,6 +3,8 @@ from typing import Dict, List
import shapely.geometry as shg
import parameters
class Landuse(object):
TYPE_COMMERCIAL = 10
......@@ -57,9 +59,9 @@ def process_osm_landuse_refs(nodes_dict, ways_dict, my_coord_transformator) -> D
def process_osm_landuse_as_areas(nodes_dict, ways_dict, my_coord_transformator) -> List[shg.Polygon]:
"""Just a wrapper around process_osm_landuse_refs(...) to get the list of polygons"""
"""Just a wrapper around process_osm_landuse_refs(...) to get the list of polygons."""
landuse_refs = process_osm_landuse_refs(nodes_dict, ways_dict, my_coord_transformator)
landuse_areas = list()
for key, value in landuse_refs.items():
landuse_areas.append(value.polygon)
landuse_areas.append(value.polygon.buffer(parameters.BUILT_UP_AREA_LIT_BUFFER))
return landuse_areas
......@@ -409,6 +409,14 @@ def is_parsable_int(str_int: str) -> bool:
return False
def is_railway(way: Way) -> bool:
return has_railway_tag(way.tags)
def has_railway_tag(tags: Dict[str, str]) -> bool:
return 'railway' in tags
def parse_hstore_tags(tags_string: str, osm_id: int) -> Dict[str, str]:
"""Parses the content of a string representation of a PostGIS hstore content for tags.
Returns a dict of key value pairs as string."""
......
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