##!/usr/bin/python # -*- coding: utf-8 -*- # The MIT License (MIT) # This code is part of the CityGML2OBJs package # Copyright (c) 2023 # Thomas Fröch # Technische Universität München (TUM) # thomas.froech@tum.de # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import markup3dmodule as m3dm from decimal import Decimal, getcontext import numpy as np # Setting the precision of Decimal getcontext().prec = 28 def performStableAddition(number1, number2): # Input: string number1, string number 2, output: string sum # Determine the number of positions after the comma. # print("Number 1 totally before: ", number1) # print("Number 2 totally before: ", number2) try: n_after_comma_number1 = len(number1.split(".")[1]) except: n_after_comma_number1 = 0 try: n_after_comma_number2 = len(number2.split(".")[1]) except: n_after_comma_number2 = 0 lengths = [n_after_comma_number1, n_after_comma_number2] # convert float numbers into integers by removing the comma # print("Number 1 before all: ", number1) # print("Number 2 before all: ", number2) number1 = number1.replace(".", "") number2 = number2.replace(".", "") # print("Number 1 after: ", number1) # print("Number 2 after: ", number2) # find the number with more positions after the comma abs_lengths = np.abs(lengths) # Erstellen eines Arrays mit den Beträgen max_index = np.argmax(abs_lengths) # Distinguish the two different cases # Case 1: number one has more positions after the comma than number two if max_index == 0: # Find the difference of positions after the comma n_positions_difference = (lengths[0] - lengths[1]) # print("n_positions_difference: ", n_positions_difference) # Fill up the missing digits of number 2 with zeros # print("number2 before: ", number2) for i in range(n_positions_difference): number2 = number2 + "0" # print("number2_after", number2) # print("Number1: ", number1) # convert both stings into integers number1_int = np.double(number1) number2_int = np.double(number2) # Add both numbers number_sum = number1_int + number2_int # convert back into string format_string = "{:.0f}" number_sum_string = format_string.format(number_sum) # Insert the comma at the correct position length_of_number = len(number_sum_string.replace("-", "")) if length_of_number >= lengths[max_index]: if n_after_comma_number1 != 0: number_sum_string = number_sum_string[:-(lengths[0])] + "." + number_sum_string[-(lengths[0]):] return number_sum_string else: number_sum_string = number_sum_string return number_sum_string elif length_of_number < n_after_comma_number1: new_string = "" for i in range(len(number_sum_string)): if number_sum_string[i] == "-": new_string += "0." else: new_string += number_sum_string[i] number_sum_string = new_string return number_sum_string else: number_sum_string = number_sum_string return number_sum_string # Case 2: number two has more positions after the comma than number one else: # find the difference of positions after the comma n_positions_difference = lengths[1] - lengths[0] # Fill up the missing digits of number 2 with zeros for i in range(n_positions_difference): number1 = number1 + "0" # convert both stings into integers number1_int = Decimal(number1) number2_int = Decimal(number2) # Add both numbers number_sum = number1_int + number2_int # convert back into string format_string = "{:.0f}" number_sum_string = format_string.format(number_sum) # Insert the comma at the correct position length_of_number = len(number_sum_string.replace("-", "")) if length_of_number > n_after_comma_number2: if n_after_comma_number2 != 0: number_sum_string = number_sum_string[:-(lengths[1])] + "." + number_sum_string[-(lengths[1]):] return number_sum_string else: number_sum_string = number_sum_string return number_sum_string elif length_of_number < n_after_comma_number2: new_string = "" for i in range(len(number_sum_string)): if number_sum_string[i] == "-": new_string += "0." else: new_string += number_sum_string[i] number_sum_string = new_string return number_sum_string else: number_sum_string = number_sum_string return number_sum_string # This function is used in order to extract all the envelopes from the CityGML-File # These envelopes are going to be used in order to determine the translation parameters later def getEnvelopes(root, ns_bldg, ns_gml, ns_citygml): envelopes = [] for envelope in root.getiterator('{%s}Envelope' % ns_gml): envelopes.append(envelope) return envelopes # This function is used in order to calculate the translation parameters from the # envelopes that were previously extracted from the envelopes def getTranslationParameters(envelopes, ns_gml): # Setting up some initial values dx = Decimal("0") dy = Decimal("0") lowerCorner = [] upperCorner = [] # Iterating through all the envelopes in the CityGML-File for envelope in envelopes: # print(" Envelope: ", envelope) # Finding the upper and the lower corner for child in envelope.getchildren(): if child.tag == '{%s}lowerCorner' % ns_gml: lowerCorner.append(child.text) elif child.tag == '{%s}upperCorner' % ns_gml: upperCorner.append(child.text) # Converting into Decimal pointCounter = 0 for point in lowerCorner: dy = dy + (Decimal(point.split(" ")[0])) dx = dx + (Decimal(point.split(" ")[1])) pointCounter = pointCounter + 1 dyret = -dy / pointCounter dxret = -dx / pointCounter return [Decimal(str(int(dxret))), Decimal(str(int(dyret)))] # This function is used to Parse the text from the CityGML file to Decimal numbers and # to aplly the previously calculated translation parameters # Notice: this code only allows ONE "gml:PosList-Element" for each Interior and exterior of a Polygon # If there are more than just one,just the first one is going to be transformed def splitAndApplyTrafo(coordString, transParam): # Splitting the coordinate string by empty spaces split = coordString.split(" ") alalala = len(split) # Apply the Trafo if split[0] == '': split.pop(0) if split[-1] == "": split.pop(-1) counter = 0 length = int(len(split)) length_new = int(length / 3) for i in range(length_new): # print("y") split[counter] = performStableAddition(split[counter], str(transParam[1])) # print("x") split[counter + 1] = performStableAddition(split[counter + 1], str(transParam[0])) # print("z") split[counter + 2] = performStableAddition(split[counter + 2], str(transParam[2])) counter += 3 # converting back to a string translated = "" for i in split: if len(translated) == 0: translated = str(i) else: translated = translated + " " + str(i) return translated # This code is used in order to find all the coordinates that are defined in the CityGML-File # Please Notice: the search for coordinates here has the same limitations as the search for coordinates that # is used in the "CityGML2OBJ" functionality! def applyTranslationToCityGML(CITYGML, root, transParam, ns_citygml, ns_gml, ns_frn, ns_veg, filename): # Iterate over all the cityObjectMembers for obj in root.getiterator('{%s}cityObjectMember' % ns_citygml): # Iterate over all the children of cityObject Member for child in obj.getchildren(): # Exclude all the implicitly referenced objects from the transformation if child.findall( './/{%s}ImplicitGeometry' % ns_citygml) == []: polys = m3dm.polygonFinder(child) # Iterate over all the polygons of the children of cityObjectMember for poly in polys: # decompose all the polygons in the interior and exterior rings exter, inter = m3dm.polydecomposer(poly) # iterate over all the exterior rings for e in exter: # find all the coordinates that are stored as a "posList" if len(e.findall('.//{%s}posList' % ns_gml)) > 0: points_tmp = e.findall('.//{%s}posList' % ns_gml)[0].text points = points_tmp.replace('\n', ' ') translated = splitAndApplyTrafo(points, transParam) # print("Before: ", e.findall('.//{%s}posList' % ns_gml)[0].text) e.findall('.//{%s}posList' % ns_gml)[0].text = translated # print("Result: ", e.findall('.//{%s}posList' % ns_gml)[0].text) # find all the coordinates that are stored as "pos" elif len(e.findall('.//{%s}pos' % ns_gml)) > 0: points = e.findall('.//{%s}pos' % ns_gml) counter = 0 for k in points: translated = splitAndApplyTrafo(k.text, transParam) e.findall('.//{%s}pos' % ns_gml)[counter].text = translated counter = counter + 1 # iterate over all the interior rings for i in inter: # find all the coordinates that are stored as a "posList" if len(i.findall('.//{%s}posList' % ns_gml)) > 0: points_tmp = i.findall('.//{%s}posList' % ns_gml)[0].text points = points_tmp.replace('\n', ' ') translated = splitAndApplyTrafo(points, transParam) i.findall('.//{%s}posList' % ns_gml)[0].text = translated # find all the coordinates that are stored as "pos" elif len(i.findall('.//{%s}pos' % ns_gml)) > 0: points = i.findall('.//{%s}pos' % ns_gml) counter = 0 for k in points: translated = splitAndApplyTrafo(k.text, transParam) i.findall('.//{%s}pos' % ns_gml)[counter].text = translated counter = counter + 1 else: # This condition is used in order to transform the reference points of the implicitly defined geometries # Step 1: find all the reference points: referencePoints = child.findall('.//{%s}referencePoint' % ns_citygml) for referencePoint in referencePoints: points_tmp = referencePoint.findall('.//{%s}pos' % ns_gml) points = points_tmp.replace('\n', ' ') counter = 0 for l in points: translated = splitAndApplyTrafo(l.text, transParam) referencePoint.findall('.//{%s}pos' % ns_gml)[counter].text = translated counter = counter + 1 # Iterate over all the envelopes for envelope in root.getiterator('{%s}Envelope' % ns_gml): lowerCorner = envelope.findall('.//{%s}lowerCorner' % ns_gml)[0].text upperCorner = envelope.findall('.//{%s}upperCorner' % ns_gml)[0].text translatedLowerCorner = splitAndApplyTrafo(lowerCorner, transParam) translatedUpperCorner = splitAndApplyTrafo(upperCorner, transParam) envelope.findall('.//{%s}lowerCorner' % ns_gml)[0].text = translatedLowerCorner envelope.findall('.//{%s}upperCorner' % ns_gml)[0].text = translatedUpperCorner CITYGML.write(filename + "_local_" + ".gml") return root # This function is used in order to write the previously calculated translation parameters to a # designated .txt file. The use of this functionality is optional and can be activated by setting the # optional "write2file" parameter to "True" when calling the "translateToLocalCRS" - function. def writeTransparam2File(filename, directory, transParam): textfileName = directory + filename + "_Translation_Parameters.txt" f = open(textfileName, "w") f.write("This file contains the translation parameters that were applied to the original CityGML file." + "\n" + "Conversion tool developed by Filip Biljecki, TU Delft " + "\n" + "Conversion tool extended by Thomas Fröch, TUM " + "\n" + "see more at Github:" + "\n" + "https://github.com/tudelft3d/CityGML2OBJs" + "\n" + "\n") key = ['y', 'x', 'z'] for i in range(len(transParam)): f.write(key[i] + ': ' + str(transParam[i]) + ' ') f.close() print("Translation parameters written to: " + textfileName) return 0 def translateToLocalCRS(CITYGML, file, root, ns_bldg, ns_gml, ns_citygml, ns_frn, ns_veg, directory, write2file=False, applyHeight=Decimal("0")): envelopes = getEnvelopes(root, ns_bldg, ns_gml, ns_citygml) transParam = getTranslationParameters(envelopes, ns_gml) transParam.append(applyHeight) if write2file == True: writeTransparam2File(file, directory, transParam) applyTranslationToCityGML(CITYGML, root, transParam, ns_citygml, ns_gml, ns_frn, ns_veg, file) return 0