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

Getting multipolygons and buildings with parts working without flickering / competing textures.

parent 8625339a
This diff is collapsed.
This diff is collapsed.
......@@ -104,9 +104,6 @@ OVERLAP_CHECK_BRIDGE_MIN_REMAINING = 10
# Use unicode strings as in the first example if there are non-ASCII characters.
# E.g. SKIP_LIST = ["Theologische Fakultät", "Rhombergpassage", 55875208]
SKIP_LIST = []
# -- keep buildings based on their OSM name tag or OSM ID.
# keeps also, building:part of buildings
KEEP_LIST = []
# -- Parameters which influence the number of buildings from OSM taken to output
BUILDING_MIN_HEIGHT = 3.4 # -- minimum height of a building to be included in output (does not include roof)
......@@ -122,7 +119,7 @@ BUILDING_COMPLEX_ROOFS = True # -- generate complex roofs on buildings?
BUILDING_COMPLEX_MIN_HEIGHT = 2 # -- don't put complex roof on buildings smaller than value without roof:shape flag
BUILDING_COMPLEX_ROOFS_MAX_LEVELS = 5 # -- don't put complex roofs on buildings taller than this
BUILDING_COMPLEX_ROOFS_MAX_AREA = 2000 # -- don't put complex roofs on buildings larger than this
BUILDING_SKEL_ROOFS = 1 # -- generate complex roofs with pySkeleton? Used to be EXPERIMENTAL_USE_SKEL
BUILDING_SKEL_ROOFS = True # -- generate complex roofs with pySkeleton?
BUILDING_SKEL_ROOFS_MIN_ANGLE = 10 # -- pySkeleton based complex roofs will
BUILDING_SKEL_ROOFS_MAX_ANGLE = 50 # have a random angle between MIN and MAX
BUILDING_SKEL_MAX_NODES = 10 # -- max number of nodes for which we generate pySkeleton roofs
......@@ -132,7 +129,7 @@ BUILDING_FAKE_AMBIENT_OCCLUSION = True # -- fake AO by darkening facade tex
BUILDING_FAKE_AMBIENT_OCCLUSION_HEIGHT = 6. # 1 - VALUE * exp(- AGL / HEIGHT )
BUILDING_FAKE_AMBIENT_OCCLUSION_VALUE = 0.6
EXPERIMENTAL_INNER = 0 # -- do we still need this?
EXPERIMENTAL_INNER = False # -- do we still need this?
# -- Parameters which influence the height of buildings if no info from OSM is available.
# It uses a triangular distribution (see http://en.wikipedia.org/wiki/Triangular_distribution)
......
......@@ -285,6 +285,9 @@ def init(stats: util.Stats, create_atlas: bool=True) -> None:
pickle.dump(roofs, pickle_file, -1)
pickle.dump(params, pickle_file, -1)
pickle_file.close()
logging.info(str(facades))
logging.info(str(roofs))
else:
logging.info("Loading %s", pkl_file_name)
pickle_file = open(pkl_file_name, 'rb')
......@@ -294,9 +297,9 @@ def init(stats: util.Stats, create_atlas: bool=True) -> None:
atlas_file_name = params['atlas_file_name']
pickle_file.close()
logging.debug(facades)
stats.textures_total = dict((filename, 0) for filename in map((lambda x: x.filename), facades.get_list()))
stats.textures_total.update(dict((filename, 0) for filename in map((lambda x: x.filename), roofs.get_list())))
logging.info('Skipped textures: %d', stats.skipped_texture)
if __name__ == "__main__":
......
......@@ -51,9 +51,9 @@ def flat(ac_object: ac.Object, b, roof_mgr: RoofManager, stats: Stats) -> None:
nodes = list(range(b.nnodes_outer))
uv = face_uv(nodes, b.X, b.roof_texture, angle=None)
nodes = np.array(nodes) + b._nnodes_ground
nodes = np.array(nodes) + b.nnodes_ground
assert(len(nodes) == b._nnodes_ground + 2 * len(b.polygon.interiors))
assert(len(nodes) == b.nnodes_ground + 2 * len(b.polygon.interiors))
l = []
for i, node in enumerate(nodes):
......@@ -74,7 +74,7 @@ def separate_gable(ac_object, b, inward_meters=0.) -> None:
roof_height = b.roof_height
else:
logging.error("no roof_height in separate_gable for building %i" % b.osm_id)
return False
return
# get orientation if exits :
try:
......@@ -164,7 +164,7 @@ def separate_pyramidal(ac_object: ac.Object, b, inward_meters=0.0) -> None:
if b.roof_height:
roof_height = b.roof_height
else:
return False
return
# -- ? corners
o = ac_object.next_node_index()
......@@ -234,7 +234,7 @@ def separate_skillion(ac_object: ac.Object, b):
uv = face_uv(nodes, b.X, b.roof_texture, angle=None)
assert(len(nodes) == b._nnodes_ground + 2 * len(b.polygon.interiors))
assert(len(nodes) == b.nnodes_ground + 2 * len(b.polygon.interiors))
l = []
o = ac_object.next_node_index()
......
......@@ -3,6 +3,8 @@ import logging
import PIL.Image as Image
import textures.texture as tex
class Region(object):
"""Also used as a container for a single image"""
......@@ -18,7 +20,7 @@ class Region(object):
class Atlas(Region):
def __init__(self, x, y, width, height, name: str) -> None:
def __init__(self, x: int, y: int, width: int, height:float, name: str) -> None:
super().__init__(x, y, width, height)
self.regions = [Region(x, y, width, height)]
self._textures = [] # Type atlas.Texture
......@@ -48,7 +50,7 @@ class Atlas(Region):
logging.debug("%s : %s: Skipping an empty texture" % (self.name, the_texture.filename))
atlas.save(filename, optimize=True)
def pack(self, the_texture) -> bool:
def pack(self, the_texture: tex.Texture) -> bool:
logging.debug("packing %s (%i %i)" %
(the_texture.filename, the_texture.width_px, the_texture.height_px))
for the_region in self.regions:
......@@ -61,7 +63,7 @@ class Atlas(Region):
return True
return False
def pack_at(self, the_texture, x, y):
def pack_at(self, the_texture: tex.Texture, x:int, y: int) -> None:
logging.debug("packing %s (%i %i) at (%i %i)" %
(the_texture.filename, the_texture.width_px, the_texture.height_px, x, y))
the_texture.ax = x
......
......@@ -159,9 +159,12 @@ Please set either h_can_repeat or v_can_repeat to False.' % self.filename
return self.y0 + y * self.sy
def __str__(self):
return "<%s> x0,1 %4.2f %4.2f y0,1 %4.2f %4.2f sh,v %4.2fm %4.2fm" % \
(self.filename, self.x0, self.x1, self.y0, self.y1,
self.h_size_meters, self.v_size_meters)
return '<%s> x0=%4.2f x1=%4.2f - y0=%4.3f y1=%4.3f - sh=%4.2fm sv=%4.2fm - ax=%d ay=%d' % \
(self.filename,
self.x0, self.x1,
self.y0, self.y1,
self.h_size_meters, self.v_size_meters,
self.ax, self.ay)
# self.type = type
# commercial-
# - warehouse
......@@ -404,7 +407,8 @@ class RoofManager(object):
return candidates
def __str__(self):
return "".join([str(t) + '\n' for t in self.__l])
start = 'Textures of type %s with %d of elements:\n' % (self.__cls, len(self.__l))
return start + "".join([str(t) + '\n' for t in self.__l])
def __getitem__(self, i):
return self.__l[i]
......@@ -414,7 +418,7 @@ class RoofManager(object):
class FacadeManager(RoofManager):
def find_matching_facade(self, requires, tags, height, width, stats: Stats):
def find_matching_facade(self, requires, tags, height, width, stats: Stats=None):
exclusions = []
if 'roof:colour' in tags:
exclusions.append("%s:%s" % ('roof:colour', tags['roof:colour']))
......@@ -433,7 +437,8 @@ class FacadeManager(RoofManager):
return None
ranked_list = _rank_candidates(candidates, tags)
the_texture = ranked_list[random.randint(0, len(ranked_list) - 1)]
stats.count_texture(the_texture)
if stats is not None:
stats.count_texture(the_texture)
return the_texture
def find_facade_candidates(self, requires, excludes, height, width):
......
......@@ -4,6 +4,8 @@ from typing import Dict, List
import shapely.geometry as shg
import parameters
from utils.coordinates import Transformation
import utils.osmparser as osm
class Landuse(object):
......@@ -20,7 +22,8 @@ class Landuse(object):
self.number_of_buildings = 0 # only set for generated TYPE_NON_OSM land-uses during generation
def process_osm_landuse_refs(nodes_dict, ways_dict, my_coord_transformator) -> Dict[int, Landuse]:
def process_osm_landuse_refs(nodes_dict: Dict[int, osm.Node], ways_dict: Dict[int, osm.Way],
my_coord_transformator: Transformation) -> Dict[int, Landuse]:
my_landuses = dict() # osm_id as key, Landuse as value
for way in list(ways_dict.values()):
......@@ -42,15 +45,9 @@ def process_osm_landuse_refs(nodes_dict, ways_dict, my_coord_transformator) -> D
my_landuse.type_ = Landuse.TYPE_RETAIL
valid_landuse = True
if valid_landuse:
# Process the Nodes
my_coordinates = list()
for ref in way.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) >= 3:
my_landuse.polygon = shg.Polygon(my_coordinates)
my_polygon = way.polygon_from_osm_way(nodes_dict, my_coord_transformator)
if my_polygon is not None:
my_landuse.polygon = my_polygon
if my_landuse.polygon.is_valid and not my_landuse.polygon.is_empty:
my_landuses[my_landuse.osm_id] = my_landuse
......
......@@ -17,6 +17,7 @@ import xml.sax
import psycopg2
import shapely.geometry as shg
from utils.coordinates import Transformation
import parameters
......@@ -44,6 +45,20 @@ class OSMElement(object):
return "<%s OSM_ID %i at %s>" % (type(self).__name__, self.osm_id, hex(id(self)))
def combine_tags(first_tags: Dict[str, str], second_tags: Dict[str, str]) -> Dict[str, str]:
"""Combines the tags of the first with the second, in such a way that the first wins in case of same keys"""
if len(second_tags) == 0:
return first_tags.copy()
if len(first_tags) == 0:
return second_tags.copy()
combined_tags = first_tags.copy()
for key, value in second_tags.items():
if key not in combined_tags:
combined_tags[key] = value
return combined_tags
class Node(OSMElement):
__slots__ = ('lat', 'lon', 'MSL', 'h_add') # the last two are written from roads.py
......@@ -67,6 +82,21 @@ class Way(OSMElement):
def add_ref(self, ref: int) -> None:
self.refs.append(ref)
def polygon_from_osm_way(self, nodes_dict: Dict[int, Node], my_coord_transformator: Transformation) \
-> Optional[shg.Polygon]:
"""Creates a shapely polygon in local coordinates. Or None is something is not valid."""
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) >= 3:
my_polygon = shg.Polygon(my_coordinates)
if my_polygon.is_valid:
return my_polygon
return None
class Member(object):
__slots__ = ('ref', 'type_', 'role')
......@@ -88,6 +118,40 @@ class Relation(OSMElement):
self.members.append(member)
def closed_ways_from_multiple_ways(way_parts: List[Way]) -> List[Way]:
"""Create closed ways from multiple not closed ways where possible.
See http://wiki.openstreetmap.org/wiki/Relation:multipolygon.
If parts of ways cannot be used, they just get disregarded.
The new Way gets the osm_id from the first piece used and gets all tags merged.
"""
remaining_parts = {way.osm_id: way for way in way_parts}
closed_ways = list()
while remaining_parts:
matched_candidates = list()
match_found = False
starting = remaining_parts.popitem()[1] # it does not matter, which one we pick
for key, candidate in remaining_parts.items():
# it does not matter whether we test the first or last node, as in the end there needs to be a connection
if starting.refs[-1] == candidate.refs[0]:
starting.refs.extend(candidate.refs[1:])
match_found = True
elif starting.refs[-1] == candidate.refs[-1]: # the candidate's nodes need to be added in reverse order
starting.refs.extend(candidate.refs[-2::-1])
match_found = True
if match_found:
matched_candidates.append(key)
# combine the tags
starting.tags = dict(list(starting.tags.items()) + list(candidate.tags.items()))
if starting.refs[0] == starting.refs[-1]: # we have found a closing ring and can stop searching
closed_ways.append(starting)
break
for matched in matched_candidates:
remaining_parts.pop(matched)
return closed_ways
OSMReadResult = namedtuple("OSMReadResult", "nodes_dict, ways_dict, relations_dict, rel_nodes_dict, rel_ways_dict")
......@@ -564,6 +628,16 @@ def fetch_osm_db_data_relations_keys(req_keys: List[str], input_read_result: OSM
db_connection = make_db_connection()
# common subquery
sub_query = "((r.tags @> 'type=>multipolygon'"
sub_query += " AND " + construct_tags_query(req_keys, list(), "r")
sub_query += ") OR r.tags @> 'type=>building')"
sub_query += " AND r.id = rm.relation_id"
sub_query += " AND rm.member_type = 'W'"
sub_query += " AND rm.member_id = w.id"
sub_query += " AND "
sub_query += construct_intersect_bbox_query()
# == Relations and members and ways
# Getting related way data might add a bit of volume, but reduces number of queries and might be seldom that
# same way is in different relations for buildings.
......@@ -571,13 +645,7 @@ def fetch_osm_db_data_relations_keys(req_keys: List[str], input_read_result: OSM
FROM relations AS r, relation_members AS rm, ways AS w
WHERE
"""
query += "r.tags @> 'type=>multipolygon'"
query += " AND " + construct_tags_query(req_keys, list(), "r")
query += " AND r.id = rm.relation_id"
query += " AND rm.member_type = 'W'"
query += " AND rm.member_id = w.id"
query += " AND "
query += construct_intersect_bbox_query()
query += sub_query
query += " ORDER BY rm.relation_id, rm.sequence_id"
query += ";"
......@@ -610,13 +678,7 @@ def fetch_osm_db_data_relations_keys(req_keys: List[str], input_read_result: OSM
FROM relations AS r, relation_members AS rm, ways AS w, way_nodes AS wn, nodes AS n
WHERE
"""
query += "r.tags @> 'type=>multipolygon'"
query += " AND " + construct_tags_query(req_keys, list(), "r")
query += " AND r.id = rm.relation_id"
query += " AND rm.member_type = 'W'"
query += " AND rm.member_id = w.id"
query += " AND "
query += construct_intersect_bbox_query()
query += sub_query
query += " AND wn.way_id = w.id"
query += " AND wn.node_id = n.id"
query += ";"
......@@ -776,7 +838,6 @@ class TestOSMParser(unittest.TestCase):
self.assertEqual(99, parse_cable_stuff(' 99.1'), 'Float')
self.assertEqual(88, parse_cable_stuff(' 88; 4'), 'Two valid numbers')
def test_is_parsable_float(self):
self.assertFalse(is_parsable_float('1,2'))
self.assertFalse(is_parsable_float('x'))
......@@ -806,3 +867,39 @@ class TestOSMParser(unittest.TestCase):
self.assertEqual(2, len(my_dict), "Keys and values with comma")
self.assertEqual("go,o", my_dict["foo"], "Keys and values with comma validate first value")
self.assertEqual("i,", my_dict["ho,o"], "Keys and values with comma validate first value")
def test_closed_ways_from_multiple_ways(self):
way_unrelated = Way(1)
way_unrelated.refs = [90, 91]
way_no_ring0 = Way(2)
way_no_ring0.refs = [80, 81, 82]
way_no_ring1 = Way(3)
way_no_ring1.refs = [80, 83]
way_a_0 = Way(4)
way_a_0.refs = [1, 2, 3, 4]
way_a_0.tags = {'ring0': 'a_0', 'building': 'yes'}
way_a_1 = Way(5)
way_a_1.refs = [4, 5, 6, 1]
way_a_1.tags = {'ring1': 'a_1', 'building': 'yes'}
way_b_0 = Way(6)
way_b_0.refs = [11, 12, 13, 14]
way_b_0.tags = {'ring0': 'b_0', 'building': 'yes'}
way_b_1 = Way(7)
way_b_1.refs = [16, 15, 14]
way_b_1.tags = {'ring1': 'b_1', 'building': 'yes'}
way_b_2 = Way(8)
way_b_2.refs = [16, 17, 18, 11]
way_b_2.tags = {'ring2': 'b_2', 'building': 'yes'}
closed_ways = closed_ways_from_multiple_ways([way_b_0, way_a_1, way_unrelated, way_a_0, way_b_2, way_no_ring1,
way_b_1, way_no_ring0])
self.assertEqual(2, len(closed_ways))
def test_combine_tags(self):
first_dict = {'1': '1', '2': '2', '3': '3'}
second_dict = {'3': '99', '4': '4'}
combined_tags = combine_tags(first_dict, second_dict)
self.assertEquals(4, len(combined_tags))
......@@ -16,8 +16,8 @@ from typing import List, Optional, Tuple
import unittest
import numpy as np
import parameters
import parameters
from utils import coordinates
import utils.vec2d as ve
......
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