diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index d469f985..8111bb5e 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -41,7 +41,7 @@ class Building(CityObject): self._roof_type = None self._internal_zones = None self._shell = None - self._aliases = None + self._aliases = [] self._type = 'building' self._cold_water_temperature = {} self._heating_demand = {} @@ -486,10 +486,7 @@ class Building(CityObject): """ Add a new alias for the building """ - if self._aliases is None: - self._aliases = [value] - else: - self._aliases.append(value) + self._aliases.append(value) if self.city is not None: self.city.add_building_alias(self, value) diff --git a/hub/city_model_structure/city.py b/hub/city_model_structure/city.py index 9b214d0a..e95348dc 100644 --- a/hub/city_model_structure/city.py +++ b/hub/city_model_structure/city.py @@ -29,6 +29,7 @@ from hub.city_model_structure.level_of_detail import LevelOfDetail from hub.city_model_structure.parts_consisting_building import PartsConsistingBuilding from hub.helpers.geometry_helper import GeometryHelper from hub.helpers.location import Location +import hub.helpers.constants as cte class City: @@ -55,9 +56,7 @@ class City: self._city_objects = None self._energy_systems = None self._fuels = None - self._machines = None self._stations = [] - self._lca_materials = None self._level_of_detail = LevelOfDetail() self._city_objects_dictionary = {} self._city_objects_alias_dictionary = {} @@ -462,7 +461,26 @@ class City: Return a merged city combining the current city and the given one :return: City """ - raise NotImplementedError('This method needs to be reimplemented') + merged_city = self.copy + for building in city.buildings: + if merged_city.city_object(building.name) is None: + # building is new so added to the city + merged_city.add_city_object(copy.deepcopy(building)) + else: + # keep the one with less radiation + parameter_city_building_total_radiation = 0 + for surface in building.surfaces: + if surface.global_irradiance: + parameter_city_building_total_radiation += surface.global_irradiance[cte.YEAR].iloc[0, 0] + merged_city_building_total_radiation = 0 + for surface in merged_city.city_object(building.name).surfaces: + if surface.global_irradiance: + merged_city_building_total_radiation += surface.global_irradiance[cte.YEAR].iloc[0, 0] + if merged_city_building_total_radiation < parameter_city_building_total_radiation: + merged_city.remove_city_object(merged_city.city_object(building.name)) + merged_city.add_city_object(building) + + return merged_city @property def level_of_detail(self) -> LevelOfDetail: diff --git a/hub/exports/building_energy/idf.py b/hub/exports/building_energy/idf.py index 860e1f5e..24bbddc2 100644 --- a/hub/exports/building_energy/idf.py +++ b/hub/exports/building_energy/idf.py @@ -451,10 +451,6 @@ class Idf: if zone.Name == f'{zone_name}_infiltration': return schedule = f'Infiltration schedules {thermal_zone.usage_name}' - # todo: if the schedule doesn't exist, does the idf notices?? - # We shouldn't check this because the schedules could be written later -# if schedule not in self._idf.idfobjects[self._COMPACT_SCHEDULE]: -# return self._idf.newidfobject(self._INFILTRATION, Name=f'{zone_name}_infiltration', Zone_or_ZoneList_Name=zone_name, @@ -468,10 +464,6 @@ class Idf: if zone.Name == f'{zone_name}_ventilation': return schedule = f'Ventilation schedules {thermal_zone.usage_name}' - # todo: if the schedule doesn't exist, does the idf notices?? - # We shouldn't check this because the schedules could be written later -# if schedule not in self._idf.idfobjects[self._COMPACT_SCHEDULE]: -# return self._idf.newidfobject(self._VENTILATION, Name=f'{zone_name}_ventilation', Zone_or_ZoneList_Name=zone_name, diff --git a/hub/exports/exports_factory.py b/hub/exports/exports_factory.py index a4b073a3..088edb5e 100644 --- a/hub/exports/exports_factory.py +++ b/hub/exports/exports_factory.py @@ -17,11 +17,7 @@ class ExportsFactory: """ Exports factory class """ - def __init__(self, handler, city, path, - target_buildings=None, - adjacent_buildings=None, - weather_file=None, - weather_format=None): + def __init__(self, handler, city, path,target_buildings=None,adjacent_buildings=None): self._city = city self._handler = '_' + handler.lower() validate_import_export_type(ExportsFactory, handler) @@ -30,8 +26,6 @@ class ExportsFactory: self._path = path self._target_buildings = target_buildings self._adjacent_buildings = adjacent_buildings - self._weather_file = weather_file - self._weather_format = weather_format @property def _citygml(self): @@ -69,8 +63,6 @@ class ExportsFactory: """ return SimplifiedRadiosityAlgorithm(self._city, (self._path / f'{self._city.name}_sra.xml'), - self._weather_file, - self._weather_format, target_buildings=self._target_buildings) def export(self): diff --git a/hub/exports/formats/simplified_radiosity_algorithm.py b/hub/exports/formats/simplified_radiosity_algorithm.py index cca81914..d5a0a005 100644 --- a/hub/exports/formats/simplified_radiosity_algorithm.py +++ b/hub/exports/formats/simplified_radiosity_algorithm.py @@ -21,8 +21,6 @@ class SimplifiedRadiosityAlgorithm: def __init__(self, city, file_name, - weather_file, - weather_format, target_buildings=None, begin_month=1, begin_day=1, @@ -37,9 +35,6 @@ class SimplifiedRadiosityAlgorithm: self._city.climate_file = str((Path(file_name).parent / f'{city.name}.cli').resolve()) self._city.climate_reference_city = city.location self._target_buildings = target_buildings - self._weather_format = weather_format - self._weather_file = weather_file - self._export() def _correct_point(self, point): @@ -56,7 +51,7 @@ class SimplifiedRadiosityAlgorithm: def _export_sra_cli(self): file = self._city.climate_file days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - WeatherFactory(self._weather_format, self._city).enrich() + WeatherFactory('epw', self._city).enrich() content = self._city.name + '\n' content += str(self._city.latitude) + ',' + str(self._city.longitude) + ',0.0,' + str(self._city.time_zone) + '\n' content += '\ndm m h G_Dh G_Bn\n' diff --git a/tests/test_city_merge.py b/tests/test_city_merge.py index 2831308e..5335798d 100644 --- a/tests/test_city_merge.py +++ b/tests/test_city_merge.py @@ -4,11 +4,17 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca """ - +import copy +import distutils.spawn +import subprocess from pathlib import Path from unittest import TestCase +from hub.city_model_structure.city import City from hub.imports.geometry_factory import GeometryFactory +from hub.imports.results_factory import ResultFactory +from hub.exports.exports_factory import ExportsFactory +import hub.helpers.constants as cte class TestCityMerge(TestCase): @@ -24,14 +30,58 @@ class TestCityMerge(TestCase): self._output_path = (Path(__file__).parent / 'tests_outputs').resolve() self._executable = 'sra' - def _get_citygml(self, file): - file_path = (self._example_path / file).resolve() - city = GeometryFactory('citygml', path=file_path).city - self.assertIsNotNone(city, 'city is none') - return city - def test_merge(self): - self.assertTrue(False, 'This test needs to be reimplemented') + file_path = Path('./tests_data/test.geojson').resolve() + full_city = GeometryFactory('geojson', file_path, height_field='citygml_me').city + self.assertEqual(17, len(full_city.buildings), 'Wrong number of buildings') + odd_city = City(full_city.lower_corner, full_city.upper_corner, full_city.srs_name) + par_city = City(full_city.lower_corner, full_city.upper_corner, full_city.srs_name) + for building in full_city.buildings: + if int(building.name) % 2 == 0: + par_city.add_city_object(copy.deepcopy(building)) + else: + odd_city.add_city_object(copy.deepcopy(building)) + self.assertEqual(8, len(odd_city.buildings), 'Wrong number of odd buildings') + self.assertEqual(9, len(par_city.buildings), 'Wrong number of par buildings') + merged_city = odd_city.merge(par_city) + self.assertEqual(17, len(merged_city.buildings), 'Wrong number of buildings in merged city') + merged_city = par_city.merge(odd_city) + self.assertEqual(17, len(merged_city.buildings), 'Wrong number of buildings in merged city') + merged_city = full_city.merge(odd_city).merge(par_city) + self.assertEqual(17, len(merged_city.buildings), 'Wrong number of buildings in merged city') def test_merge_with_radiation(self): - self.assertTrue(False, 'This test needs to be reimplemented') + sra = distutils.spawn.find_executable('sra') + file_path = Path('./tests_data/test.geojson').resolve() + output_path = Path('./tests_outputs/') + full_city = GeometryFactory('geojson', file_path, height_field='citygml_me').city + par_city = City(full_city.lower_corner, full_city.upper_corner, full_city.srs_name) + for building in full_city.buildings: + if int(building.name) % 2 == 0: + par_city.add_city_object(copy.deepcopy(building)) + ExportsFactory('sra', full_city, output_path).export() + sra_file = str((output_path / f'{full_city.name}_sra.xml').resolve()) + subprocess.run([sra, sra_file], stdout=subprocess.DEVNULL) + ResultFactory('sra', full_city, output_path).enrich() + self.assertEqual(17, len(full_city.buildings), 'Wrong number of buildings') + merged_city = full_city.merge(par_city) + buildings_with_radiation = 0 + for building in merged_city.buildings: + radiation = True + for surface in building.surfaces: + if not surface.global_irradiance: + radiation = False + if radiation: + buildings_with_radiation += 1 + self.assertEqual(17, buildings_with_radiation, 'Some buildings have radiation') + merged_city = par_city.merge(full_city) + buildings_with_radiation = 0 + for building in merged_city.buildings: + radiation = True + for surface in building.surfaces: + if not surface.global_irradiance: + radiation = False + if radiation: + buildings_with_radiation += 1 + self.assertEqual(17, buildings_with_radiation, 'Some buildings have radiation') +