diff --git a/city_model_structure/building.py b/city_model_structure/building.py index d358898d..6fd8a779 100644 --- a/city_model_structure/building.py +++ b/city_model_structure/building.py @@ -20,8 +20,8 @@ class Building(CityObject): """ Building(CityObject) class """ - def __init__(self, name, lod, surfaces, year_of_construction, function, terrains=None): - super().__init__(name, lod, surfaces) + def __init__(self, name, surfaces, year_of_construction, function, city_lower_corner, terrains=None): + super().__init__(name, surfaces, city_lower_corner) self._households = None self._basement_heated = None self._attic_heated = None @@ -131,6 +131,9 @@ class Building(CityObject): def attic_heated(self) -> Union[None, int]: """ Get if the city object attic is heated + 0: no attic in the building + 1: attic exists but is not heated + 2: attic exists and is heated :return: None or int """ return self._attic_heated @@ -139,6 +142,9 @@ class Building(CityObject): def attic_heated(self, value): """ Set if the city object attic is heated + 0: no attic in the building + 1: attic exists but is not heated + 2: attic exists and is heated :param value: int """ if value is not None: @@ -148,6 +154,9 @@ class Building(CityObject): def basement_heated(self) -> Union[None, int]: """ Get if the city object basement is heated + 0: no basement in the building + 1: basement exists but is not heated + 2: basement exists and is heated :return: None or int """ return self._basement_heated @@ -156,6 +165,9 @@ class Building(CityObject): def basement_heated(self, value): """ Set if the city object basement is heated + 0: no basement in the building + 1: basement exists but is not heated + 2: basement exists and is heated :param value: int """ if value is not None: diff --git a/city_model_structure/bus_system.py b/city_model_structure/bus_system.py index 74a57e93..328834ae 100644 --- a/city_model_structure/bus_system.py +++ b/city_model_structure/bus_system.py @@ -17,8 +17,8 @@ class BusSystem(CityObject): """ BusSystem(CityObject) class """ - def __init__(self, name, lod, surfaces): - super().__init__(name, lod, surfaces) + def __init__(self, name, surfaces, city_lower_corner): + super().__init__(name, surfaces, city_lower_corner) self._bus_routes = None self._bus_network = None self._buses = None diff --git a/city_model_structure/city.py b/city_model_structure/city.py index 18fb1996..59476ecd 100644 --- a/city_model_structure/city.py +++ b/city_model_structure/city.py @@ -21,6 +21,7 @@ from city_model_structure.city_objects_cluster import CityObjectsCluster from city_model_structure.buildings_cluster import BuildingsCluster from city_model_structure.fuel import Fuel from city_model_structure.iot.station import Station +from city_model_structure.level_of_detail import LevelOfDetail from city_model_structure.machine import Machine from city_model_structure.parts_consisting_building import PartsConsistingBuilding from city_model_structure.subway_entrance import SubwayEntrance @@ -59,6 +60,7 @@ class City: self._machines = None self._stations = [] self._lca_materials = None + self._level_of_detail = LevelOfDetail() @property def fuels(self) -> [Fuel]: @@ -291,7 +293,6 @@ 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) selected_region_city.climate_file = self.climate_file -# selected_region_city.climate_reference_city = self.climate_reference_city for city_object in self.city_objects: location = city_object.centroid if location is not None: @@ -430,9 +431,9 @@ class City: """ self._lca_materials = value - def lca_material(self, lca_id) -> LcaMaterial: + def lca_material(self, lca_id) -> Union[LcaMaterial, None]: """ - Get the lca materiol matching the given Id + Get the lca material matching the given Id :return: LcaMaterial or None """ for lca_material in self.lca_materials: @@ -452,3 +453,7 @@ class City: for city_object in city.city_objects: _merge_city.add_city_object(city_object) return _merge_city + + @property + def level_of_detail(self): + return self._level_of_detail diff --git a/city_model_structure/city_object.py b/city_model_structure/city_object.py index 261af197..09320603 100644 --- a/city_model_structure/city_object.py +++ b/city_model_structure/city_object.py @@ -18,9 +18,8 @@ class CityObject: """ class CityObject """ - def __init__(self, name, lod, surfaces): + def __init__(self, name, surfaces): self._name = name - self._lod = lod self._surfaces = surfaces self._type = None self._city_object_lower_corner = None @@ -44,17 +43,6 @@ class CityObject: """ return self._name - @property - def lod(self) -> int: - """ - Get city object level of detail 0, 1, 2, 3 or 4 - :return: int - """ - if self._lod == 0: - return self._lod - lod = int(math.log(self._lod, 2) + 1) - return lod - @property def type(self) -> str: """ diff --git a/city_model_structure/city_objects_cluster.py b/city_model_structure/city_objects_cluster.py index 4a0a898f..683fbe39 100644 --- a/city_model_structure/city_objects_cluster.py +++ b/city_model_structure/city_objects_cluster.py @@ -20,8 +20,7 @@ class CityObjectsCluster(ABC, CityObject): self._cluster_type = cluster_type self._city_objects = city_objects self._sensors = [] - self._lod = '' - super().__init__(name, self._lod, None) + super().__init__(name, None, None) @property def name(self): diff --git a/city_model_structure/energy_system.py b/city_model_structure/energy_system.py index 866414c5..deecb2fc 100644 --- a/city_model_structure/energy_system.py +++ b/city_model_structure/energy_system.py @@ -16,8 +16,8 @@ class EnergySystem(CityObject): EnergySystem(CityObject) class """ - def __init__(self, name, lod, surfaces): - super().__init__(name, lod, surfaces) + def __init__(self, name, surfaces): + super().__init__(name, surfaces) self._air_source_hp = None self._water_to_water_hp = None self._type = 'energy_system' diff --git a/city_model_structure/level_of_detail.py b/city_model_structure/level_of_detail.py new file mode 100644 index 00000000..5f6c8274 --- /dev/null +++ b/city_model_structure/level_of_detail.py @@ -0,0 +1,57 @@ +""" +Level of detail module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" + +class LevelOfDetail: + """ + Level of detail for the city class + """ + def __init__(self): + self._geometry = None + self._construction = None + self._usage = None + + @property + def geometry(self): + """ + Get the city minimal geometry level of detail + """ + return self._geometry + + @geometry.setter + def geometry(self, value): + """ + Set the city minimal geometry level of detail + """ + self._geometry = value + + @property + def construction(self): + """ + Get the city minimal construction level of detail + """ + return self._construction + + @construction.setter + def construction(self, value): + """ + Set the city minimal construction level of detail + """ + self._construction = value + + @property + def usage(self): + """ + Get the city minimal usage level of detail + """ + return self._usage + + @usage.setter + def usage(self, value): + """ + Set the city minimal usage level of detail + """ + self._usage = value diff --git a/imports/construction_factory.py b/imports/construction_factory.py index 3616b08c..7f136ed0 100644 --- a/imports/construction_factory.py +++ b/imports/construction_factory.py @@ -24,6 +24,7 @@ class ConstructionFactory: Enrich the city by using NREL information """ UsPhysicsParameters(self._city, self._base_path).enrich_buildings() + self._city.level_of_detail.construction = 2 def enrich(self): """ diff --git a/imports/energy_systems/air_source_hp_parameters.py b/imports/energy_systems/air_source_hp_parameters.py index 362f823a..e27e3fcf 100644 --- a/imports/energy_systems/air_source_hp_parameters.py +++ b/imports/energy_systems/air_source_hp_parameters.py @@ -82,7 +82,7 @@ class AirSourceHeatPumpParameters: heat_pump.heating_comp_power = h_data[1] heat_pump.heating_capacity_coff = self._compute_coefficients(h_data) - energy_system = EnergySystem('{} capacity heat pump'.format(heat_pump.model), 0, []) + energy_system = EnergySystem('{} capacity heat pump'.format(heat_pump.model), [], None) energy_system.air_source_hp = heat_pump self._city.add_city_object(energy_system) return self._city diff --git a/imports/energy_systems/water_to_water_hp_parameters.py b/imports/energy_systems/water_to_water_hp_parameters.py index ca4b62e1..d8501f57 100644 --- a/imports/energy_systems/water_to_water_hp_parameters.py +++ b/imports/energy_systems/water_to_water_hp_parameters.py @@ -129,7 +129,7 @@ class WaterToWaterHPParameters: heat_pump.entering_water_temp = data['ewt'] heat_pump.leaving_water_temp = data['lwt'] heat_pump.power_demand_coff = self._compute_coefficients(data) - energy_system = EnergySystem(heat_pump.model, 0, []) + energy_system = EnergySystem(heat_pump.model, [], None) energy_system.water_to_water_hp = heat_pump self._city.add_city_object(energy_system) return self._city diff --git a/imports/geometry/citygml.py b/imports/geometry/citygml.py index 5eafa121..758882e2 100644 --- a/imports/geometry/citygml.py +++ b/imports/geometry/citygml.py @@ -23,6 +23,7 @@ class CityGml: """ def __init__(self, path, extrusion_height_field=None, year_of_construction_field=None, function_field=None): self._city = None + self._lod = None self._lod1_tags = ['lod1Solid', 'lod1MultiSurface'] self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve'] self._extrusion_height_field = extrusion_height_field @@ -69,7 +70,7 @@ class CityGml: self._srs_name = envelope['@srsName'] else: # If not coordinate system given assuming hub standard - self._srs_name = "EPSG:4326" + self._srs_name = "EPSG:26911" else: # get the boundary from the city objects instead for city_object_member in self._gml['CityModel']['cityObjectMember']: @@ -114,14 +115,16 @@ class CityGml: if 'function' in city_object: function = city_object['function'] if any(key in city_object for key in self._lod1_tags): - lod = 1 + if self._lod is None or self._lod > 1: + self._lod = 1 surfaces = CityGmlLod1(city_object).surfaces elif any(key in city_object for key in self._lod2_tags): - lod = 2 + if self._lod is None or self._lod > 2: + self._lod = 2 surfaces = CityGmlLod2(city_object).surfaces else: raise NotImplementedError("Not supported level of detail") - return Building(name, lod, surfaces, year_of_construction, function, terrains=None) + return Building(name, surfaces, year_of_construction, function, terrains=None) def _create_parts_consisting_building(self, city_object): name = city_object['@id'] @@ -147,4 +150,5 @@ class CityGml: self._city.add_city_objects_cluster(self._create_parts_consisting_building(city_object)) else: self._city.add_city_object(self._create_building(city_object)) + self._city.level_of_detail.geometry = self._lod return self._city diff --git a/imports/geometry/gpandas.py b/imports/geometry/gpandas.py index b30b8024..bf77c5b7 100644 --- a/imports/geometry/gpandas.py +++ b/imports/geometry/gpandas.py @@ -59,7 +59,6 @@ class GPandas: self._city = City(self._lower_corner, self._upper_corner, self._srs_name) for scene_index, bldg in self._scene.iterrows(): geometry = bldg.geom - print(geometry['coordinates'][0]) polygon = ShapelyPoly(geometry['coordinates'][0]) height = float(bldg['height']) building_mesh = trimesh.creation.extrude_polygon(polygon, height) @@ -82,8 +81,9 @@ class GPandas: perimeter_polygon = solid_polygon surface = Surface(solid_polygon, perimeter_polygon) surfaces.append(surface) - building = Building(name, lod, surfaces, year_of_construction, function) + building = Building(name, surfaces, year_of_construction, function, self._lower_corner, terrains=None) self._city.add_city_object(building) + self._city.level_of_detail.geometry = lod return self._city @staticmethod diff --git a/imports/geometry/obj.py b/imports/geometry/obj.py index 5d14b5cf..8aff725c 100644 --- a/imports/geometry/obj.py +++ b/imports/geometry/obj.py @@ -77,6 +77,7 @@ class Obj: perimeter_polygon = solid_polygon surface = Surface(solid_polygon, perimeter_polygon) surfaces.append(surface) - building = Building(name, lod, surfaces, year_of_construction, function) + building = Building(name, surfaces, year_of_construction, function, self._lower_corner, terrains=None) self._city.add_city_object(building) + self._city.level_of_detail.geometry = lod return self._city diff --git a/imports/geometry/rhino.py b/imports/geometry/rhino.py index d43281ad..ee03ee10 100644 --- a/imports/geometry/rhino.py +++ b/imports/geometry/rhino.py @@ -101,7 +101,7 @@ class Rhino: if face is None: break hub_surfaces = hub_surfaces + self._add_face(face) - building = Building(name, 3, hub_surfaces, 'unknown', 'unknown', []) + building = Building(name, hub_surfaces, 'unknown', 'unknown', (self._min_x, self._min_y, self._min_z), []) city_objects.append(building) lower_corner = (self._min_x, self._min_y, self._min_z) upper_corner = (self._max_x, self._max_y, self._max_z) @@ -133,4 +133,5 @@ class Rhino: for building in buildings: city.add_city_object(building) + city.level_of_detail.geometry = 3 return city diff --git a/imports/usage_factory.py b/imports/usage_factory.py index 69ec4922..50d94915 100644 --- a/imports/usage_factory.py +++ b/imports/usage_factory.py @@ -26,12 +26,14 @@ class UsageFactory: """ Enrich the city with HFT usage library """ + self._city.level_of_detail.usage = 2 return HftUsageParameters(self._city, self._base_path).enrich_buildings() def _comnet(self): """ Enrich the city with COMNET usage library """ + self._city.level_of_detail.usage = 2 return ComnetUsageParameters(self._city, self._base_path).enrich_buildings() def enrich(self): diff --git a/unittests/test_construction_factory.py b/unittests/test_construction_factory.py index 83b6d680..6ba75ee9 100644 --- a/unittests/test_construction_factory.py +++ b/unittests/test_construction_factory.py @@ -28,6 +28,7 @@ class TestConstructionFactory(TestCase): file_path = (self._example_path / file).resolve() self._city = GeometryFactory('citygml', path=file_path).city self.assertIsNotNone(self._city, 'city is none') + self.assertIsNotNone(self._city.level_of_detail.geometry, 'wrong construction level of detail') return self._city @staticmethod @@ -70,7 +71,6 @@ class TestConstructionFactory(TestCase): def _check_buildings(self, city): for building in city.buildings: self.assertIsNotNone(building.name, 'building name is none') - self.assertIsNotNone(building.lod, 'building lod is none') self.assertIsNotNone(building.type, 'building type is none') self.assertIsNotNone(building.volume, 'building volume is none') self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none') diff --git a/unittests/test_geometry_factory.py b/unittests/test_geometry_factory.py index 7f54db4b..c0b91997 100644 --- a/unittests/test_geometry_factory.py +++ b/unittests/test_geometry_factory.py @@ -41,7 +41,6 @@ class TestGeometryFactory(TestCase): def _check_buildings(self, city): for building in city.buildings: self.assertIsNotNone(building.name, 'building name is none') - self.assertIsNotNone(building.lod, 'building lod is none') self.assertIsNotNone(building.type, 'building type is none') self.assertIsNotNone(building.volume, 'building volume is none') self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none') diff --git a/unittests/test_insel_exports.py b/unittests/test_insel_exports.py index fbadd3d8..bc6b2776 100644 --- a/unittests/test_insel_exports.py +++ b/unittests/test_insel_exports.py @@ -100,8 +100,8 @@ class TestExports(TestCase): building.year_of_construction = 2006 if building.function is None: building.function = 'large office' - building.attic_heated = False - building.basement_heated = False + building.attic_heated = 0 + building.basement_heated = 0 ConstructionFactory('nrel', city).enrich() UsageFactory('comnet', city).enrich() @@ -141,9 +141,8 @@ class TestExports(TestCase): self.assertIsNotNone(usage_zone.days_year, f'usage zone {usage_zone.usage} days_year is none') self.assertIsNotNone(usage_zone.mechanical_air_change, f'usage zone {usage_zone.usage} ' f'mechanical_air_change is none') - # export files try: - EnergyBuildingsExportsFactory('insel_monthly_energy_balance', city, self._output_path).export_debug() + EnergyBuildingsExportsFactory('insel_monthly_energy_balance', city, self._output_path).export() except Exception: self.fail("Insel MonthlyEnergyBalance ExportsFactory raised ExceptionType unexpectedly!") diff --git a/unittests/test_usage_factory.py b/unittests/test_usage_factory.py index f4feb04c..d9357fa8 100644 --- a/unittests/test_usage_factory.py +++ b/unittests/test_usage_factory.py @@ -33,7 +33,6 @@ class TestUsageFactory(TestCase): def _check_buildings(self, city): for building in city.buildings: self.assertIsNotNone(building.name, 'building name is none') - self.assertIsNotNone(building.lod, 'building lod is none') self.assertIsNotNone(building.type, 'building type is none') self.assertIsNotNone(building.volume, 'building volume is none') self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none')