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

* Changing logic such that more roofs without residuals from underlying facade textures.

* Introduced compat:roof-large tag
parent 8539481c
This diff is collapsed.
......@@ -1087,7 +1087,8 @@ if __name__ == "__main__":
files_to_remove.append(path_to_stg + file_name + ".xml")
else:
# -- write .ac and .xml
building_lib.write(path_to_stg + file_name + ".ac", cl.objects, fg_elev, cluster_elev, cluster_offset)
building_lib.write(path_to_stg + file_name + ".ac", cl.objects, fg_elev,
cluster_elev, cluster_offset, prepare_textures.roofs)
write_xml(path_to_stg, file_name, cl.objects, cluster_offset)
total_buildings_written += len(cl.objects)
......
......@@ -120,12 +120,12 @@ BUILDING_SIMPLIFY_TOLERANCE = 1.0 # -- all points in the simplified building w
BUILDING_NEVER_SKIP_LEVELS = 6 # -- buildings that tall will never be skipped
BUILDING_UNKNOWN_ROOF_TYPE = "flat" # -- If the roof type isn't given use this type
BUILDING_COMPLEX_ROOFS = 1 # -- generate complex roofs on buildings?
BUILDING_COMPLEX_MIN_HEIGHT = 2 # -- don't put complex roof on buildings smallers than value without roof:shape flag
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_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_MIN_ANGLE = 20 # -- pySkeleton based complex roofs will
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
BUILDING_SKEL_MAX_HEIGHT_RATIO = 0.7 # -- skip skel roofs if ratio of roof height to base building height is larger than this
......
......@@ -6,10 +6,10 @@ from typing import List
import numpy as np
import utils.ac3d as ac
from textures.texture import Texture
from textures.texture import Texture, RoofManager
def flat(ac_object: ac.Object, b, X) -> None:
def flat(ac_object: ac.Object, b, roof_mgr: RoofManager) -> None:
"""Flat roof. Also works for relations."""
# 3-----------------2 Outer is CCW: 0 1 2 3
# | /| Inner[0] is CW: 4 5 6 7
......@@ -22,6 +22,13 @@ def flat(ac_object: ac.Object, b, X) -> None:
if b.roof_texture is None:
raise ValueError("Roof texture None")
if "compat:roof-flat" not in b.roof_requires:
logging.debug("Replacing texture for flat roof despite " + str(b.roof_requires))
if "compat:roof-pitched" in b.roof_requires:
b.roof_requires.remove("compat:roof-pitched")
b.roof_requires.append("compat:roof-flat")
b.roof_texture = roof_mgr.find_matching_roof(b.roof_requires, b.longest_edge_len)
if len(b.polygon.interiors):
outer_closest = copy.copy(b.outer_nodes_closest)
i = b.nnodes_outer
......@@ -42,7 +49,7 @@ def flat(ac_object: ac.Object, b, X) -> None:
else:
nodes = list(range(b.nnodes_outer))
uv = face_uv(nodes, X, b.roof_texture, angle=None)
uv = face_uv(nodes, b.X, b.roof_texture, angle=None)
nodes = np.array(nodes) + b._nnodes_ground
assert(len(nodes) == b._nnodes_ground + 2 * len(b.polygon.interiors))
......@@ -50,15 +57,14 @@ def flat(ac_object: ac.Object, b, X) -> None:
l = []
for i, node in enumerate(nodes):
l.append((node + b.first_node, uv[i][0], uv[i][1]))
foo = 0
ac_object.face(l)
def separate_hipped(ac_object: ac.Object, b, X) -> None:
return separate_gable(ac_object, b, X, inward_meters=3.)
def separate_hipped(ac_object: ac.Object, b) -> None:
return separate_gable(ac_object, b, inward_meters=3.)
def separate_gable(ac_object, b, X, inward_meters=0.) -> None:
def separate_gable(ac_object, b, inward_meters=0.) -> None:
"""gable roof, 4 nodes, separate model. Inward_"""
# -- pitched roof for 4 ground nodes
t = b.roof_texture
......@@ -66,13 +72,13 @@ def separate_gable(ac_object, b, X, inward_meters=0.) -> None:
if b.roof_height:
roof_height = b.roof_height
else:
logging.error("no roof_height in seperate_gable for building %i" % b.osm_id)
logging.error("no roof_height in separate_gable for building %i" % b.osm_id)
return False
# get orientation if exits :
try:
roof_orientation = str(b.tags['roof:orientation'])
if not (roof_orientation in ['along', 'accross']):
if not (roof_orientation in ['along', 'across']):
roof_orientation = 'along'
except:
roof_orientation = 'along'
......@@ -80,12 +86,12 @@ def separate_gable(ac_object, b, X, inward_meters=0.) -> None:
# search smallest and longest sides
i_small = 3
i_long = 3
l_side2 = (X[0][0] - X[3][0])**2 + (X[0][1] - X[3][1])**2
l_side2 = (b.X[0][0] - b.X[3][0])**2 + (b.X[0][1] - b.X[3][1])**2
l_small = l_side2
l_long = l_side2
for i in range(0, 3):
l_side2 = (X[i+1][0] - X[i][0])**2 + (X[i+1][1] - X[i][1])**2
l_side2 = (b.X[i+1][0] - b.X[i][0])**2 + (b.X[i+1][1] - b.X[i][1])**2
if l_side2 > l_long:
i_long = i
l_long = l_side2
......@@ -93,7 +99,7 @@ def separate_gable(ac_object, b, X, inward_meters=0.) -> None:
i_small = i
l_small = l_side2
if roof_orientation == 'accross':
if roof_orientation == 'across':
i_side = i_small
else:
i_side = i_long
......@@ -105,19 +111,19 @@ def separate_gable(ac_object, b, X, inward_meters=0.) -> None:
# -- 4 corners
o = ac_object.next_node_index()
for i in range(0, 4):
ac_object.node(-X[ind_X[i]][1], b.ground_elev + b.height - roof_height, -X[ind_X[i]][0])
ac_object.node(-b.X[ind_X[i]][1], b.ground_elev + b.height - roof_height, -b.X[ind_X[i]][0])
# We don't want the hipped part to be greater than the height, which is 45 deg
inward_meters = min(roof_height, inward_meters)
# -- tangential vector of long edge
tang = (X[ind_X[1]]-X[ind_X[0]])/b.lenX[ind_X[1]] * inward_meters
tang = (b.X[ind_X[1]]-b.X[ind_X[0]])/b.lenX[ind_X[1]] * inward_meters
len_roof_bottom = 1.*b.lenX[ind_X[0]]
ac_object.node(-(0.5 * (X[ind_X[3]][1] + X[ind_X[0]][1]) + tang[1]), b.ground_elev + b.height,
-(0.5*(X[ind_X[3]][0]+X[ind_X[0]][0]) + tang[0]))
ac_object.node(-(0.5 * (X[ind_X[1]][1] + X[ind_X[2]][1]) - tang[1]), b.ground_elev + b.height,
-(0.5*(X[ind_X[1]][0]+X[ind_X[2]][0]) - tang[0]))
ac_object.node(-(0.5 * (b.X[ind_X[3]][1] + b.X[ind_X[0]][1]) + tang[1]), b.ground_elev + b.height,
-(0.5*(b.X[ind_X[3]][0] + b.X[ind_X[0]][0]) + tang[0]))
ac_object.node(-(0.5 * (b.X[ind_X[1]][1] + b.X[ind_X[2]][1]) - tang[1]), b.ground_elev + b.height,
-(0.5*(b.X[ind_X[1]][0] + b.X[ind_X[2]][0]) - tang[0]))
roof_texture_size_x = t.h_size_meters # size of roof texture in meters
roof_texture_size_y = t.v_size_meters
......@@ -148,7 +154,7 @@ def separate_gable(ac_object, b, X, inward_meters=0.) -> None:
(o + 4, t.x(0.5*repeat_x), t.y(repeat_y))])
def separate_pyramidal(ac_object: ac.Object, b, X, inward_meters=0.0) -> None:
def separate_pyramidal(ac_object: ac.Object, b, inward_meters=0.0) -> None:
"""pyramidal roof, ? nodes, separate model. Inward_"""
# -- pitched roof for ? ground nodes
t = b.roof_texture
......@@ -161,38 +167,38 @@ def separate_pyramidal(ac_object: ac.Object, b, X, inward_meters=0.0) -> None:
# -- ? corners
o = ac_object.next_node_index()
for x in X:
for x in b.X:
ac_object.node(-x[1], b.ground_elev + b.height - roof_height, -x[0])
# We don't want the hipped part to be greater than the height, which is 45 deg
inward_meters = min(roof_height, inward_meters)
# get middle node of the "tower"
out_1 = -sum([xi[1] for xi in X])/len(X)
out_2 = -sum([xi[0] for xi in X])/len(X)
out_1 = -sum([xi[1] for xi in b.X])/len(b.X)
out_2 = -sum([xi[0] for xi in b.X])/len(b.X)
ac_object.node(out_1, b.ground_elev + b.height, out_2)
# texture it
roof_texture_size_x = t.h_size_meters # size of roof texture in meters
# loop on sides of the building
for i in range(0, len(X)):
for i in range(0, len(b.X)):
repeat_x = b.lenX[1]/roof_texture_size_x
len_roof_hypo = (inward_meters**2 + roof_height**2)**0.5
repeat_y = len_roof_hypo/roof_texture_size_x
ac_object.face([(o + i, t.x(0), t.y(0)),
(o + (i+1) % len(X), t.x(repeat_x), t.y(0)),
(o + len(X), t.x(0.5*repeat_x), t.y(repeat_y))])
(o + (i+1) % len(b.X), t.x(repeat_x), t.y(0)),
(o + len(b.X), t.x(0.5*repeat_x), t.y(repeat_y))])
def separate_skillion(ac_object: ac.Object, b, X):
def separate_skillion(ac_object: ac.Object, b):
"""skillion roof, n nodes, separate model. Inward_"""
# - handle square skillion roof
# it's assumed that the first 2 nodes are at building:height-roof:height
# the last 2 nodes are at building:height
# -- 4 corners
o = ac_object.next_node_index()
for x in X:
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))
......@@ -201,7 +207,7 @@ def separate_skillion(ac_object: ac.Object, b, X):
# FLAT PART
i = 0
for x in X:
for x in b.X:
ac_object.node(-x[1], b.ground_elev + b.height - b.roof_height + b.roof_height_X[i], -x[0])
i += 1
......@@ -227,7 +233,7 @@ def separate_skillion(ac_object: ac.Object, b, X):
else:
nodes = list(range(b.nnodes_outer))
uv = face_uv(nodes, X, b.roof_texture, angle=None)
uv = face_uv(nodes, b.X, b.roof_texture, angle=None)
assert(len(nodes) == b._nnodes_ground + 2 * len(b.polygon.interiors))
......@@ -237,7 +243,7 @@ def separate_skillion(ac_object: ac.Object, b, X):
# create nodes for/ and roof
for i, node in enumerate(nodes):
# new nodes
ac_object.node(-X[node][1], b.ground_elev + b.height - b.roof_height + b.roof_height_X[node], -X[node][0])
ac_object.node(-b.X[node][1], b.ground_elev + b.height - b.roof_height + b.roof_height_X[node], -b.X[node][0])
l.append((o + node, uv[i][0], uv[i][1]))
ac_object.face(l)
return
......
......@@ -3,7 +3,7 @@ import os
from PIL import Image
import random
import re
from typing import Dict
from typing import Dict, List, Optional
import numpy as np
......@@ -281,18 +281,44 @@ class RoofManager(object):
return False
return True
def find_matching_roof(self, requires=[]):
candidates = self.find_candidates(requires)
logging.verbose("looking for texture" + str(requires)) # @UndefinedVariable
for c in candidates:
logging.verbose(" candidate " + c.filename + " provides " + str(c.provides)) # @UndefinedVariable
def find_matching_roof(self, requires: List[str], max_dimension: float):
candidates = self.find_candidates(requires, list())
if len(candidates) == 0:
logging.warning("No matching texture found for " + str(requires))
return None
the_texture = candidates[random.randint(0, len(candidates)-1)]
if "compat:roof-flat" not in requires:
the_texture = candidates[random.randint(0, len(candidates) - 1)]
else: # for flat roofs make sure that a candidate texture that can be found with enough length/width
max_dim_candidates = list()
for candidate in candidates:
if not candidate.h_can_repeat and candidate.h_size_meters < max_dimension:
continue
if not candidate.v_can_repeat and candidate.v_size_meters < max_dimension:
continue
max_dim_candidates.append(candidate)
if len(max_dim_candidates) > 0:
the_texture = max_dim_candidates[random.randint(0, len(max_dim_candidates)-1)]
else:
# now we do not care about colour, material etc. Just pick a
fallback_candidates = self.find_candidates(['compat:roof-large'], list())
final_candidates = list()
if len(fallback_candidates) == 0:
logging.error("Large roof required, but no roof texture providing 'compat:roof-large' found.")
exit(1)
for candidate in fallback_candidates:
if not candidate.h_can_repeat and candidate.h_size_meters < max_dimension:
continue
if not candidate.v_can_repeat and candidate.v_size_meters < max_dimension:
continue
final_candidates.append(candidate)
if len(final_candidates) > 0:
the_texture = final_candidates[random.randint(0, len(final_candidates) - 1)]
else: # give up and live with some visual residuals instead of excluding the building
the_texture = fallback_candidates[random.randint(0, len(fallback_candidates) - 1)]
tools.stats.count_texture(the_texture)
return the_texture
def find_candidates(self, requires=[], excludes=[]):
def find_candidates(self, requires: List[str], excludes: List[str]):
candidates = []
# replace known hex colour codes
requires = list(_map_hex_colour(value) for value in requires)
......@@ -343,7 +369,7 @@ class RoofManager(object):
can_material = False
if req_material is not None:
for prov_material in prov_materials:
logging.verbose("Provides ", prov_material, " Requires ", requires) # @UndefinedVariable
logging.verbose("Provides ", prov_material, " Requires ", requires)
if prov_material in requires:
can_material = True
break
......@@ -366,7 +392,7 @@ class RoofManager(object):
candidates.append(candidate)
else:
logging.verbose(" unmet requires %s req %s prov %s",
str(candidate.filename), str(requires), str(candidate.provides)) # @UndefinedVariable
str(candidate.filename), str(requires), str(candidate.provides))
return candidates
def __str__(self):
......@@ -416,11 +442,11 @@ class FacadeManager(RoofManager):
for t in candidates:
if height < t.height_min or height > t.height_max:
logging.verbose("height %.2f (%.2f-%.2f) outside bounds : %s",
height, t.height_min, t.height_max, str(t.filename)) # @UndefinedVariable
height, t.height_min, t.height_max, str(t.filename))
continue
if width < t.width_min or width > t.width_max:
logging.verbose("width %.2f (%.2f-%.2f) outside bounds : %s",
width, t.width_min, t.width_max, str(t.filename)) # @UndefinedVariable
width, t.width_min, t.width_max, str(t.filename))
continue
new_candidates.append(t)
......
......@@ -116,8 +116,6 @@ class STGFile(object):
self.our_list.append(line)
self.our_ac_file_name_list.append(ac_file_name)
logging.debug(self.file_name + ':' + line)
else:
logging.debug(self.file_name + ': not writing (once=True) ' + line)
self._make_path_to_stg()
return self.path_to_stg
......
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