From 9a40cfa54b41f0650a79205b4e93c706fda15e49 Mon Sep 17 00:00:00 2001 From: Pilar Date: Thu, 25 Mar 2021 09:11:42 -0400 Subject: [PATCH] testing building separation in storeys with the new configuration of geometry using polygons and polyhedrons (NOT working yet). --- city_model_structure/attributes/polyhedron.py | 12 ++-- city_model_structure/building.py | 64 ++++++++++++++----- city_model_structure/city.py | 3 +- city_model_structure/city_object.py | 4 +- config/configuration.ini | 1 + helpers/configuration_helper.py | 8 +++ tests/test_building.py | 42 ++++++++++++ tests/test_geometry_factory.py | 14 +--- 8 files changed, 109 insertions(+), 39 deletions(-) create mode 100644 tests/test_building.py diff --git a/city_model_structure/attributes/polyhedron.py b/city_model_structure/attributes/polyhedron.py index d04a47b9..ecf6336d 100644 --- a/city_model_structure/attributes/polyhedron.py +++ b/city_model_structure/attributes/polyhedron.py @@ -376,8 +376,7 @@ class Polyhedron: self._max_z = ConfigurationHelper().min_coordinate for polygon in self._polygons: for point in polygon.points: - if self._max_z < point[2]: - self._max_z = point[2] + self._max_z = max(self._max_z, point[2]) return self._max_z @property @@ -450,15 +449,14 @@ class Polyhedron: return self._min_x @property - def center(self): + def centroid(self): """ Polyhedron centroid :return: [x,y,z] """ - x = (self.max_x + self.min_x) / 2 - y = (self.max_y + self.min_y) / 2 - z = (self.max_z + self.min_z) / 2 - return [x, y, z] + if self._centroid is None: + self._centroid = self.trimesh.centroid + return self._centroid def stl_export(self, full_path): """ diff --git a/city_model_structure/building.py b/city_model_structure/building.py index 862ad9ff..af2e08f7 100644 --- a/city_model_structure/building.py +++ b/city_model_structure/building.py @@ -14,6 +14,9 @@ from city_model_structure.attributes.thermal_zone import ThermalZone from city_model_structure.attributes.usage_zone import UsageZone from city_model_structure.city_object import CityObject from helpers.geometry_helper import GeometryHelper as gh +from helpers.configuration_helper import ConfigurationHelper +import math +from pathlib import Path class Building(CityObject): @@ -21,15 +24,14 @@ class Building(CityObject): Building(CityObject) class """ def __init__(self, name, lod, surfaces, terrains, year_of_construction, function, city_lower_corner): - # todo: take the default values out of the classes!! super().__init__(lod, surfaces, name) self._basement_heated = None self._attic_heated = None self._terrains = terrains self._year_of_construction = year_of_construction self._function = function - #todo: change lower_corner to building lower_corner instead city lower corner - self._lower_corner = city_lower_corner + self._city_lower_corner = city_lower_corner + self._building_lower_corner = None self._heated = None self._cooled = None self._average_storey_height = None @@ -43,6 +45,10 @@ class Building(CityObject): self._global_horizontal = dict() self._diffuse = dict() self._beam = dict() + self._min_x = ConfigurationHelper().max_coordinate + self._min_y = ConfigurationHelper().max_coordinate + self._min_z = ConfigurationHelper().max_coordinate + self._centroid = None # ToDo: Check this for LOD4 self._thermal_zones = [] @@ -54,8 +60,10 @@ class Building(CityObject): t_zones.bounded = [ThermalBoundary(s, [t_zones]) for s in t_zones.surfaces] surface_id = 0 for surface in self.surfaces: - surface.lower_corner = self._lower_corner surface.parent(self, surface_id) + self._min_x = min(self._min_x, surface.min_x) + self._min_y = min(self._min_y, surface.min_y) + self._min_z = min(self._min_z, surface.min_z) surface_id += 1 @property @@ -303,24 +311,47 @@ class Building(CityObject): """ self._beam = value + @property + def building_lower_corner(self): + if self._building_lower_corner is None: + self._building_lower_corner = [self._min_x, self._min_y, self._min_z] + return self._building_lower_corner + @property def storeys(self): storeys = [] - # todo: these values are not read yet from the files -# number_of_storeys = self.storeys_above_ground -# height = self.average_storey_height - number_of_storeys = 4 - height = 1.5 + height = self.average_storey_height + if self.storeys_above_ground is not None: + number_of_storeys = self.storeys_above_ground + else: + number_of_storeys = math.floor(float(self.max_height) / height) + 1 + print('number_of_storeys', number_of_storeys) + last_storey_height = float(self.max_height) - height*(number_of_storeys-1) + print('last_storey_height', last_storey_height) + if last_storey_height < height/2: + number_of_storeys -= 1 + print('number storeys', number_of_storeys) trimesh = self.simplified_polyhedron.trimesh normal_plane = [0, 0, -1] rest_trimesh = trimesh for n in range(0, number_of_storeys - 1): - # todo: I need the lower corner of the building!! - # point_plane = [self._lower_corner[0], self._lower_corner[1], self._lower_corner[2] + height] - point_plane = [self._lower_corner[0] + 0.5, self._lower_corner[1] + 0.5, self._lower_corner[2] + height * (n + 1)] + print(n) + point_plane = [self.building_lower_corner[0], self.building_lower_corner[1], + self.building_lower_corner[2] + height*(n+1)] + print('point plane', point_plane) + print('rest trimesh', rest_trimesh.volume) trimeshes = gh.divide_mesh_by_plane(rest_trimesh, normal_plane, point_plane) + print('number meshes', len(trimeshes)) storey = trimeshes[0] + file_name = 'storey_' + str(n) + '.obj' + path_name = (Path(__file__).parent.parent / 'tests' / 'tests_outputs' / file_name).resolve() + with open(path_name, 'w') as file: + file.write(storey.export(file_type='obj')) rest_trimesh = trimeshes[1] + file_name = 'rest_trimesh_' + str(n) + '.obj' + path_name = (Path(__file__).parent.parent / 'tests' / 'tests_outputs' / file_name).resolve() + with open(path_name, 'w') as file: + file.write(rest_trimesh.export(file_type='obj')) storeys.append(storey) storeys.append(rest_trimesh) return storeys @@ -338,7 +369,8 @@ class Building(CityObject): self._floor_area += surface.perimeter_polygon.area return self._floor_area - # todo: erase this function, only for rabeehs case - @floor_area.setter - def floor_area(self, value): - self._floor_area = value + @property + def centroid(self): + if self._centroid is None: + self._centroid = self.simplified_polyhedron.centroid + return self._centroid diff --git a/city_model_structure/city.py b/city_model_structure/city.py index f9516f55..8cd6ccff 100644 --- a/city_model_structure/city.py +++ b/city_model_structure/city.py @@ -15,7 +15,6 @@ from city_model_structure.building import Building from city_model_structure.city_object import CityObject from helpers.geometry_helper import GeometryHelper import math -import time class City: @@ -191,7 +190,7 @@ class City: selected_region_upper_corner = [center[0] + radius, center[1] + radius, center[2] + radius] selected_region_city = City(selected_region_lower_corner, selected_region_upper_corner, srs_name=self.srs_name) for city_object in self.city_objects: - location = city_object.location + location = city_object.centroid distance = math.sqrt(math.pow(location[0]-center[0], 2) + math.pow(location[1]-center[1], 2) + math.pow(location[2]-center[2], 2)) if distance < radius: diff --git a/city_model_structure/city_object.py b/city_model_structure/city_object.py index cab047bf..e49759fb 100644 --- a/city_model_structure/city_object.py +++ b/city_model_structure/city_object.py @@ -88,12 +88,12 @@ class CityObject: return None @property - def location(self): + def centroid(self): """ City object location :return: [x,y,z] """ - return self.simplified_polyhedron.center + return self.simplified_polyhedron.centroid @property def max_height(self): diff --git a/config/configuration.ini b/config/configuration.ini index b77cf6fa..394c1c61 100644 --- a/config/configuration.ini +++ b/config/configuration.ini @@ -2,3 +2,4 @@ [buildings] max_location_distance_for_shared_walls = 5.0 min_coordinate = -1.7976931348623157e+308 +max_coordinate = 1.7976931348623157e+308 diff --git a/helpers/configuration_helper.py b/helpers/configuration_helper.py index 0e2ea187..20ee7819 100644 --- a/helpers/configuration_helper.py +++ b/helpers/configuration_helper.py @@ -31,3 +31,11 @@ class ConfigurationHelper: :return: float """ return self._config.getfloat('buildings', 'min_coordinate') + + @property + def max_coordinate(self) -> float: + """ + Configured maximal coordinate value + :return: float + """ + return self._config.getfloat('buildings', 'max_coordinate') diff --git a/tests/test_building.py b/tests/test_building.py new file mode 100644 index 00000000..14940467 --- /dev/null +++ b/tests/test_building.py @@ -0,0 +1,42 @@ +""" +Building test +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from pathlib import Path +from unittest import TestCase +from imports.geometry_factory import GeometryFactory + + +class MyTestCase(TestCase): + """ + TestBuilding TestCase 1 + """ + def setUp(self) -> None: + """ + Test setup + :return: None + """ + self._city_gml = None + self._example_path = (Path(__file__).parent / 'tests_data').resolve() + + def _get_citygml(self, file): + if self._city_gml is None: + file_path = (self._example_path / file).resolve() + self._city_gml = GeometryFactory('citygml', file_path)._city_debug + self.assertIsNotNone(self._city_gml, 'city is none') + return self._city_gml + + def test_storeys_division(self): + file = 'kelowna.gml' + city = self._get_citygml(file) + for building in city.buildings: + if building.name == 'BLD126221': + building.average_storey_height = 1.5 + print(building.name) + print(building.volume) + print(building.floor_area) + print(building.max_height) + print(building.centroid) + print(len(building.storeys)) + diff --git a/tests/test_geometry_factory.py b/tests/test_geometry_factory.py index 8d56a706..38cb3410 100644 --- a/tests/test_geometry_factory.py +++ b/tests/test_geometry_factory.py @@ -2,6 +2,7 @@ TestGeometryFactory test and validate the city model structure geometric parameters SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +Contributors Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca """ import os from pathlib import Path @@ -79,7 +80,7 @@ class TestGeometryFactory(TestCase): for building in city.buildings: self.assertIsNotNone(building.name, 'building name is none') self.assertIsNotNone(building.lod, 'building lod is none') - self.assertIsNotNone(building.location, 'building location is none') + self.assertIsNotNone(building.centroid, 'building centroid is none') self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none') self.assertIsNotNone(building.function, 'building function is none') self.assertIsNotNone(building.volume, 'building volume is none') @@ -223,17 +224,6 @@ class TestGeometryFactory(TestCase): for building in city.buildings: self.assertIsNotNone(building.volume, 'building volume is none') - def test_divide_mesh_by_plane(self): - file = 'FZK-Haus-LoD-all-KIT-IAI-KHH-B36-V1.gml' - # todo @Guille: this file has 5 lods (0, 1, 2, 3 and 4), all as one single city_object. - # Only lod1 is read and saved - city = self._get_citygml(file) - for building in city.buildings: - print(building.name) - print(building.volume) - print(building.thermal_zones[0].floor_area) - print(len(building.storeys)) - def test_surface(self): coordinates = '0.0 0.0 0.0 0.0 4.0 0.0 4.0 4.0 0.0 4.0 0.0 0.0 0.0 0.0 0.0 ' \ '1.0 1.0 0.0 2.0 1.0 0.0 2.0 2.0 0.0 1.0 2.0 0.0 1.0 1.0 0.0 0.0 0.0 0.0 ' \