From 11e8949b30d13717b8c6fb33e657f5d6b14c3555 Mon Sep 17 00:00:00 2001 From: guille Date: Tue, 7 Feb 2023 13:01:49 -0500 Subject: [PATCH] Added the importer for sra results --- hub/city_model_structure/city.py | 8 +- hub/exports/exports_factory.py | 13 ++- .../formats/simplified_radiosity_algorithm.py | 45 ++++++++- .../results/simplified_radiosity_algorithm.py | 92 +++++++++++++++++++ hub/imports/results_factory.py | 42 +++++++++ setup.py | 1 + 6 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 hub/imports/results/simplified_radiosity_algorithm.py create mode 100644 hub/imports/results_factory.py diff --git a/hub/city_model_structure/city.py b/hub/city_model_structure/city.py index 69960c50..ef8ebbd1 100644 --- a/hub/city_model_structure/city.py +++ b/hub/city_model_structure/city.py @@ -101,13 +101,19 @@ class City: """ return self._get_location().country + @property + def location(self): + return self._get_location().city + @property def name(self): """ Get city name :return: str """ - return self._get_location().city + if self._name is None: + return self._get_location().city + return self._name @property def climate_reference_city(self) -> Union[None, str]: diff --git a/hub/exports/exports_factory.py b/hub/exports/exports_factory.py index 2d769af0..678b5a06 100644 --- a/hub/exports/exports_factory.py +++ b/hub/exports/exports_factory.py @@ -17,7 +17,11 @@ class ExportsFactory: """ Exports factory class """ - def __init__(self, export_type, city, path, target_buildings=None, adjacent_buildings=None): + def __init__(self, export_type, city, path, + target_buildings=None, + adjacent_buildings=None, + weather_file=None, + weather_format=None): self._city = city self._export_type = '_' + export_type.lower() class_funcs = validate_import_export_type(ExportsFactory) @@ -30,6 +34,8 @@ 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): @@ -73,7 +79,10 @@ class ExportsFactory: Export the city to Simplified Radiosity Algorithm xml format :return: None """ - return SimplifiedRadiosityAlgorithm(self._city, (self._path / f'{self._city.name}_sra.xml'), + 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 84e6bd30..8a19cac3 100644 --- a/hub/exports/formats/simplified_radiosity_algorithm.py +++ b/hub/exports/formats/simplified_radiosity_algorithm.py @@ -6,12 +6,25 @@ Project Coder Guillermo.GutierrezMorote@concordia.ca """ import xmltodict +from hub.imports.weather_factory import WeatherFactory +import hub.helpers.constants as cte + class SimplifiedRadiosityAlgorithm: """ Export to SRA format """ - def __init__(self, city, file_name, target_buildings=None, begin_month=1, begin_day=1, end_month=12, end_day=31): + + def __init__(self, + city, + file_name, + weather_file, + weather_format, + target_buildings=None, + begin_month=1, + begin_day=1, + end_month=12, + end_day=31): self._file_name = file_name self._begin_month = begin_month self._begin_day = begin_day @@ -19,6 +32,8 @@ class SimplifiedRadiosityAlgorithm: self._end_day = end_day self._city = city self._target_buildings = target_buildings + self._weather_format = weather_format + self._weather_file = weather_file self._export() def _correct_point(self, point): @@ -29,6 +44,34 @@ class SimplifiedRadiosityAlgorithm: return [x, y, z] def _export(self): + self._export_sra_xml() + self._export_sra_cli() + + 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, file_name=self._weather_file).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' + total_days = 0 + for month in range(1, 13): + if month > 1: + total_days += days_in_month[month - 2] + for day in range(1, days_in_month[month - 1] + 1): + for hour in range(1, 25): + if month == 1: + i = 24 * (day - 1) + hour - 1 + else: + i = (total_days + day - 1) * 24 + hour - 1 + representative_building = self._city.buildings[0] + content += str(day) + ' ' + str(month) + ' ' + str(hour) + ' ' \ + + str(representative_building.global_horizontal[cte.HOUR].epw[i]) + ' ' \ + + str(representative_building.beam[cte.HOUR].epw[i]) + '\n' + with open(file, "w") as file: + file.write(content) + + def _export_sra_xml(self): buildings = [] for building_index, building in enumerate(self._city.buildings): if self._target_buildings is None: diff --git a/hub/imports/results/simplified_radiosity_algorithm.py b/hub/imports/results/simplified_radiosity_algorithm.py new file mode 100644 index 00000000..251dceaf --- /dev/null +++ b/hub/imports/results/simplified_radiosity_algorithm.py @@ -0,0 +1,92 @@ +""" +Simplified Radiosity Algorithm +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guillermo.GutierrezMorote@concordia.ca +""" +import pandas as pd +import numpy as np +import calendar as cal +import hub.helpers.constants as cte + + +class SimplifiedRadiosityAlgorithm: + """ + Import SRA results + """ + def __init__(self, city, base_path): + + self._city = city + self._base_path = base_path + self._input_file_path = (self._base_path / f'{self._city.name}_sra_SW.out') + self._month_hour = self._month_hour_data_frame + self._results = self._read_results() + self._radiation_list = [] + + @property + def _month_hour_data_frame(self): + array = [] + for i in range(0, 12): + days_of_month = cal.monthrange(2015, i+1)[1] + total_hours = days_of_month * 24 + array = np.concatenate((array, np.full(total_hours, i + 1))) + return pd.DataFrame(array, columns=[cte.MONTH]) + + def _get_mean_values(self, values): + out = None + if values is not None: + if cte.MONTH not in values.columns: + values = pd.concat([self._month_hour, pd.DataFrame(values)], axis=1) + out = values.groupby(cte.MONTH, as_index=False).mean() + del out[cte.MONTH] + return out + + def _read_results(self): + try: + return pd.read_csv(self._input_file_path, sep='\s+', header=0) + except Exception: + raise Exception('No SRA output file found') + + @property + def _radiation(self) -> []: + if len(self._radiation_list) == 0: + id_building = '' + header_building = [] + for column in self._results.columns.values: + if id_building != column.split(':')[1]: + id_building = column.split(':')[1] + if len(header_building) > 0: + self._radiation_list.append(pd.concat([self._month_hour, self._results[header_building]],axis=1)) + header_building = [column] + else: + header_building.append(column) + self._radiation_list.append(pd.concat([self._month_hour, self._results[header_building]], axis=1)) + return self._radiation_list + + def enrich(self): + """ + saves in building surfaces the correspondent irradiance at different time-scales depending on the mode + if building is None, it saves all buildings' surfaces in file, if building is specified, it saves only that + specific building values + :return: none + """ + for radiation in self._radiation: + city_object_name = radiation.columns.values.tolist()[1].split(':')[1] + building = self._city.city_object(city_object_name) + for column in radiation.columns.values: + if column == cte.MONTH: + continue + header_id = column + surface_id = header_id.split(':')[2] + surface = building.surface_by_id(surface_id) + new_value = pd.DataFrame(radiation[[header_id]].to_numpy(), columns=['sra']) + + month_new_value = self._get_mean_values(new_value) + if cte.MONTH not in surface.global_irradiance: + surface.global_irradiance[cte.MONTH] = month_new_value + else: + pd.concat([surface.global_irradiance[cte.MONTH], month_new_value], axis=1) + if cte.HOUR not in surface.global_irradiance: + surface.global_irradiance[cte.HOUR] = new_value + else: + pd.concat([surface.global_irradiance[cte.HOUR], new_value], axis=1) diff --git a/hub/imports/results_factory.py b/hub/imports/results_factory.py new file mode 100644 index 00000000..0d4db8d3 --- /dev/null +++ b/hub/imports/results_factory.py @@ -0,0 +1,42 @@ +""" +Result factory retrieve the specific tool results and store the data in the given city +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from pathlib import Path + +from hub.helpers.utils import validate_import_export_type +from hub.hub_logger import logger +from hub.imports.results.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm + + +class ResultFactory: + """ + UsageFactory class + """ + def __init__(self, handler, city, base_path=None): + if base_path is None: + base_path = Path(Path(__file__).parent.parent / 'data/results') + self._handler = '_' + handler.lower().replace(' ', '_') + class_funcs = validate_import_export_type(ResultFactory) + if self._handler not in class_funcs: + err_msg = f"Wrong import type [{self._handler}]. Valid functions include {class_funcs}" + logger.error(err_msg) + raise Exception(err_msg) + self._city = city + self._base_path = base_path + + def _sra(self): + """ + Enrich the city with Simplified Radiosity Algorithm results + """ + SimplifiedRadiosityAlgorithm(self._city, self._base_path).enrich() + + def enrich(self): + """ + Enrich the city given to the class using the usage factory given handler + :return: None + """ + getattr(self, self._handler, lambda: None)() diff --git a/setup.py b/setup.py index 73bd3596..ba917de2 100644 --- a/setup.py +++ b/setup.py @@ -72,6 +72,7 @@ setup( 'hub.imports.geometry', 'hub.imports.geometry.citygml_classes', 'hub.imports.geometry.helpers', + 'hub.imports.results', 'hub.imports.usage', 'hub.imports.weather', 'hub.imports.weather.helpers',