Commit 5efc1cf0 authored by Rick Gruber-Riemer's avatar Rick Gruber-Riemer

First integration of owbb

parent 9405e5eb
......@@ -12,6 +12,7 @@ from typing import List
import unittest
import buildings
import owbb.landuse as ol
import piers
import platforms
import pylons
......@@ -20,7 +21,7 @@ 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, date_time_now, check_boundary, parse_boundary
from utils.utilities import BoundaryError, FGElev, date_time_now, check_boundary, parse_boundary, time_logging
class SceneryTile(object):
......@@ -52,6 +53,7 @@ class Procedures(IntEnum):
roads = 3
pylons = 4
details = 5
owbb = 6 # experimental
def _parse_exec_for_procedure(exec_argument: str) -> Procedures:
......@@ -116,35 +118,38 @@ def process_scenery_tile(scenery_tile: SceneryTile, params_file_name: str,
parameters.PREFIX,
os.getpid()))
# prepare shared resources
the_coords_transform = coordinates.Transformation(parameters.get_center_global())
my_fg_elev = FGElev(the_coords_transform, scenery_tile.tile_index)
my_stg_entries = utils.stg_io2.read_stg_entries_in_boundary(True, the_coords_transform)
# cannot be read once for all outside of tiles in main function
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,
parameters.BOUNDARY_WEST,
parameters.BOUNDARY_SOUTH,
parameters.BOUNDARY_EAST,
parameters.BOUNDARY_NORTH,
my_airports)
# run programs
if exec_argument in [Procedures.buildings, Procedures.main, Procedures.all]:
buildings.process_buildings(the_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries, file_lock)
if exec_argument in [Procedures.roads, Procedures.main, Procedures.all]:
roads.process_roads(the_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries, file_lock)
if exec_argument in [Procedures.pylons, Procedures.main, Procedures.all]:
pylons.process_pylons(the_coords_transform, my_fg_elev, my_stg_entries, file_lock)
if exec_argument in [Procedures.details, Procedures.all]:
pylons.process_details(the_coords_transform, my_fg_elev, file_lock)
platforms.process_details(the_coords_transform, my_fg_elev, file_lock)
piers.process_details(the_coords_transform, my_fg_elev, file_lock)
# clean-up
my_fg_elev.close()
if exec_argument is Procedures.owbb:
ol.process(the_coords_transform)
else:
my_fg_elev = FGElev(the_coords_transform, scenery_tile.tile_index)
my_stg_entries = utils.stg_io2.read_stg_entries_in_boundary(True, the_coords_transform)
# cannot be read once for all outside of tiles in main function due to local coordinates
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,
parameters.BOUNDARY_WEST,
parameters.BOUNDARY_SOUTH,
parameters.BOUNDARY_EAST,
parameters.BOUNDARY_NORTH,
my_airports)
# run programs
if exec_argument in [Procedures.buildings, Procedures.main, Procedures.all]:
buildings.process_buildings(the_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries, file_lock)
if exec_argument in [Procedures.roads, Procedures.main, Procedures.all]:
roads.process_roads(the_coords_transform, my_fg_elev, my_blocked_areas, my_stg_entries, file_lock)
if exec_argument in [Procedures.pylons, Procedures.main, Procedures.all]:
pylons.process_pylons(the_coords_transform, my_fg_elev, my_stg_entries, file_lock)
if exec_argument in [Procedures.details, Procedures.all]:
pylons.process_details(the_coords_transform, my_fg_elev, file_lock)
platforms.process_details(the_coords_transform, my_fg_elev, file_lock)
piers.process_details(the_coords_transform, my_fg_elev, file_lock)
# clean-up
my_fg_elev.close()
except:
logging.exception('Exception occurred while processing tile {}.'.format(scenery_tile.tile_index))
......@@ -279,7 +284,7 @@ if __name__ == '__main__':
pool.close()
pool.join()
logging.info("Total time used {}".format(time.time() - start_time))
time_logging("Total time used", start_time)
# ================ UNITTESTS =======================
......
......@@ -26,8 +26,9 @@ import numpy as np
import parameters
import prepare_textures
import textures.materials
import utils.osmparser as op
import utils.vec2d as v
from utils import osmparser, coordinates, stg_io2, utilities
from utils import coordinates, stg_io2, utilities
OUR_MAGIC = "osm2city" # Used in e.g. stg files to mark edits by osm2city
......@@ -36,8 +37,8 @@ OUR_MAGIC = "osm2city" # Used in e.g. stg files to mark edits by osm2city
ALLOWED_BUiLDING_PART_VALUES = ['yes', 'residential', 'apartments', 'house', 'commercial', 'retail']
def _process_osm_relations(nodes_dict: Dict[int, osmparser.Node], rel_ways_dict: Dict[int, osmparser.Way],
relations_dict: Dict[int, osmparser.Relation],
def _process_osm_relations(nodes_dict: Dict[int, op.Node], rel_ways_dict: Dict[int, op.Way],
relations_dict: Dict[int, op.Relation],
my_buildings: Dict[int, building_lib.Building],
coords_transform: coordinates.Transformation,
stats: utilities.Stats) -> None:
......@@ -102,8 +103,8 @@ def _process_osm_relations(nodes_dict: Dict[int, osmparser.Node], rel_ways_dict:
logging.info("Added {} buildings based on relations.".format(number_of_created_buildings))
def _process_multipolygon_buildings(nodes_dict: Dict[int, osmparser.Node], rel_ways_dict: Dict[int, osmparser.Way],
relation: osmparser.Relation, my_buildings: Dict[int, building_lib.Building],
def _process_multipolygon_buildings(nodes_dict: Dict[int, op.Node], rel_ways_dict: Dict[int, op.Way],
relation: op.Relation, my_buildings: Dict[int, building_lib.Building],
coords_transform: coordinates.Transformation,
stats: utilities.Stats) -> int:
"""Processes the members in a multipolygon relationship. Returns the number of buildings actually created.
......@@ -142,8 +143,8 @@ def _process_multipolygon_buildings(nodes_dict: Dict[int, osmparser.Node], rel_w
logging.debug("Way osm_id={} not found for relation osm_id={}.".format(m.ref, relation.osm_id))
# Process multiple and add to outer_ways/inner_ways as whole rings
inner_ways.extend(osmparser.closed_ways_from_multiple_ways(inner_ways_multiple))
outer_ways.extend(osmparser.closed_ways_from_multiple_ways(outer_ways_multiple))
inner_ways.extend(op.closed_ways_from_multiple_ways(inner_ways_multiple))
outer_ways.extend(op.closed_ways_from_multiple_ways(outer_ways_multiple))
# Create polygons to allow some geometry analysis
polygons = dict()
......@@ -178,7 +179,7 @@ def _process_multipolygon_buildings(nodes_dict: Dict[int, osmparser.Node], rel_w
return added_buildings
def _process_simple_3d_building(relation: osmparser.Relation, my_buildings: Dict[int, building_lib.Building]):
def _process_simple_3d_building(relation: op.Relation, my_buildings: Dict[int, building_lib.Building]):
"""Processes the members in a Simple3D relationship in order to make sure that a building outline exists."""
buildings_found = list() # osm_id
building_outlines_found = list() # osm_id
......@@ -219,7 +220,7 @@ def _process_simple_3d_building(relation: osmparser.Relation, my_buildings: Dict
# this after checking that the building_parts are relevant.
# There will be no additional checking in _process_building_parts() because now there is a parent already
logging.warning('OSM data error: there is no "outline" member in relation %i', relation.osm_id)
parent = building_lib.BuildingParent(osmparser.get_next_pseudo_osm_id(), False)
parent = building_lib.BuildingParent(op.get_next_pseudo_osm_id(op.OSMFeatureType.building_relation), False)
# no tags are available to be added on parent level
for m in relation.members:
if m.ref in my_buildings:
......@@ -229,7 +230,7 @@ def _process_simple_3d_building(relation: osmparser.Relation, my_buildings: Dict
parent.add_child(building_part)
def _process_building_parts(nodes_dict: Dict[int, osmparser.Node],
def _process_building_parts(nodes_dict: Dict[int, op.Node],
my_buildings: Dict[int, building_lib.Building],
coords_transform: coordinates.Transformation,
stats: utilities.Stats) -> None:
......@@ -349,7 +350,7 @@ def _process_building_parts(nodes_dict: Dict[int, osmparser.Node],
building_parent.add_child(original_building)
original_building_still_used = True
else:
new_way = osmparser.Way(osmparser.get_next_pseudo_osm_id())
new_way = op.Way(op.get_next_pseudo_osm_id(op.OSMFeatureType.building_relation))
new_way.refs = new_refs
new_tags = {'building_part': 'yes'}
new_building_part = _make_building_from_way(nodes_dict, new_tags, new_way,
......@@ -387,7 +388,7 @@ def _clean_building_parents_with_one_child(my_buildings: List[building_lib.Build
building.parent = None
def _process_osm_building(nodes_dict: Dict[int, osmparser.Node], ways_dict: Dict[int, osmparser.Way],
def _process_osm_building(nodes_dict: Dict[int, op.Node], ways_dict: Dict[int, op.Way],
coords_transform: coordinates.Transformation,
stats: utilities.Stats) -> Dict[int, building_lib.Building]:
my_buildings = dict()
......@@ -424,7 +425,7 @@ def _process_osm_building(nodes_dict: Dict[int, osmparser.Node], ways_dict: Dict
return my_buildings
def _make_building_from_way(nodes_dict: Dict[int, osmparser.Node], all_tags: Dict[str, str], way: osmparser.Way,
def _make_building_from_way(nodes_dict: Dict[int, op.Node], all_tags: Dict[str, str], way: op.Way,
coords_transform: coordinates.Transformation, stats: utilities.Stats,
inner_ways=list()) -> Optional[building_lib.Building]:
if way.refs[0] == way.refs[-1]:
......@@ -456,7 +457,7 @@ def _make_building_from_way(nodes_dict: Dict[int, osmparser.Node], all_tags: Dic
def _refs_to_ring(coords_transform: coordinates.Transformation, refs,
nodes_dict: Dict[int, osmparser.Node]) -> shg.LinearRing:
nodes_dict: Dict[int, op.Node]) -> shg.LinearRing:
"""Accept a list of OSM refs, return a linear ring."""
coords = []
for ref in refs:
......@@ -509,8 +510,8 @@ def process_buildings(coords_transform: coordinates.Transformation, fg_elev: uti
random.seed(42)
stats = utilities.Stats()
osm_read_results = osmparser.fetch_osm_db_data_ways_keys(["building", "building:part"])
osm_read_results = osmparser.fetch_osm_db_data_relations_keys(["building", "building:part"], osm_read_results)
osm_read_results = op.fetch_osm_db_data_ways_keys(["building", "building:part"])
osm_read_results = op.fetch_osm_db_data_relations_keys(["building", "building:part"], osm_read_results)
osm_nodes_dict = osm_read_results.nodes_dict
osm_ways_dict = osm_read_results.ways_dict
osm_relations_dict = osm_read_results.relations_dict
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -18,7 +18,6 @@ import re
import sys
import traceback
import types
from typing import Optional
import textures.road
from utils import vec2d as v
......@@ -281,6 +280,71 @@ TEXTURES_REGIONS_EXPLICIT = [] # list of exclusive regions to accept. All if em
TEXTURES_EMPTY_LM_RGB_VALUE = 35
# =============================================================================
# PARAMETERS RELATED TO OWBB
# =============================================================================
# ==================== BUILD-ZONES GENERATION ============
OWBB_GENERATE_LANDUSE = True # from buildings outside of existing land-use zones
OWBB_GENERATE_LANDUSE_BUILDING_BUFFER_DISTANCE = 30
OWBB_GENERATE_LANDUSE_BUILDING_BUFFER_DISTANCE_MAX = 50
OWBB_GENERATE_LANDUSE_LANDUSE_MIN_AREA = 5000 # also used for CORINE
OWBB_GENERATE_LANDUSE_LANDUSE_HOLES_MIN_AREA = 5000
OWBB_GENERATE_LANDUSE_SIMPLIFICATION_TOLERANCE = 20
OWBB_USE_BTG_LANDUSE = True
OWBB_USE_GENERATED_LANDUSE_FOR_BUILDING_GENERATION = False
OWBB_USE_EXTERNAL_LANDUSE_FOR_BUILDING_GENERATION = True
OWBB_SPLIT_MADE_UP_LANDUSE_BY_MAJOR_LINES = True # for external and generated
OWBB_SPLIT_MADE_UP_LANDUSE_WATERWAY_BUFFER = 10 # meters
# ==================== BUILDING GENERATION ============
OWBB_STEP_DISTANCE = 2 # in meters
OWBB_MIN_STREET_LENGTH = 10 # in meters
OWBB_RESIDENTIAL_HIGHWAY_MIN_GEN_SHARE = 0.3
OWBB_INDUSTRIAL_HIGHWAY_MIN_GEN_SHARE = 0.3 # FIXME: not yet used
OWBB_ZONE_AREA_MAX_GEN = 0.1 # FIXME: needs to be zone type specific and maybe village vs. town
OWBB_RESIDENTIAL_HOUSE_FRONT_MIN = 5
OWBB_RESIDENTIAL_HOUSE_FRONT_MAX = 15
OWBB_RESIDENTIAL_HOUSE_BACK_MIN = 10
OWBB_RESIDENTIAL_HOUSE_BACK_MAX = 20
OWBB_RESIDENTIAL_HOUSE_SIDE_MIN = 30
OWBB_RESIDENTIAL_HOUSE_SIDE_MAX = 20
OWBB_RESIDENTIAL_TERRACE_SHARE = 0.3
OWBB_RESIDENTIAL_TERRACE_FRONT_MIN = 5
OWBB_RESIDENTIAL_TERRACE_FRONT_MAX = 10
OWBB_RESIDENTIAL_TERRACE_BACK_MIN = 10
OWBB_RESIDENTIAL_TERRACE_BACK_MAX = 30
OWBB_RESIDENTIAL_TERRACE_SIDE_MIN = 5
OWBB_RESIDENTIAL_TERRACE_SIDE_MAX = 10
OWBB_RESIDENTIAL_TERRACE_MIN_NUMBER = 4
OWBB_INDUSTRIAL_LARGE_SHARE = 0.4
OWBB_INDUSTRIAL_BUILDING_FRONT_MIN = 10
OWBB_INDUSTRIAL_BUILDING_FRONT_MAX = 20
OWBB_INDUSTRIAL_BUILDING_BACK_MIN = 10
OWBB_INDUSTRIAL_BUILDING_BACK_MAX = 20
OWBB_INDUSTRIAL_BUILDING_SIDE_MIN = 10
OWBB_INDUSTRIAL_BUILDING_SIDE_MAX = 20
# ==================== RECTIFY BUILDINGS ============
OWBB_RECTIFY_MAX_DRAW_SAMPLE = 20
OWBB_RECTIFY_SEED_SAMPLE = True
OWBB_RECTIFY_MAX_90_DEVIATION = 7
OWBB_RECTIFY_90_TOLERANCE = 0.1
# default_args_end # DO NOT MODIFY THIS LINE
......
This diff is collapsed.
This diff is collapsed.
......@@ -119,9 +119,8 @@ def read_apt_dat_gz_file(min_lon: float, min_lat: float,
my_helipad = Helipad(float(parts[5]), float(parts[6]), Vec2d(float(parts[3]), float(parts[2])))
my_airport.append_runway(my_helipad)
end_time = time.time()
logging.info("Read %d airports, %d having runways/helipads within the boundary", total_airports, len(airports))
logging.info("Execution time: %f", end_time - start_time)
utilities.time_logging("Execution time", start_time)
return airports
......
This diff is collapsed.
......@@ -292,7 +292,7 @@ def transform_to_rotated_coordinate_frame(quat: Quaternion, originals: List[Vec3
vertex.subtract(third_part)
def calc_angle_of_line_local(x1, y1, x2, y2):
def calc_angle_of_line_local(x1: float, y1: float, x2: float, y2: float) -> float:
"""Returns the angle in degrees of a line relative to North.
Based on local coordinates (x,y) of two points.
"""
......@@ -303,6 +303,23 @@ def calc_angle_of_line_local(x1, y1, x2, y2):
return degree
def calc_point_angle_away(x: float, y: float, added_distance:float, angle: float) -> Tuple[float, float]:
new_x = x + added_distance * sin(radians(angle))
new_y = y + added_distance * cos(radians(angle))
return new_x, new_y
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:
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)
if final_angle > 180:
final_angle = 360 - final_angle
return final_angle
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))
......@@ -386,3 +403,22 @@ class TestCoordinates(unittest.TestCase):
bounds_2 = (0, 20, 20, 30)
self.assertTrue(disjoint_bounds(bounds_1, bounds_2), 'Disjoint 1-2')
self.assertTrue(disjoint_bounds(bounds_2, bounds_1), 'Disjoint 2-1')
def test_calc_angle_of_corner_local(self):
self.assertAlmostEqual(180, calc_angle_of_corner_local(-1, 0, 0, 0, 1, 0), 2)
self.assertAlmostEqual(180, calc_angle_of_corner_local(1, 0, 0, 0, -1, 0), 2)
self.assertAlmostEqual(90, calc_angle_of_corner_local(1, 0, 0, 0, 0, 1), 2)
self.assertAlmostEqual(90, calc_angle_of_corner_local(0, 1, 0, 0, 1, 0), 2)
self.assertAlmostEqual(90, calc_angle_of_corner_local(1, 0, 0, 0, 0, -1), 2)
self.assertAlmostEqual(90, calc_angle_of_corner_local(0, -1, 0, 0, 1, 0), 2)
self.assertAlmostEqual(45, calc_angle_of_corner_local(1, 0, 0, 0, 1, 1), 2)
self.assertAlmostEqual(45, calc_angle_of_corner_local(1, 1, 0, 0, 1, 0), 2)
self.assertAlmostEqual(135, calc_angle_of_corner_local(-1, 0, 0, 0, 1, 1), 2)
self.assertAlmostEqual(135, calc_angle_of_corner_local(1, 1, 0, 0, -1, 0), 2)
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)
class MyException(Exception):
"""A custom exception in osm2city, when you do not want to preserve the original one.
E.g use as follows:
...
except IOError as e:
raise MyException('Something spectacular happened') from e
"""
pass
......@@ -8,7 +8,9 @@ Use a tool like Osmosis to pre-process data.
"""
from collections import namedtuple
from enum import IntEnum, unique
import logging
import multiprocessing as mp
from typing import Dict, List, Optional, Tuple
import time
import unittest
......@@ -23,10 +25,33 @@ from utils.coordinates import Transformation
PSEUDO_OSM_ID = -1 # For those nodes and ways, which get added as part of processing. Not written back to OSM.
def get_next_pseudo_osm_id() -> int:
@unique
class OSMFeatureType(IntEnum):
building_relation = 0
building_generated = 1
landuse = 2
road = 3
pylon_way = 4
def get_next_pseudo_osm_id(osm_feature: OSMFeatureType) -> int:
"""Constructs a pseudo id for OSM as a negative value and therefore never a real OSM value.
Depending on which OSM feature is requesting, a different number range is returned.
In order not to have conflicts between different processes in multiprocessing, the number range is adapted.
In Osmosis Ids have to be sorted from low to high.
The highest ID value will be different for each type of object (nodes, ways and relations)
The IDs are all 64 bit signed integers at the moment, so have a theoretical limit of 2^63-1
or 9,223,372,036,854,775,807
See http://gis.stackexchange.com/questions/17242/what-is-the-highest-possible-value-of-osm-id
"""
global PSEUDO_OSM_ID
PSEUDO_OSM_ID -= 1
return PSEUDO_OSM_ID
type_factor = 1000000000000 * osm_feature.value
pid_factor = mp.current_process().pid * 1000000
return PSEUDO_OSM_ID + type_factor + pid_factor
class OSMElement(object):
......@@ -93,9 +118,27 @@ class Way(OSMElement):
my_coordinates.append((x, y))
if len(my_coordinates) >= 3:
my_polygon = shg.Polygon(my_coordinates)
if my_polygon.is_valid:
if not my_polygon.is_valid: # it might be self-touching or self-crossing polygons
clean = my_polygon.buffer(0) # cf. http://toblerity.org/shapely/manual.html#constructive-methods
if clean.is_valid:
my_polygon = clean # it is now a Polygon or a MultiPolygon
if my_polygon.is_valid and not my_polygon.is_empty:
return my_polygon
return None
return None
def line_string_from_osm_way(self, nodes_dict: Dict[int, Node], my_coord_transformator: Transformation) \
-> Optional[shg.LineString]:
my_coordinates = list()
for ref in self.refs:
if ref in nodes_dict:
my_node = nodes_dict[ref]
x, y = my_coord_transformator.toLocal((my_node.lon, my_node.lat))
my_coordinates.append((x, y))
if len(my_coordinates) >= 2:
my_geometry = shg.LineString(my_coordinates)
if my_geometry.is_valid and not my_geometry.is_empty:
return my_geometry
return None
class Member(object):
......@@ -108,7 +151,7 @@ class Member(object):
class Relation(OSMElement):
__slots__ = ('members')
__slots__ = 'members'
def __init__(self, osm_id: int):
OSMElement.__init__(self, osm_id)
......@@ -251,6 +294,15 @@ def parse_generator_output(str_output: str) -> float:
return 0.
def parse_int(str_int: str, default_value: int) -> int:
"""If string can be parsed then return int, otherwise return the default value."""
try:
x = int(str_int)
return x
except ValueError:
return default_value
def parse_multi_int_values(str_value: str) -> int:
"""Parse int values for tags, where values can be separated by semi-colons.
E.g. for building levels, 'cables' and 'voltage' for power cables, which can have multiple values.
......@@ -318,8 +370,7 @@ def parse_hstore_tags(tags_string: str, osm_id: int) -> Dict[str, str]:
return tags_dict
def fetch_db_way_data(req_way_keys: List[str], req_way_key_values: List[str],
db_connection: psycopg2.extensions.connection) -> Dict[int, Way]:
def fetch_db_way_data(req_way_keys: List[str], req_way_key_values: List[str], db_connection) -> Dict[int, Way]:
"""Fetches Way objects out of database given required tag keys and boundary in parameters."""
query = """SELECT id, tags, nodes
FROM ways AS w
......@@ -342,8 +393,7 @@ def fetch_db_way_data(req_way_keys: List[str], req_way_key_values: List[str],
return ways_dict
def fetch_db_nodes_for_way(req_way_keys: List[str], req_way_key_values: List[str],
db_connection: psycopg2.extensions.connection) -> Dict[int, Node]:
def fetch_db_nodes_for_way(req_way_keys: List[str], req_way_key_values: List[str], db_connection) -> Dict[int, Node]:
"""Fetches Node objects for ways out of database given same constraints as for Way.
Constraints for way: see fetch_db_way_data"""
query = """SELECT n.id, ST_X(n.geom) as lon, ST_Y(n.geom) as lat
......@@ -367,7 +417,7 @@ def fetch_db_nodes_for_way(req_way_keys: List[str], req_way_key_values: List[str
return nodes_dict
def fetch_db_nodes_isolated(req_node_key_values: List[str]) -> Dict[int, Node]:
def fetch_db_nodes_isolated(req_way_keys: List[str], req_node_key_values: List[str]) -> Dict[int, Node]:
"""Fetches Node objects isolated without relation to way etc."""
start_time = time.time()
......@@ -376,7 +426,7 @@ def fetch_db_nodes_isolated(req_node_key_values: List[str]) -> Dict[int, Node]:
query = """SELECT n.id, ST_X(n.geom) as lon, ST_Y(n.geom) as lat, n.tags
FROM nodes AS n
WHERE """
query += construct_tags_query(list(), req_node_key_values, table_alias="n")
query += construct_tags_query(req_way_keys, req_node_key_values, table_alias="n")
query += " AND "
query += construct_intersect_bbox_query(is_way=False)
query += ";"
......@@ -396,7 +446,7 @@ def fetch_db_nodes_isolated(req_node_key_values: List[str]) -> Dict[int, Node]:
return nodes_dict
def fetch_all_query_into_tuple(query: str, db_connection: psycopg2.extensions.connection) -> List[Tuple]:
def fetch_all_query_into_tuple(query: str, db_connection) -> List[Tuple]:
"""Given a query string and a db connection execute fetch all and return the result as a list of tuples"""
cur = db_connection.cursor()
logging.debug("Query string for execution in database: " + query)
......@@ -508,10 +558,11 @@ def fetch_osm_db_data_relations_keys(req_keys: List[str], input_read_result: OSM
relations_dict=relations_dict, rel_nodes_dict=rel_nodes_dict, rel_ways_dict=rel_ways_dict)
def make_db_connection() -> psycopg2.extensions.connection:
def make_db_connection():
""""Create connection to the database based on parameters."""
return psycopg2.connect(database=parameters.DB_NAME, host=parameters.DB_HOST, port=parameters.DB_PORT,
user=parameters.DB_USER, password=parameters.DB_USER_PASSWORD)
connection = psycopg2.connect(database=parameters.DB_NAME, host=parameters.DB_HOST, port=parameters.DB_PORT,
user=parameters.DB_USER, password=parameters.DB_USER_PASSWORD)
return connection
def construct_intersect_bbox_query(is_way: bool=True) -> str:
......@@ -564,7 +615,8 @@ def construct_tags_query(req_tag_keys: List[str], req_tag_key_values: List[str],
return tags_query
def split_way_at_boundary(nodes_dict: Dict[int, Node], complete_way: Way, clipping_border: shg.Polygon) -> List[Way]:
def split_way_at_boundary(nodes_dict: Dict[int, Node], complete_way: Way, clipping_border: shg.Polygon,
osm_feature: OSMFeatureType) -> List[Way]:
"""Splits a way (e.g. road) at the clipping border into 0 to n ways.
A way can be totally inside a boundary, totally outside a boundary, intersect once or several times.
Splitting is tested at existing nodes of the way. A split way's first node is always inside the boundary.
......@@ -587,7 +639,7 @@ def split_way_at_boundary(nodes_dict: Dict[int, Node], complete_way: Way, clippi
split_ways.append(current_way)
current_way = Way(complete_way.osm_id)
current_way.tags = complete_way.tags
current_way.pseudo_osm_id = get_next_pseudo_osm_id()
current_way.pseudo_osm_id = get_next_pseudo_osm_id(osm_feature)
previous_inside = False
# nothing to do if previous also outside
......
......@@ -13,6 +13,7 @@ import pickle
import subprocess
import sys
import textwrap
import time
from typing import Dict, List, Optional, Tuple
import unittest
......@@ -21,7 +22,8 @@ from shapely import affinity
import shapely.geometry as shg
import parameters
from utils import coordinates, osmparser
import utils.coordinates as co
import utils.osmparser as op
import utils.vec2d as ve
......@@ -117,8 +119,8 @@ def log_level_debug_or_lower():
def match_local_coords_with_global_nodes(local_list: List[Tuple[float, float]], ref_list: List[int],
all_nodes: Dict[int, osmparser.Node],
coords_transform: coordinates.Transformation, osm_id: int,
all_nodes: Dict[int, op.Node],
coords_transform: co.Transformation, osm_id: int,
create_node: bool=False) -> List[int]:
"""Given a set of coordinates in local space find matching Node objects in global space.
Matching is using a bit of tolerance (cf. parameter), which should be enough to account for conversion precision
......@@ -136,7 +138,7 @@ def match_local_coords_with_global_nodes(local_list: List[Tuple[float, float]],
closest_distance = 999999
found_key = -1
for key, node_local in nodes_local.items():
distance = coordinates.calc_distance_local(local[0], local[1], node_local[0], node_local[1])
distance = co.calc_distance_local(local[0], local[1], node_local[0], node_local[1])
if distance < closest_distance:
closest_distance = distance
if distance < parameters.BUILDING_TOLERANCE_MATCH_NODE:
......@@ -145,7 +147,7 @@ def match_local_coords_with_global_nodes(local_list: List[Tuple[float, float]],
if found_key < 0:
if create_node:
lon, lat = coords_transform.toGlobal(local)
new_node = osmparser.Node(osmparser.get_next_pseudo_osm_id(), lat, lon)
new_node = op.Node(op.get_next_pseudo_osm_id(op.OSMFeatureType.building_relation), lat, lon)
all_nodes[new_node.osm_id] = new_node
matched_nodes.append(new_node.osm_id)
else:
......@@ -324,7 +326,7 @@ class FGElev(object):
By default, queries are cached. Call save_cache() to
save the cache to disk before freeing the object.
"""
def __init__(self, coords_transform: coordinates.Transformation, tile_index: int,
def __init__(self, coords_transform: co.Transformation, tile_index: int,
auto_save_every: int=50000) -> None:
"""Open pipe to fgelev.
Unless disabled by cache=False, initialize the cache and try to read
......@@ -546,6 +548,12 @@ def bounds_from_list(bounds_list: List[Tuple[float, float, float, float]]) -> Tu
return min_x, min_y, max_x, max_y
def time_logging(message: str, last_time: float) -> float:
current_time = time.time()
logging.info(message + ": %f", current_time - last_time)
return current_time
def minimum_circumference_rectangle_for_polygon(hull: shg.Polygon) -> Tuple[float, float, float]:
"""Constructs a minimum circumference rectangle around a polygon and returns its angle, length and width
There is no check whether length is longer than width - or that length is closer to e.g. the x-axis.
......@@ -573,8 +581,8 @@ def minimum_circumference_rectangle_for_polygon(hull: shg.Polygon) -> Tuple[floa
min_circumference = 99999999.
hull_coords = hull.exterior.coords[:] # list of x,y tuples
for index in range(len(hull_coords) - 1):
angle = coordinates.calc_angle_of_line_local(hull_coords[index][0], hull_coords[index][1],
hull_coords[index + 1][0], hull_coords[index + 1][1])
angle = co.calc_angle_of_line_local(hull_coords[index][0], hull_coords[index][1],
hull_coords[index + 1][0], hull_coords[index + 1][1])
rotated_hull = affinity.rotate(hull, - angle, (0, 0))
bounding_box = rotated_hull.bounds # tuple x_min, y_min, x_max, y_max
bb_length = math.fabs(bounding_box[2] - bounding_box[0])
......
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