Initial commit
This commit is contained in:
commit
a437c068c8
64 changed files with 561683 additions and 0 deletions
756
scripts/CityGML2OBJv2/componentseparationmodule.py
Normal file
756
scripts/CityGML2OBJv2/componentseparationmodule.py
Normal file
|
|
@ -0,0 +1,756 @@
|
|||
import re
|
||||
import config
|
||||
import markup3dmodule as m3dm
|
||||
import polygon3dmodule as p3dm
|
||||
import json
|
||||
import numpy as np
|
||||
import open3d as o3d
|
||||
import open3d.core as o3c
|
||||
import os
|
||||
|
||||
|
||||
# Function to create triangles at each corner in 3D
|
||||
def create_corner_triangles(box_points, triangle_size=1):
|
||||
triangles = []
|
||||
for i, point in enumerate(box_points):
|
||||
x, y, z = point
|
||||
if i == 0: # Bottom-left-front corner
|
||||
triangles.append([[x, y, z], [x + triangle_size, y, z], [x, y + triangle_size, z]])
|
||||
elif i == 1: # Bottom-right-front corner
|
||||
triangles.append([[x, y, z], [x - triangle_size, y, z], [x, y + triangle_size, z]])
|
||||
elif i == 2: # Top-left-front corner
|
||||
triangles.append([[x, y, z], [x + triangle_size, y, z], [x, y - triangle_size, z]])
|
||||
elif i == 3: # Bottom-left-back corner
|
||||
triangles.append([[x, y, z], [x + triangle_size, y, z], [x, y + triangle_size, z]])
|
||||
elif i == 4: # Top-right-back corner
|
||||
triangles.append([[x, y, z], [x - triangle_size, y, z], [x, y - triangle_size, z]])
|
||||
elif i == 5: # Top-left-back corner
|
||||
triangles.append([[x, y, z], [x + triangle_size, y, z], [x, y - triangle_size, z]])
|
||||
elif i == 6: # Bottom-right-back corner
|
||||
triangles.append([[x, y, z], [x - triangle_size, y, z], [x, y + triangle_size, z]])
|
||||
elif i == 7: # Top-right-front corner
|
||||
triangles.append([[x, y, z], [x - triangle_size, y, z], [x, y - triangle_size, z]])
|
||||
return triangles
|
||||
|
||||
|
||||
def addTranslationParameters(e, i, trans_param):
|
||||
# Convert lists to numpy arrays for easier manipulation
|
||||
e = np.array(e)
|
||||
# i = np.array(i)
|
||||
i_translated = []
|
||||
trans_param = np.array(trans_param)
|
||||
# case distinction for empty translation parameters
|
||||
if len(trans_param) > 0:
|
||||
# Subtract the translation parameters from each point
|
||||
if len(e) > 0:
|
||||
e_translated = e - trans_param
|
||||
elif len(e) == 0:
|
||||
e_translated = np.asarray([])
|
||||
# Iterate over all the different interior rings
|
||||
if len(i) > 0:
|
||||
for interior_ring in i:
|
||||
interior_ring = np.asarray(interior_ring)
|
||||
# Translate the interior ring
|
||||
interior_ring_translated = interior_ring - trans_param
|
||||
# Coollect the translated interior rings
|
||||
i_translated.append(interior_ring_translated.tolist())
|
||||
else:
|
||||
i_translated = i
|
||||
return e_translated.tolist(), i_translated
|
||||
else:
|
||||
return e.tolist(), i
|
||||
|
||||
def getBufferedBBoxPoints(b):
|
||||
# Schritt 1: identifying all wallsurfaces and roof surfaces of the building
|
||||
output = {}
|
||||
specifyVersion()
|
||||
# comprehensive list of semantic surfaces
|
||||
semanticSurfaces = ['GroundSurface', 'WallSurface', 'RoofSurface', 'ClosureSurface', 'CeilingSurface', ]
|
||||
|
||||
for semanticSurface in semanticSurfaces:
|
||||
output[semanticSurface] = []
|
||||
data = []
|
||||
for cl in output:
|
||||
cls = []
|
||||
for child in b.getiterator():
|
||||
if child.tag == '{%s}%s' % (ns_bldg, cl):
|
||||
cls.append(child)
|
||||
|
||||
for feature in cls:
|
||||
for p in feature.findall('.//{%s}Polygon' % ns_gml):
|
||||
e, i = m3dm.polydecomposer(p)
|
||||
epoints = m3dm.GMLpoints(e[0])
|
||||
# -- Clean recurring points, except the last one
|
||||
last_ep = epoints[-1]
|
||||
epoints_clean = list(remove_reccuring(epoints))
|
||||
epoints_clean.append(last_ep)
|
||||
for point in epoints_clean:
|
||||
data.append(point)
|
||||
|
||||
# Schritt 2: Idetify the Bounding volume
|
||||
# 2.1 creating an open3d pointcloud from all the idetified vertex points
|
||||
pcd = o3d.t.geometry.PointCloud(o3c.Tensor(data, o3c.float32))
|
||||
# 2.2 obtain the axis aligned boundign box of the point cloud
|
||||
axis_aligned_bb = pcd.get_axis_aligned_bounding_box()
|
||||
# Schritt 3: Construct small triangles that describe the boundign box sufficienly
|
||||
box_points = axis_aligned_bb.get_box_points().numpy().tolist()
|
||||
# Convert the list to a numpy array for easier manipulation
|
||||
box_points = np.array(box_points)
|
||||
# Calculate the min and max coordinates
|
||||
min_x, min_y, min_z = np.min(box_points, axis=0)
|
||||
max_x, max_y, max_z = np.max(box_points, axis=0)
|
||||
# Add a 3m buffer
|
||||
buffer = 3
|
||||
min_x -= buffer
|
||||
min_y -= buffer
|
||||
min_z -= buffer
|
||||
max_x += buffer
|
||||
max_y += buffer
|
||||
max_z += buffer
|
||||
# Define the buffered bounding box points
|
||||
buffered_box_points = np.array([
|
||||
[min_x, min_y, min_z],
|
||||
[max_x, min_y, min_z],
|
||||
[min_x, max_y, min_z],
|
||||
[min_x, min_y, max_z],
|
||||
[max_x, max_y, max_z],
|
||||
[min_x, max_y, max_z],
|
||||
[max_x, min_y, max_z],
|
||||
[max_x, max_y, min_z]
|
||||
])
|
||||
return buffered_box_points
|
||||
|
||||
def obtainSRSInfo(root):
|
||||
specifyVersion()
|
||||
# obtain the envelope object
|
||||
envelopes = []
|
||||
for envelope in root.getiterator('{%s}Envelope' % ns_gml):
|
||||
envelopes.append(envelope)
|
||||
|
||||
# Extracting the srsName attribute from each Envelope
|
||||
srs_names = [envelope.get('srsName') for envelope in envelopes]
|
||||
srs_Dimensions = [envelope.get('srsDimension') for envelope in envelopes]
|
||||
return srs_names, srs_Dimensions
|
||||
|
||||
|
||||
# This function is used to create a corresponding json file defining the bbox of an object for each corresponding obj file
|
||||
def writeBBOXJSON(b, overall_counter, path, b_counter, trans_param):
|
||||
if len(trans_param) > 0:
|
||||
translation_parameters = {
|
||||
"d_x": str(trans_param[0]),
|
||||
"d_y": str(trans_param[1]),
|
||||
"d_z": str(trans_param[2])
|
||||
}
|
||||
else:
|
||||
translation_parameters = {
|
||||
"d_x": str(0),
|
||||
"d_y": str(0),
|
||||
"d_z": str(0)
|
||||
}
|
||||
|
||||
buffered_box_points_global = getBufferedBBoxPoints(b)
|
||||
|
||||
# translate to the local coordinate system
|
||||
buffered_box_points, _ = addTranslationParameters(buffered_box_points_global, [], trans_param=trans_param)
|
||||
|
||||
# From this set of points, obtain the minimum set that is necessary to describe the bounding box.
|
||||
min_point = buffered_box_points[0]
|
||||
max_point = buffered_box_points[4]
|
||||
|
||||
# Construct the json file path
|
||||
json_file_path = str(path) + str(b_counter) + "_" + str(overall_counter) + "_bbox_" + ".json"
|
||||
|
||||
# Write the bbox to a designated json file
|
||||
# Prüfen, ob die JSON-Datei existiert und laden
|
||||
if os.path.exists(json_file_path):
|
||||
with open(json_file_path, 'r') as json_file:
|
||||
axis_aligned_bbox = json.load(json_file)
|
||||
|
||||
|
||||
else:
|
||||
axis_aligned_bbox = {}
|
||||
|
||||
# Neuen Identifier hinzufügen
|
||||
axis_aligned_bbox["axis_aligned_bbox"] = {
|
||||
"min_point": str(min_point),
|
||||
"max_point": str(max_point),
|
||||
"translation_parameters": translation_parameters
|
||||
|
||||
}
|
||||
|
||||
# Zuordnungen in JSON-Datei schreiben
|
||||
with open(json_file_path, 'w') as json_file:
|
||||
json.dump(axis_aligned_bbox, json_file, indent=4)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
# This function is used to add information about the used spatial reference system to the json file
|
||||
def addCRSToJSON(root, json_file_path):
|
||||
specifyVersion()
|
||||
# obtain the envelope object
|
||||
envelopes = []
|
||||
for envelope in root.getiterator('{%s}Envelope' % ns_gml):
|
||||
envelopes.append(envelope)
|
||||
if envelopes:
|
||||
# Extracting the srsName attribute from each Envelope
|
||||
srs_names = [envelope.get('srsName') for envelope in envelopes]
|
||||
srs_Dimensions = [envelope.get('srsDimension') for envelope in envelopes]
|
||||
elif not envelopes:
|
||||
srs_names = "Unkown"
|
||||
srs_Dimensions = "Unkown"
|
||||
|
||||
|
||||
used_srs = srs_names[0]
|
||||
# Prüfen, ob die JSON-Datei existiert und laden
|
||||
if os.path.exists(json_file_path):
|
||||
with open(json_file_path, 'r') as json_file:
|
||||
crs_info = json.load(json_file)
|
||||
else:
|
||||
crs_info = {}
|
||||
|
||||
# Neuen Identifier hinzufügen
|
||||
crs_info["CRS"] = {
|
||||
"srsName": used_srs,
|
||||
"srsDimensions": srs_Dimensions
|
||||
}
|
||||
|
||||
# Zuordnungen in JSON-Datei schreiben
|
||||
with open(json_file_path, 'w') as json_file:
|
||||
json.dump(crs_info, json_file, indent=4)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def claculateCornerTriangles(b, trans_param):
|
||||
buffered_box_points = getBufferedBBoxPoints(b)
|
||||
|
||||
# Translate the Bounding box into the local coordinate system
|
||||
# buffered_box_points, _ = addTranslationParameters(buffered_box_points_global, [], trans_param=trans_param)
|
||||
|
||||
# Create triangles at the corners of the buffered bounding box
|
||||
corner_triangles = create_corner_triangles(buffered_box_points)
|
||||
|
||||
# Convert the triangles to lists
|
||||
corner_triangles = [np.array(triangle).tolist() for triangle in corner_triangles]
|
||||
|
||||
print("\nCorner Triangles:")
|
||||
for i, triangle in enumerate(corner_triangles):
|
||||
print(f"Triangle {i + 1}: {triangle}")
|
||||
|
||||
return corner_triangles
|
||||
|
||||
|
||||
# diese funktion dient dazu ein JSON file zu schreiben um die meta informationen über die einzelnen objekte zuspeichern
|
||||
def add_identifier_to_json(filename, tag, parentID, gmlID, json_file_path):
|
||||
"""
|
||||
Adds the identifier information for one .obj file to a JSON file.
|
||||
|
||||
Parameters:
|
||||
- number (int): The number corresponding to the .obj file.
|
||||
- tag (str): The tag corresponding to the .obj file.
|
||||
- parentID (str): The parent ID corresponding to the .obj file.
|
||||
- gmlID (str): The gml ID corresponding to the .obj file.
|
||||
- json_file_path (str): Path to the JSON file where identifier information will be stored.
|
||||
"""
|
||||
|
||||
# Prüfen, ob die JSON-Datei existiert und laden
|
||||
if os.path.exists(json_file_path):
|
||||
with open(json_file_path, 'r') as json_file:
|
||||
identifiers = json.load(json_file)
|
||||
else:
|
||||
identifiers = {}
|
||||
|
||||
# Neuen Identifier hinzufügen
|
||||
identifiers[filename] = {
|
||||
'tag': tag,
|
||||
'parentID': parentID,
|
||||
'gmlID': gmlID
|
||||
}
|
||||
|
||||
# Zuordnungen in JSON-Datei schreiben
|
||||
with open(json_file_path, 'w') as json_file:
|
||||
json.dump(identifiers, json_file, indent=4)
|
||||
|
||||
print(f"Zuordnung für {filename} wurde gespeichert.")
|
||||
|
||||
|
||||
def perturb_points(points, perturbation_scale=1e-6):
|
||||
"""
|
||||
Perturb the points slightly to avoid degenerate cases.
|
||||
|
||||
Parameters:
|
||||
points (list of tuple of floats): A list where each element is a tuple (x, y, z) representing a 3D point.
|
||||
perturbation_scale (float): The maximum magnitude of the perturbation applied to each coordinate.
|
||||
|
||||
Returns:
|
||||
list of list of floats: Perturbed list of points.
|
||||
"""
|
||||
points_array = np.array(points)
|
||||
perturbation = np.random.uniform(-perturbation_scale, perturbation_scale, points_array.shape)
|
||||
perturbed_points = points_array + perturbation
|
||||
return perturbed_points.tolist()
|
||||
|
||||
|
||||
def write_obj_file(surfaces, filename, tag, parentid, gmlid, counter, path, tr_1, translation_parameters):
|
||||
for triangle in tr_1:
|
||||
triangle_local, _ = addTranslationParameters(triangle, [], trans_param=translation_parameters)
|
||||
surfaces.append(triangle_local)
|
||||
with open(filename, 'w') as file:
|
||||
vertex_index = 1
|
||||
for triangle in surfaces:
|
||||
for vertex in triangle:
|
||||
file.write(f"v {vertex[0]} {vertex[1]} {vertex[2]}\n")
|
||||
file.write(f"f {vertex_index} {vertex_index + 1} {vertex_index + 2}\n")
|
||||
vertex_index += 3
|
||||
add_identifier_to_json(filename, tag, parentid, gmlid, (path + "index.json"))
|
||||
|
||||
|
||||
def remove_reccuring(list_vertices):
|
||||
"""Removes recurring vertices, which messes up the triangulation.
|
||||
Inspired by http://stackoverflow.com/a/1143432"""
|
||||
# last_point = list_vertices[-1]
|
||||
list_vertices_without_last = list_vertices[:-1]
|
||||
found = set()
|
||||
for item in list_vertices_without_last:
|
||||
if str(item) not in found:
|
||||
yield item
|
||||
found.add(str(item))
|
||||
|
||||
|
||||
def separate_string(s):
|
||||
# Define the regex pattern
|
||||
pattern = r'\{([^}]*)\}(.*)'
|
||||
# Search for the pattern in the input string
|
||||
match = re.search(pattern, s)
|
||||
if match:
|
||||
# Extract the parts
|
||||
inside_braces = match.group(1)
|
||||
outside_braces = match.group(2)
|
||||
return inside_braces, outside_braces
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def specifyVersion():
|
||||
global ns_citygml
|
||||
global ns_gml
|
||||
global ns_bldg
|
||||
global ns_xsi
|
||||
global ns_xAL
|
||||
global ns_xlink
|
||||
global ns_dem
|
||||
global ns_con
|
||||
global ns_app
|
||||
global ns_pcl
|
||||
global ns_gen
|
||||
global ns_gss
|
||||
global ns_pfx0
|
||||
global ns_gsr
|
||||
global ns_tran
|
||||
global ns_gmd
|
||||
global ns_gts
|
||||
global ns_veg
|
||||
global ns_frn
|
||||
global ns_tun
|
||||
global ns_wtr
|
||||
global nsmap
|
||||
|
||||
if config.getVersion() == 1:
|
||||
# -- Name spaces for CityGML 2.0
|
||||
ns_citygml = "http://www.opengis.net/citygml/1.0"
|
||||
ns_gml = "http://www.opengis.net/gml"
|
||||
ns_bldg = "http://www.opengis.net/citygml/building/1.0"
|
||||
ns_tran = "http://www.opengis.net/citygml/transportation/1.0"
|
||||
ns_veg = "http://www.opengis.net/citygml/vegetation/1.0"
|
||||
ns_gen = "http://www.opengis.net/citygml/generics/1.0"
|
||||
ns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
ns_xAL = "urn:oasis:names:tc:ciq:xsdschema:xAL:1.0"
|
||||
ns_xlink = "http://www.w3.org/1999/xlink"
|
||||
ns_dem = "http://www.opengis.net/citygml/relief/1.0"
|
||||
ns_frn = "http://www.opengis.net/citygml/cityfurniture/1.0"
|
||||
ns_tun = "http://www.opengis.net/citygml/tunnel/1.0"
|
||||
ns_wtr = "http://www.opengis.net/citygml/waterbody/1.0"
|
||||
ns_brid = "http://www.opengis.net/citygml/bridge/1.0"
|
||||
ns_app = "http://www.opengis.net/citygml/appearance/1.0"
|
||||
if config.getVersion() == 2:
|
||||
# -- Name spaces for CityGML 2.0
|
||||
ns_citygml = "http://www.opengis.net/citygml/2.0"
|
||||
ns_gml = "http://www.opengis.net/gml"
|
||||
ns_bldg = "http://www.opengis.net/citygml/building/2.0"
|
||||
ns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
ns_xAL = "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"
|
||||
ns_xlink = "http://www.w3.org/1999/xlink"
|
||||
ns_dem = "http://www.opengis.net/citygml/relief/2.0"
|
||||
elif config.getVersion() == 3:
|
||||
# -- Name spaces for CityGML 3.0
|
||||
ns_citygml = "http://www.opengis.net/citygml/3.0"
|
||||
ns_con = "http://www.opengis.net/citygml/construction/3.0"
|
||||
ns_xlink = "http://www.w3.org/1999/xlink"
|
||||
ns_gml = "http://www.opengis.net/gml/3.2"
|
||||
ns_bldg = "http://www.opengis.net/citygml/building/3.0"
|
||||
ns_app = "http://www.opengis.net/citygml/appearance/3.0"
|
||||
ns_pcl = "http://www.opengis.net/citygml/pointcloud/3.0"
|
||||
ns_gen = "http://www.opengis.net/citygml/generics/3.0"
|
||||
ns_gss = "http://www.isotc211.org/2005/gss"
|
||||
ns_pfx0 = "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"
|
||||
ns_gsr = "http://www.isotc211.org/2005/gsr"
|
||||
ns_xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
ns_tran = "http://www.opengis.net/citygml/transportation/3.0"
|
||||
ns_gmd = "http://www.isotc211.org/2005/gmd"
|
||||
ns_gts = "http://www.isotc211.org/2005/gts"
|
||||
ns_veg = "http://www.opengis.net/citygml/vegetation/3.0"
|
||||
ns_xAL = "urn:oasis:names:tc:ciq:xal:3"
|
||||
ns_dem = "http://www.opengis.net/citygml/relief/3.0"
|
||||
ns_frn = "http://www.opengis.net/citygml/cityfurniture/3.0"
|
||||
ns_tun = "http://www.opengis.net/citygml/tunnel/3.0"
|
||||
ns_wtr = "http://www.opengis.net/citygml/waterbody/3.0"
|
||||
|
||||
nsmap = {
|
||||
None: ns_citygml,
|
||||
'gml': ns_gml,
|
||||
'bldg': ns_bldg,
|
||||
'xsi': ns_xsi,
|
||||
'xAL': ns_xAL,
|
||||
'xlink': ns_xlink,
|
||||
'dem': ns_dem
|
||||
}
|
||||
|
||||
|
||||
def compute_convex_hull(points):
|
||||
"""
|
||||
Computes the convex hull of a set of 3D points using Open3D, including triangulation of the hull's faces.
|
||||
|
||||
Parameters:
|
||||
points (list of tuple of floats): A list where each element is a tuple (x, y, z) representing a 3D point.
|
||||
|
||||
Returns:
|
||||
list: A list of faces, where each face is a list of vertex coordinates forming that face.
|
||||
"""
|
||||
# Convert the list of points to a NumPy array
|
||||
points_array = np.array(points)
|
||||
perturbed_points = perturb_points(points_array)
|
||||
|
||||
# Create an Open3D PointCloud object
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(perturbed_points)
|
||||
|
||||
# Compute the convex hull
|
||||
hull, triangles = pcd.compute_convex_hull()
|
||||
|
||||
# Extract vertices and faces (triangles)
|
||||
hull_vertices = np.asarray(hull.vertices)
|
||||
hull_triangles = np.asarray(hull.triangles)
|
||||
|
||||
# Prepare the faces in the desired format
|
||||
faces = []
|
||||
for triangle in hull_triangles:
|
||||
face = [list(hull_vertices[vertex]) for vertex in triangle]
|
||||
faces.append(face)
|
||||
return faces
|
||||
|
||||
|
||||
def process_polygon(p, trans_param):
|
||||
e = p[0]
|
||||
i = p[1]
|
||||
e_trans, i_trans = addTranslationParameters(e, i, trans_param=trans_param)
|
||||
t = p3dm.triangulation(e_trans, i_trans)
|
||||
# print(f"t: {t}")
|
||||
return t
|
||||
|
||||
|
||||
def process_polygons_parallel(polys, trans_param):
|
||||
data = []
|
||||
results = []
|
||||
for poly in polys:
|
||||
e, i = m3dm.polydecomposer(poly)
|
||||
epoints = m3dm.GMLpoints(e[0])
|
||||
# -- Clean recurring points, except the last one
|
||||
last_ep = epoints[-1]
|
||||
epoints_clean = list(remove_reccuring(epoints))
|
||||
epoints_clean.append(last_ep)
|
||||
# -- LinearRing(s) forming the interior
|
||||
irings = []
|
||||
for iring in i:
|
||||
ipoints = m3dm.GMLpoints(iring)
|
||||
# -- Clean them in the same manner as the exterior ring
|
||||
last_ip = ipoints[-1]
|
||||
ipoints_clean = list(remove_reccuring(ipoints))
|
||||
ipoints_clean.append(last_ip)
|
||||
irings.append(ipoints_clean)
|
||||
if len(epoints_clean) > 4:
|
||||
t = process_polygon([epoints_clean, irings], trans_param=trans_param)
|
||||
results.append(t)
|
||||
# data.append(poly_components)
|
||||
if len(epoints_clean) == 4:
|
||||
epoints_clean_translated, _ = addTranslationParameters(epoints_clean, [], trans_param=trans_param)
|
||||
results.append([epoints_clean_translated])
|
||||
|
||||
# t = process_polygon(poly)
|
||||
# results.append(t)
|
||||
|
||||
# cpu_cores = os.cpu_count()
|
||||
# print(f'Number of available CPU cores (using os): {cpu_cores}')
|
||||
# with ThreadPoolExecutor(max_workers=cpu_cores) as executor:
|
||||
# # Submitting all tasks
|
||||
# futures = [executor.submit(process_polygon, p) for p in data]
|
||||
# # Collecting results
|
||||
# for future in futures:
|
||||
# result = future.result() # This will re-raise any exception caught during the execution of the task
|
||||
# results.append(result)
|
||||
return results
|
||||
|
||||
|
||||
# this is an experimental method for parallelization
|
||||
def processOpening(o, path, buildingid, overall_counter, tr_1, trans_param, b_counter):
|
||||
for child in o.getiterator():
|
||||
unique_identifier = child.xpath("@g:id", namespaces={'g': ns_gml})
|
||||
if child.tag == '{%s}Window' % ns_bldg or child.tag == '{%s}Door' % ns_bldg:
|
||||
polys = m3dm.polygonFinder(o)
|
||||
t = process_polygons_parallel(polys, trans_param=trans_param)
|
||||
triangles = []
|
||||
for poly in t:
|
||||
for tr in poly:
|
||||
triangles.append(tr)
|
||||
filename = path + str(b_counter) + "_" + str(overall_counter) + ".obj"
|
||||
write_obj_file(triangles, filename, str(child.tag), buildingid, unique_identifier, overall_counter, path,
|
||||
tr_1, trans_param)
|
||||
|
||||
|
||||
def getAllExteriorPoints(polys):
|
||||
data = []
|
||||
for poly in polys:
|
||||
e, i = m3dm.polydecomposer(poly)
|
||||
epoints = m3dm.GMLpoints(e[0])
|
||||
# -- Clean recurring points, except the last one
|
||||
last_ep = epoints[-1]
|
||||
epoints_clean = list(remove_reccuring(epoints))
|
||||
epoints_clean.append(last_ep)
|
||||
for point in epoints_clean:
|
||||
data.append(point)
|
||||
return data
|
||||
|
||||
|
||||
def processWithApproximatedWindows(o, path, buildingid, overall_counter, tr_1, translation_parameters, b_counter):
|
||||
for child in o.getiterator():
|
||||
unique_identifier = child.xpath("@g:id", namespaces={'g': ns_gml})
|
||||
if child.tag == '{%s}Window' % ns_bldg or child.tag == '{%s}Door' % ns_bldg:
|
||||
polys = m3dm.polygonFinder(o)
|
||||
exterior_points = getAllExteriorPoints(polys)
|
||||
t_global = compute_convex_hull(exterior_points)
|
||||
_, t = addTranslationParameters(e=[], i=t_global, trans_param=translation_parameters)
|
||||
filename = path + str(b_counter) + "_" + str(overall_counter) + ".obj"
|
||||
write_obj_file(t, filename, str(child.tag), buildingid, unique_identifier, overall_counter, path, tr_1,
|
||||
translation_parameters=translation_parameters)
|
||||
|
||||
|
||||
# This function is used to im port a bounding box that is associated to the corresponding building component
|
||||
# It still has to be tested if it works
|
||||
def importBoundingBox(pathToBoundingBoxFile, trans_param):
|
||||
print("Hier")
|
||||
keys = ["_xmin", "_xmax", "_ymin", "_ymax", "_zmin", "_zmax"]
|
||||
values = {}
|
||||
|
||||
with open(pathToBoundingBoxFile, 'r') as file:
|
||||
for _ in range(15):
|
||||
line = file.readline()
|
||||
for key in keys:
|
||||
match = re.search(f'"{key}"\s*:\s*([-0-9.]+)', line)
|
||||
if match:
|
||||
values[key] = float(match.group(1))
|
||||
|
||||
if not all(k in values for k in keys):
|
||||
raise ValueError("Missing bounding box values in the first 15 lines")
|
||||
|
||||
min_x, max_x = values["_xmin"], values["_xmax"]
|
||||
min_y, max_y = values["_ymin"], values["_ymax"]
|
||||
min_z, max_z = values["_zmin"], values["_zmax"]
|
||||
|
||||
print(f"min x: {min_x} min y: {min_y} , min z: {min_z}")
|
||||
print(f"max x: {max_x} max y: {max_y} , max z: {max_z}")
|
||||
|
||||
box_points = np.array([
|
||||
[min_x, min_y, min_z],
|
||||
[max_x, min_y, min_z],
|
||||
[min_x, max_y, min_z],
|
||||
[min_x, min_y, max_z],
|
||||
[max_x, max_y, max_z],
|
||||
[min_x, max_y, max_z],
|
||||
[max_x, min_y, max_z],
|
||||
[max_x, max_y, min_z]
|
||||
])
|
||||
|
||||
# Create triangles at the corners of the buffered bounding box
|
||||
corner_triangles = create_corner_triangles(box_points)
|
||||
|
||||
# Convert the triangles to lists
|
||||
corner_triangles = [np.array(triangle).tolist() for triangle in corner_triangles]
|
||||
|
||||
print("\nCorner Triangles:")
|
||||
for i, triangle in enumerate(corner_triangles):
|
||||
print(f"Triangle {i + 1}: {triangle}")
|
||||
|
||||
return corner_triangles
|
||||
|
||||
|
||||
def separateComponents(b, path, APPROXIMATEWINDOWS, ADDBOUNDINGBOX, ADDBOUNDINGBOXJSON, TRANSLATEBUILDINGS,
|
||||
IMPORTBOUNDINGBOX, b_counter):
|
||||
if TRANSLATEBUILDINGS:
|
||||
# Step 1: Obtain the axis oriented bounding box of the building
|
||||
bounding_box_points = getBufferedBBoxPoints(b)
|
||||
|
||||
# Step 2 calculate the mean value of the points that the bbox points
|
||||
translation_parameters = np.mean(bounding_box_points, axis=0)
|
||||
|
||||
if not TRANSLATEBUILDINGS: # todo: nocheinmal üerlegen ob man hier nicht vielleicht besser elif oder so nehmen sollte
|
||||
translation_parameters = []
|
||||
|
||||
if IMPORTBOUNDINGBOX != None:
|
||||
ADDBOUNDINGBOX = False # make sure that the bounding box is not calculated
|
||||
|
||||
# Option to include the small triangles to mark the buffered bounding box
|
||||
if ADDBOUNDINGBOX:
|
||||
tr_1 = claculateCornerTriangles(b, trans_param=translation_parameters)
|
||||
elif not ADDBOUNDINGBOX:
|
||||
tr_1 = []
|
||||
global overall_counter
|
||||
overall_counter = 0
|
||||
output = {}
|
||||
specifyVersion()
|
||||
# comprehensive list of semantic surfaces
|
||||
semanticSurfaces = ['GroundSurface', 'WallSurface', 'RoofSurface', 'ClosureSurface', 'CeilingSurface',
|
||||
'InteriorWallSurface', 'FloorSurface', 'OuterCeilingSurface', 'OuterFloorSurface', 'Door',
|
||||
"outerBuildingInstallation",
|
||||
'Window', "BuildingInstallation", "BuildingConstructiveElement"]
|
||||
|
||||
for semanticSurface in semanticSurfaces:
|
||||
output[semanticSurface] = []
|
||||
|
||||
# get the building id for the building
|
||||
buildingid = b.xpath("@g:id", namespaces={'g': ns_gml})
|
||||
|
||||
# Handle the case when a building has no gml:id - This should however never occur...
|
||||
if not buildingid:
|
||||
buildingid = b_counter
|
||||
|
||||
# Todo: Muss noch implementiert werden
|
||||
# Import the bounding box
|
||||
if IMPORTBOUNDINGBOX != None:
|
||||
pathToBoundingBoxFile = IMPORTBOUNDINGBOX + "/" + str(buildingid) + ".json"
|
||||
tr_1 = importBoundingBox(pathToBoundingBoxFile=pathToBoundingBoxFile, trans_param=translation_parameters)
|
||||
|
||||
if config.getVersion() != 3:
|
||||
openings = []
|
||||
openingpolygons = []
|
||||
for child in b.getiterator():
|
||||
if child.tag == '{%s}opening' % ns_bldg:
|
||||
openings.append(child)
|
||||
for o in child.findall('.//{%s}Polygon' % ns_gml):
|
||||
openingpolygons.append(o)
|
||||
|
||||
for o in openings:
|
||||
# print("approximate windows: ", APPROXIMATEWINDOWS)
|
||||
if APPROXIMATEWINDOWS:
|
||||
processWithApproximatedWindows(o, path, buildingid, overall_counter, tr_1=tr_1,
|
||||
translation_parameters=translation_parameters, b_counter=b_counter)
|
||||
if not APPROXIMATEWINDOWS:
|
||||
processOpening(o, path, buildingid, overall_counter, tr_1, trans_param=translation_parameters,
|
||||
b_counter=b_counter)
|
||||
if ADDBOUNDINGBOXJSON:
|
||||
writeBBOXJSON(b, overall_counter=overall_counter, path=path, b_counter=b_counter,
|
||||
trans_param=translation_parameters)
|
||||
overall_counter += 1
|
||||
|
||||
if config.getVersion() == 3:
|
||||
openingpolygons = []
|
||||
print("Component separation for CityGML 3.0 is not implemented yet.")
|
||||
# todo: muss noch implementiert werden
|
||||
|
||||
# -- Process other thematic boundaries
|
||||
for cl in output:
|
||||
cls = []
|
||||
for child in b.getiterator():
|
||||
if child.tag == '{%s}%s' % (ns_bldg, cl):
|
||||
cls.append(child)
|
||||
|
||||
for feature in cls:
|
||||
# -- If it is the first feature, print the object identifier
|
||||
unique_identifier = feature.xpath("@g:id", namespaces={
|
||||
'g': ns_gml})
|
||||
if str(unique_identifier) != "[]" or str(unique_identifier) == "[]":
|
||||
cleaned_filename = str(unique_identifier)
|
||||
# -- This is not supposed to happen, but just to be sure...
|
||||
if feature.tag == '{%s}Window' % ns_bldg or feature.tag == '{%s}Door' % ns_bldg:
|
||||
continue
|
||||
tag = feature.tag
|
||||
_, cleaned_tag = separate_string(tag)
|
||||
# -- Find all polygons in this semantic boundary hierarchy
|
||||
poly_t = []
|
||||
t_ges = []
|
||||
number_of_polygons = len(feature.findall('.//{%s}Polygon' % ns_gml))
|
||||
pcounter = 0
|
||||
print(f"there are {number_of_polygons} polygons there!")
|
||||
for p in feature.findall('.//{%s}Polygon' % ns_gml):
|
||||
found_opening = False
|
||||
for optest in openingpolygons:
|
||||
if p == optest:
|
||||
found_opening = True
|
||||
break
|
||||
# -- If there is an opening skip it
|
||||
if found_opening:
|
||||
pass
|
||||
else:
|
||||
# -- Decompose the polygon into exterior and interior
|
||||
e, i = m3dm.polydecomposer(p)
|
||||
# -- Points forming the exterior LinearRing
|
||||
epoints = m3dm.GMLpoints(e[0])
|
||||
# -- Clean recurring points, except the last one
|
||||
last_ep = epoints[-1]
|
||||
epoints_clean = list(remove_reccuring(epoints))
|
||||
epoints_clean.append(last_ep)
|
||||
|
||||
# -- LinearRing(s) forming the interior
|
||||
irings = []
|
||||
for iring in i:
|
||||
ipoints = m3dm.GMLpoints(iring)
|
||||
# -- Clean them in the same manner as the exterior ring
|
||||
last_ip = ipoints[-1]
|
||||
ipoints_clean = list(remove_reccuring(ipoints))
|
||||
ipoints_clean.append(last_ip)
|
||||
irings.append(ipoints_clean)
|
||||
|
||||
# Applying the translation parameters
|
||||
e_trans, i_trans = addTranslationParameters(e=epoints_clean, i=irings,
|
||||
trans_param=translation_parameters)
|
||||
|
||||
try:
|
||||
if len(epoints_clean) > 4:
|
||||
t = p3dm.triangulation(e_trans, i_trans)
|
||||
# print("Nur drei Punkte")
|
||||
poly_t.append(t)
|
||||
if len(epoints_clean) == 4:
|
||||
t = e_trans[0:3]
|
||||
poly_t.append([t])
|
||||
if len(epoints_clean) < 3:
|
||||
t = []
|
||||
# print("Empty Surface!")
|
||||
except:
|
||||
t = []
|
||||
|
||||
for surfaces in poly_t:
|
||||
t_ges = t_ges + surfaces
|
||||
|
||||
pcounter += 1
|
||||
if pcounter % 100 == 0:
|
||||
print(pcounter)
|
||||
|
||||
filename = path + str(b_counter) + "_" + str(overall_counter) + ".obj"
|
||||
if ADDBOUNDINGBOXJSON:
|
||||
writeBBOXJSON(b, overall_counter=overall_counter, path=path, b_counter=b_counter,
|
||||
trans_param=translation_parameters)
|
||||
write_obj_file(t_ges, filename, str(feature.tag), buildingid, cleaned_filename, overall_counter, path,
|
||||
tr_1, translation_parameters=translation_parameters)
|
||||
overall_counter += 1
|
||||
|
||||
print("Segmentation finished!")
|
||||
return 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue