Commit 39d2c9f3 authored by Rick Gruber-Riemer's avatar Rick Gruber-Riemer

* Adding try/expect in build_tiles_db to make exceptions explicit, but still continue

* Make sure roads.py also uses BUILD_MESH* STG verbs (railways and large highways go to rough)
* Re-use read stg-entries across processes
* Add code to detect bridges as static objects
* Removed old OVERLAP_CHECK parameter and related code
* Make sure that sagging of cables in pylons does not tough ground when there are large distances.
* Small reduction of logging.info to logging.debug
parent 46106c57
......@@ -17,6 +17,7 @@ import roads
import utils.aptdat_io as aptdat_io
import utils.calc_tile as calc_tile
import utils.coordinates as coordinates
import utils.stg_io2
from utils.utilities import BoundaryError, FGElev, parse_boundary
......@@ -58,7 +59,7 @@ def _parse_exec_for_procedure(exec_argument: str) -> Procedures:
def process_scenery_tile(scenery_tile: SceneryTile, params_file_name: str, log_level: str,
exec_argument: Procedures, airports: List[aptdat_io.Airport]) -> None:
exec_argument: Procedures, my_airports: List[aptdat_io.Airport]) -> None:
parameters.read_from_file(params_file_name)
parameters.set_loglevel(log_level)
parameters.USE_DATABASE = True # just to be sure
......@@ -71,37 +72,40 @@ def process_scenery_tile(scenery_tile: SceneryTile, params_file_name: str, log_l
logging.info("Processing tile {} in prefix {} with process id = {}".format(scenery_tile.tile_index,
parameters.PREFIX,
os.getpid()))
# prepare shared resources
the_coords_transform = coordinates.Transformation(parameters.get_center_global())
my_fg_elev = FGElev(the_coords_transform)
# cannot be read once for all
my_blocked_areas = None
if exec_argument in (Procedures.all, Procedures.buildings, Procedures.roads):
my_blocked_areas = aptdat_io.get_apt_dat_blocked_areas_from_airports(the_coords_transform, airports)
# run programs
if exec_argument is Procedures.all:
buildings.process(the_coords_transform, my_fg_elev, my_blocked_areas)
roads.process(the_coords_transform, my_fg_elev, my_blocked_areas)
pylons.process(the_coords_transform, my_fg_elev)
platforms.process(the_coords_transform, my_fg_elev)
# piers.process(the_coords_transform, my_fg_elev)
elif exec_argument is Procedures.buildings:
buildings.process(the_coords_transform, my_fg_elev, my_blocked_areas)
elif exec_argument is Procedures.roads:
roads.process(the_coords_transform, my_fg_elev, my_blocked_areas)
elif exec_argument is Procedures.pylons:
pylons.process(the_coords_transform, my_fg_elev)
elif exec_argument is Procedures.platforms:
platforms.process(the_coords_transform, my_fg_elev)
elif exec_argument is Procedures.piers:
# piers.process(the_coords_transform, my_fg_elev)
pass
# clean-up
my_fg_elev.close()
try:
# prepare shared resources
the_coords_transform = coordinates.Transformation(parameters.get_center_global())
my_fg_elev = FGElev(the_coords_transform)
my_stg_entries = utils.stg_io2.read_stg_entries_in_boundary(True, the_coords_transform)
# cannot be read once for all
my_blocked_areas = None
if exec_argument in (Procedures.all, Procedures.buildings, Procedures.roads):
my_blocked_areas = aptdat_io.get_apt_dat_blocked_areas_from_airports(the_coords_transform, my_airports)
# run programs
if exec_argument is Procedures.all:
buildings.process(the_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries)
roads.process(the_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries)
pylons.process(the_coords_transform, my_fg_elev, my_stg_entries)
platforms.process(the_coords_transform, my_fg_elev)
# piers.process(the_coords_transform, my_fg_elev)
elif exec_argument is Procedures.buildings:
buildings.process(the_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries)
elif exec_argument is Procedures.roads:
roads.process(the_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries)
elif exec_argument is Procedures.pylons:
pylons.process(the_coords_transform, my_fg_elev, my_stg_entries)
elif exec_argument is Procedures.platforms:
platforms.process(the_coords_transform, my_fg_elev)
elif exec_argument is Procedures.piers:
# piers.process(the_coords_transform, my_fg_elev)
pass
# clean-up
my_fg_elev.close()
except Exception:
logging.exception('Exception occured while processing tile {}.'.format(scenery_tile.tile_index))
logging.info("******* Finished tile {} *******".format(scenery_tile.tile_index))
......
This diff is collapsed.
......@@ -70,7 +70,7 @@ import prepare_textures
import textures.texture as tex
import utils.stg_io2
import utils.vec2d as v
from utils import aptdat_io, osmparser, calc_tile, coordinates, stg_io2, utilities
from utils import aptdat_io, osmparser, coordinates, stg_io2, utilities
OUR_MAGIC = "osm2city" # Used in e.g. stg files to mark edits by osm2city
SCENERY_TYPE = "Buildings"
......@@ -200,7 +200,7 @@ def _process_osm_relation(rel_nodes_dict: Dict[int, osmparser.Node], rel_ways_di
my_buildings.append(a_building)
if not outer_multipolygons and not outer_ways:
logging.info("Skipping relation %i: no outer way." % relation.osm_id)
logging.debug("Skipping relation %i: no outer way." % relation.osm_id)
additional_buildings = len(my_buildings) - number_of_buildings_before
logging.info("Added {} buildings based on relations.".format(additional_buildings))
......@@ -244,7 +244,7 @@ def _make_building_from_way(nodes_dict: Dict[int, osmparser.Node], all_tags: Dic
if 'name' in all_tags:
name = all_tags['name']
if name in parameters.SKIP_LIST:
logging.info("SKIPPING " + name)
logging.debug("SKIPPING " + name)
return None
if 'height' in all_tags:
height = osmparser.parse_length(all_tags['height'])
......@@ -361,7 +361,7 @@ def _write_xml(path: str, file_name: str, the_buildings: List[building_lib.Build
def process(coords_transform: coordinates.Transformation, fg_elev: utilities.FGElev,
blocked_areas: List[shg.Polygon]) -> None:
blocked_areas: List[shg.Polygon], stg_entries: List[utils.stg_io2.STGEntry]) -> None:
random.seed(42)
stats = utilities.Stats()
......@@ -410,29 +410,8 @@ def process(coords_transform: coordinates.Transformation, fg_elev: utilities.FGE
if blocked_areas:
the_buildings = building_lib.overlap_check_blocked_areas(the_buildings, blocked_areas)
if parameters.OVERLAP_CHECK:
# -- read static/shared objects in our area from .stg(s)
# FG tiles are assumed to be much larger than our clusters.
# Loop all clusters, find relevant tile by checking tile_index at center of each cluster.
# Then read objects from .stg.
stgs = []
static_objects = []
for cl in clusters_building_mesh_detailed:
center_global = coords_transform.toGlobal(cl.center)
path = calc_tile.construct_path_to_stg(parameters.PATH_TO_SCENERY, "Objects", center_global)
stg_file_name = calc_tile.construct_stg_file_name(center_global)
if stg_file_name not in stgs:
stgs.append(stg_file_name)
static_objects.extend(building_lib.read_buildings_from_stg_entries(path, stg_file_name, OUR_MAGIC,
coords_transform))
logging.info("read %i objects from %i tiles", len(static_objects), len(stgs))
else:
static_objects = None
if parameters.OVERLAP_CHECK_CONVEX_HULL: # needs to be before building_lib.analyse to catch more at first hit
the_buildings = building_lib.overlap_check_convex_hull(the_buildings, coords_transform, stats)
the_buildings = building_lib.overlap_check_convex_hull(the_buildings, stg_entries, stats)
# - analyze buildings
# - calculate area
......@@ -446,7 +425,7 @@ def process(coords_transform: coordinates.Transformation, fg_elev: utilities.FGE
prepare_textures.init(stats, False)
the_buildings = building_lib.analyse(the_buildings, static_objects, fg_elev,
the_buildings = building_lib.analyse(the_buildings, fg_elev,
prepare_textures.facades, prepare_textures.roofs, stats)
building_lib.decide_lod(the_buildings, stats)
......@@ -546,7 +525,9 @@ if __name__ == "__main__":
parameters.BOUNDARY_WEST, parameters.BOUNDARY_SOUTH,
parameters.BOUNDARY_EAST, parameters.BOUNDARY_NORTH)
process(my_coords_transform, my_fg_elev, my_blocked_areas)
my_stg_entries = utils.stg_io2.read_stg_entries_in_boundary(True, my_coords_transform)
process(my_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries)
my_fg_elev.close()
......
......@@ -160,9 +160,11 @@ class LinearObject(object):
self.segment_len[i] = (dy*dy + dx*dx)**0.5
if self.segment_len[i] == 0:
logging.error("osm id: %i contains a segment with zero len", self.osm_id)
self.normals[i] = np.array((-dy, dx)) / 0.00000001
else:
self.normals[i] = np.array((-dy, dx)) / self.segment_len[i]
cumulated_distance += self.segment_len[i]
self.dist[i+1] = cumulated_distance
self.normals[i] = np.array((-dy, dx))/self.segment_len[i]
self.vectors[i] = vector
#assert abs(self.normals[i].magnitude() - 1.) < 0.00001
......
......@@ -76,6 +76,8 @@ FG_ELEV = '"D:/Program Files/FlightGear/bin/Win64/fgelev.exe"'
FG_ELEV_CACHE = True # saves the elevation probing results to a file, so next rerun is faster (but uses disk space!)
PROBE_FOR_WATER = False # only possible with FGElev version after 9th of November 2016 / FG 2016.4.1
TILE_SIZE = 2000 # -- tile size in meters for clustering of buildings, roads, ...
USE_EXTERNAL_MODELS = False
WRITE_CLUSTER_STATS = False
......@@ -92,12 +94,8 @@ OVERLAP_CHECK_CH_BUFFER_SHARED = 0.0
# -- Check for overlap with static and shared models. The scenery folder must contain an "Objects" folder
OVERLAP_CHECK = True
OVERLAP_CHECK_CONSIDER_SHARED = True
# -- Additionally check if one of the near nodes is actually inside the building
OVERLAP_CHECK_INSIDE = False
OVERLAP_RADIUS = 5
BUILDING_REMOVE_WITH_PARTS = False
TILE_SIZE = 2000 # -- tile size in meters for clustering of buildings
# when a static bridge model intersect with a way, how much must at least be left so the way is kept after intersection
OVERLAP_CHECK_BRIDGE_MIN_REMAINING = 10
# -- skip buildings based on their OSM name tag or OSM ID, in case there's already
# a static model for these, and the overlap check fails.
......@@ -244,6 +242,7 @@ BRIDGE_BODY_HEIGHT = 0.9 # height of bridge body
EMBANKMENT_TEXTURE = textures.road.EMBANKMENT_1 # Texture for the embankment
MIN_ABOVE_GROUND_LEVEL = 0.01 # how much a highway / railway is at least hovering above ground
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
......
......@@ -497,13 +497,11 @@ class WindFarm(object):
def _process_osm_wind_turbines(osm_nodes_dict: Dict[int, osmparser.Node], coords_transform: coordinates.Transformation,
fg_elev: utilities.FGElev) -> List[WindTurbine]:
fg_elev: utilities.FGElev, stg_entries: List[stg_io2.STGEntry]) -> List[WindTurbine]:
my_wind_turbines = list()
wind_farms = list()
# make sure no existing shared objects are duplicated. Do not care what shared obejct within distance
stg_entries = stg_io2.read_stg_entries_in_boundary()
# make sure no existing shared objects are duplicated. Do not care what shared object within distance
# find relevant / valid wind turbines
for key, node in osm_nodes_dict.items():
if "generator:source" in node.tags and node.tags["generator:source"] == "wind":
......@@ -1550,10 +1548,13 @@ def _optimize_catenary(half_distance_pylons: float, max_value: float, sag: float
Max variation is factor applied to sag.
"""
my_variation = sag * max_variation
for a in range(1, max_value):
value = a * math.cosh(float(half_distance_pylons)/a) - a # float() needed to make sure result is float
if (value >= (sag - my_variation)) and (value <= (sag + my_variation)):
return a, value
try:
for a in range(1, max_value):
value = a * math.cosh(float(half_distance_pylons)/a) - a # float() needed to make sure result is float
if (value >= (sag - my_variation)) and (value <= (sag + my_variation)):
return a, value
except OverflowError:
return -1, -1
return -1, -1
......@@ -1771,7 +1772,8 @@ def _fetch_osm_file_data() -> Tuple[Dict[int, osmparser.Node], Dict[int, osmpars
return handler.nodes_dict, handler.ways_dict
def process(coords_transform: coordinates.Transformation, fg_elev: utilities.FGElev) -> None:
def process(coords_transform: coordinates.Transformation, fg_elev: utilities.FGElev,
stg_entries: List[stg_io2.STGEntry]) -> None:
# Transform to real objects
logging.info("Transforming OSM data to Line and Pylon objects")
......@@ -1859,7 +1861,7 @@ def process(coords_transform: coordinates.Transformation, fg_elev: utilities.FGE
if parameters.C2P_PROCESS_WIND_TURBINES:
if parameters.USE_DATABASE:
osm_nodes_dict = osmparser.fetch_db_nodes_isolated(["generator:source=>wind"])
wind_turbines = _process_osm_wind_turbines(osm_nodes_dict, coords_transform, fg_elev)
wind_turbines = _process_osm_wind_turbines(osm_nodes_dict, coords_transform, fg_elev, stg_entries)
logging.info("Number of valid wind turbines found: {}".format(len(wind_turbines)))
# free some memory
......@@ -1919,8 +1921,9 @@ if __name__ == "__main__":
my_coords_transform = coordinates.Transformation(parameters.get_center_global())
my_fg_elev = utilities.FGElev(my_coords_transform)
my_stg_entries = stg_io2.read_stg_entries_in_boundary()
process(my_coords_transform, my_fg_elev)
process(my_coords_transform, my_fg_elev, my_stg_entries)
my_fg_elev.close()
......@@ -1993,7 +1996,7 @@ class TestOSMPylons(unittest.TestCase):
self.assertAlmostEqual(50, vertex.y, 2)
def test_catenary(self):
# Values taken form example 2 in http://www.mathdemos.org/mathdemos/catenary/catenary.html
# Values taken from example 2 in http://www.mathdemos.org/mathdemos/catenary/catenary.html
a, value = _optimize_catenary(170, 5000, 14, 0.001)
print(a, value)
self.assertAlmostEqual(1034/100, a/100, 2)
......
This diff is collapsed.
......@@ -202,8 +202,6 @@ def separate_skillion(ac_object: ac.Object, b):
for x in b.X:
ac_object.node(-x[1], b.ground_elev + b.height - b.roof_height, -x[0])
print(('SKILLION ', b.osm_id, ' ', b.tags))
# We don't want the hipped part to be greater than the height, which is 45 deg
# FLAT PART
......
......@@ -17,8 +17,12 @@ import os
import time
from typing import List, Optional
from shapely import affinity
import shapely.geometry as shg
import parameters
from utils import calc_tile
from utils.coordinates import Transformation
from utils.vec2d import Vec2d
from utils.utilities import assert_trailing_slash
......@@ -66,8 +70,8 @@ class STGFile(object):
stg = open(self.file_name, 'r')
lines = stg.readlines()
stg.close()
except IOError as reason:
logging.info("Error reading %s as it might not exist yet: %s", self.file_name, reason)
except IOError as ioe:
logging.info("Error reading %s as it might not exist yet: %s", self.file_name, ioe)
return
temp_other_list = []
......@@ -201,6 +205,7 @@ class STGEntry(object):
self.lat = lat
self.elev = elev
self.hdg = hdg
self.convex_hull = None # a Polygon object set by parse_stg_entries_for_convex_hull(...) in local coordinates
def _translate_verb_type(self, type_string: str) -> None:
"""Translates from a string in FGFS to an enumeration.
......@@ -290,8 +295,9 @@ def read_stg_entries(stg_path_and_name: str, consider_shared: bool = True, our_m
return entries
def read_stg_entries_in_boundary(consider_shared: bool = True) -> List[STGEntry]:
"""Returns a list of all STGEntries within the boundary according to parameters."""
def read_stg_entries_in_boundary(consider_shared: bool=True, my_coord_transform: Transformation=None) -> List[STGEntry]:
"""Returns a list of all STGEntries within the boundary according to parameters.
If my_cord_transform is set, then for each entry the convex hull is calculated in local coordinates."""
stg_entries = list()
stg_files = calc_tile.get_stg_files_in_boundary(parameters.BOUNDARY_WEST, parameters.BOUNDARY_SOUTH,
parameters.BOUNDARY_EAST, parameters.BOUNDARY_NORTH,
......@@ -305,9 +311,91 @@ def read_stg_entries_in_boundary(consider_shared: bool = True) -> List[STGEntry]
for filename in stg_files:
stg_entries.extend(read_stg_entries(filename, consider_shared))
if my_coord_transform is not None:
_parse_stg_entries_for_convex_hull(stg_entries, my_coord_transform)
return stg_entries
def _parse_stg_entries_for_convex_hull(stg_entries: List[STGEntry], my_coord_transformation: Transformation) -> None:
"""
Parses the ac-file content for a set of STGEntry objects and sets their boundary attribute
to be the convex hull of all points in the ac-file in the specified local coordinate system.
"""
for entry in stg_entries:
if entry.verb_type in [STGVerbType.object_static, STGVerbType.object_shared]:
try:
ac_filename = entry.obj_filename
if ac_filename.endswith(".xml"):
entry.overwrite_filename(_extract_ac_from_xml(entry.get_obj_path_and_name(),
entry.get_obj_path_and_name(
parameters.PATH_TO_SCENERY)))
boundary_polygon = _extract_boundary(entry.get_obj_path_and_name(),
entry.get_obj_path_and_name(parameters.PATH_TO_SCENERY))
rotated_polygon = affinity.rotate(boundary_polygon, entry.hdg - 90, (0, 0))
x_y_point = my_coord_transformation.toLocal((entry.lon, entry.lat))
translated_polygon = affinity.translate(rotated_polygon, x_y_point[0], x_y_point[1])
if entry.verb_type is STGVerbType.object_static and parameters.OVERLAP_CHECK_CH_BUFFER_STATIC > 0.01:
entry.convex_hull = translated_polygon.buffer(
parameters.OVERLAP_CHECK_CH_BUFFER_STATIC, shg.CAP_STYLE.square)
elif entry.verb_type is STGVerbType.object_shared and parameters.OVERLAP_CHECK_CH_BUFFER_SHARED > 0.01:
entry.convex_hull = translated_polygon.buffer(
parameters.OVERLAP_CHECK_CH_BUFFER_SHARED, shg.CAP_STYLE.square)
else:
entry.convex_hull = translated_polygon
except IOError as reason:
logging.warning("Ignoring unreadable stg_entry %s", reason)
def _extract_boundary(ac_filename: str, alternative_ac_filename: str=None) -> shg.Polygon:
"""Reads an ac-file and constructs a convex hull as a proxy to the real boundary.
No attempt is made to follow rotations and translations.
Returns a tuple (x_min, y_min, x_max, y_max) in meters.
An alternative path is tried, if the first path is not successful"""
numvert = 0
points = list()
try:
checked_filename = ac_filename
if not os.path.isfile(checked_filename) and alternative_ac_filename is not None:
checked_filename = alternative_ac_filename
with open(checked_filename, 'r') as my_file:
for my_line in my_file:
if 0 == my_line.find("numvert"):
numvert = int(my_line.split()[1])
elif numvert > 0:
vertex_values = my_line.split()
# minus factor in y-axis due to ac3d coordinate system. Switch of y_min and y_max for same reason
points.append((float(vertex_values[0]), -1 * float(vertex_values[2])))
numvert -= 1
except IOError as e:
raise e
hull_polygon = shg.MultiPoint(points).convex_hull
return hull_polygon
def _parse_ac_file_name(xml_string: str) -> str:
"""Finds the corresponding ac-file in an xml-file"""
try:
x1 = xml_string.index("<path>")
x2 = xml_string.index("</path>", x1)
except ValueError as e:
raise e
ac_file_name = (xml_string[x1+6:x2]).strip()
return ac_file_name
def _extract_ac_from_xml(xml_filename: str, alternative_xml_filename: str=None) -> str:
"""Reads the *.ac filename out of an xml-file"""
checked_filename = xml_filename
if not os.path.isfile(checked_filename) and alternative_xml_filename is not None:
checked_filename = alternative_xml_filename
with open(checked_filename, 'r') as f:
xml_data = f.read()
ac_filename = _parse_ac_file_name(xml_data)
return ac_filename
def _make_delimiter_string(our_magic: Optional[str], prefix: Optional[str], is_start: bool) -> str:
if our_magic is None:
magic = ""
......
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