diff --git a/hub/city_model_structure/city.py b/hub/city_model_structure/city.py index 97000fbb..2042d14d 100644 --- a/hub/city_model_structure/city.py +++ b/hub/city_model_structure/city.py @@ -359,6 +359,8 @@ class City: Get city latitude in degrees :return: None or float """ + if self._latitude is None: + return self._get_location().climate_reference_city_latitude return self._latitude @latitude.setter @@ -376,6 +378,8 @@ class City: Get city longitude in degrees :return: None or float """ + if self._longitude is None: + return self._get_location().climate_reference_city_longitude return self._longitude @longitude.setter diff --git a/hub/exports/building_energy/trnsys.py b/hub/exports/building_energy/trnsys.py index 41354d66..da7a2d80 100644 --- a/hub/exports/building_energy/trnsys.py +++ b/hub/exports/building_energy/trnsys.py @@ -4,9 +4,18 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ - +import json +import math +from datetime import datetime from pathlib import Path +from urllib.error import URLError +from urllib.request import urlopen + +import pytz +from timezonefinder import TimezoneFinder + from hub.version import __version__ +import hub.helpers.constants as cte class Trnsys: @@ -18,6 +27,8 @@ class Trnsys: self._city = city self._path = path self._buildings = self._city.buildings + self._tf = TimezoneFinder() + if target_buildings is not None: buildings = [] for building in target_buildings: @@ -32,9 +43,9 @@ class Trnsys: f.write('***************************************\n') f.write('* Building descriptions file TRNSYS\n') location = ( - self._city.location.climate_reference_city_latitude, self._city.location.climate_reference_city_longitude + self._city.latitude, self._city.longitude ) - f.write(f'* Export: {self._city.location.city} {self._city.location.country} {location}') + f.write(f'* Export: {self._city.location.city} {self._city.location.country} {location}\n') f.write(f'* For building: {building.name}') if building.aliases is not None: f.write(f' {building.aliases})') @@ -43,8 +54,19 @@ class Trnsys: f.write(f'* Year build: {building.year_of_construction}\n') f.write('***************************************\n') - @staticmethod - def _properties(f): + def _properties(self, f): + latitude = self._city.latitude + longitude = self._city.longitude + utc_offset = datetime.now( + pytz.timezone(self._tf.timezone_at(lng=longitude, lat=latitude)) + ).utcoffset().total_seconds()/60/60 + elevation = 200 + try: + elevation = json.loads( + urlopen(f'https://api.open-elevation.com/api/v1/lookup?locations={latitude},{longitude}').read() + )['results'][0]['elevation'] + except URLError: + pass f.write('***************************************\n') f.write(f'* Properties\n') f.write('***************************************\n') @@ -57,7 +79,7 @@ class Trnsys: f.write(' KVERTICAL=5.76 : EVERTICAL=0.3\n') f.write('* Radiance parameters\n') f.write(' SCENE_ROTATION_ANGLE=0 : GROUND_IDS= : GROUND_REFLECTANCE=0.2 : SHADER_REFLECTANCE=0\n') - f.write(' CALC_MODE=RAD : LATITUDE=48 : LONGITUDE=-9.2 : TIME_ZONE=-15 : SITE_ELEVATION=200\n') + f.write(f' CALC_MODE=RAD : LATITUDE={latitude} : LONGITUDE={longitude} : TIME_ZONE={utc_offset} : SITE_ELEVATION={elevation}\n') f.write(' AB=5 : AD=1000 : AS=20 : AR=300 : AA=0.1\n') f.write(' LR=6 : ST=0.15 : SJ=1 : LW=0.004 : DJ=0 : DS=0.2 : DR=2 : DP=512\n') f.write('* Comfort parameters\n') @@ -75,15 +97,27 @@ class Trnsys: f.write(f'* Layers\n') f.write('***************************************\n') layers = {} + for thermal_zone in building.thermal_zones_from_internal_zones: for thermal_boundary in thermal_zone.thermal_boundaries: for layer in thermal_boundary.layers: + capacity = 0 + conductivity = 0 + density = 0 + if layer.thermal_absorptance is not None: + capacity = layer.thermal_absorptance + if layer.conductivity is not None: + conductivity = layer.conductivity + if layer.density is not None: + density = layer.density + + layers[layer.material_name] = { - 'conductivity': layer.conductivity, - 'capacity': '1', # todo: ask oriol about this - 'density': layer.density, - 'pert': '0', # todo: ask oriol about this - 'penrt': '0', # todo: ask oriol about this + 'conductivity': conductivity, + 'capacity': capacity, # todo: transform units + 'density': density, + 'pert': '0', + 'penrt': '0' } f.write('PROPERTIES\n') for layer_name, values in layers.items(): @@ -92,31 +126,41 @@ class Trnsys: f.write(f'{attribute.upper()}=\t{value}\t') f.write('\n') + @staticmethod + def _inputs(f): + f.write('***************************************\n') + f.write(f'* Inputs\n') + f.write('***************************************\n') + f.write('INPUTS TGROUND TBOUNDARY SHADE_CLOSE SHADE_OPEN MAX_ISHADE MAX_ESHADE\n') + f.write('INPUTS_DESCRIPTION\n') + f.write('TGROUND : C : Ground Temperature (boundary temperature used for floors adjacent to the ground)\n') + f.write('TBOUNDARY : C : Boundary Temperature (boundary temperature used for boundary floors, walls, ceilings)\n') + f.write('SHADE_CLOSE : kJ/hr.m^2 : threshold of total radiation on facade where shading device is activated\n') + f.write('SHADE_OPEN : kJ/hr.m^2 : threshold of total radiation on facade where shading device is deactivated\n') + f.write('MAX_ISHADE : any : max shading factor of internal shading\n') + f.write('MAX_ESHADE : any : max shading factor of external shading\n') @staticmethod - def _geometry_information(f, building): - f.write('[Geometry]\n') - f.write(f'Length: {building.upper_corner[0] - building.lower_corner[0]}\n') - f.write(f'Width: {building.upper_corner[1] - building.lower_corner[1]}\n') - f.write(f'Height: {building.upper_corner[2] - building.lower_corner[2]}\n') - - @staticmethod - def _wall_construction(f, building): - basic_thermal_boundary = building.walls[0].associated_thermal_boundaries[0] - - f.write('[Wall Construction]\n') - f.write(f'Wall Type: Multi-Layered Wall\n') - f.write(f'Layers: {len(basic_thermal_boundary.layers)}') - for i, layer in enumerate(basic_thermal_boundary.layers): - f.write(f'Layer {i}: {layer.material_name}\n') - f.write(f'Thickness: {layer.thickness}m\n') - - @staticmethod - def _wall_information(f, building): - f.write('[Wall Section]\n') - f.write(f'Material: {building.upper_corner[0] - building.lower_corner[0]}\n') - f.write(f'Width: {building.upper_corner[1] - building.lower_corner[1]}\n') - f.write(f'Height: {building.upper_corner[2] - building.lower_corner[2]}\n') + def _schedules(f, building): + f.write('***************************************\n') + f.write(f'* Schedules\n') + f.write('***************************************\n') + for thermal_zone in building.thermal_zones_from_internal_zones: + for usage in thermal_zone.usages: + for schedule in usage.occupancy.occupancy_schedules: + if schedule.time_step is cte.HOUR: + for day_type in schedule.day_types: + f.write(f'SCHEDULE {day_type.upper()}\n') + hours = 'HOURS= ' + values = 'VALUES= ' + previous = math.inf + for hour, value in enumerate(schedule.values): + if previous != value: + hours = f'{hours} {hour}.000' + values = f'{values} {value}' + previous = value + f.write(f'{hours} 24.0\n') + f.write(f'{values} {schedule.values[0]}\n') def export(self): print("export") @@ -125,11 +169,9 @@ class Trnsys: with open(path, 'w', encoding='utf-8') as f: print(path) self._header(f, building) - Trnsys._properties(f) + self._properties(f) Trnsys._types(f, building) + Trnsys._inputs(f) + Trnsys._schedules(f, building) + - """ - self._building_information(f, building) - Trnsys._geometry_information(f, building) - Trnsys._wall_information(f, building) - """ diff --git a/requirements.txt b/requirements.txt index 93b455ca..e88cc71b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,4 +23,5 @@ geopandas triangle psycopg2-binary Pillow -pathlib \ No newline at end of file +pathlib +timezonefinder \ No newline at end of file diff --git a/tests/test_exports.py b/tests/test_exports.py index de95bc64..c851bc67 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -162,12 +162,7 @@ class TestExports(TestCase): function_to_hub=Dictionaries().montreal_function_to_hub_function).city self.assertIsNotNone(city, 'city is none') - EnergyBuildingsExportsFactory('trnsys', city, self._output_path).export() ConstructionFactory('nrcan', city).enrich() - EnergyBuildingsExportsFactory('idf', city, self._output_path).export() UsageFactory('nrcan', city).enrich() WeatherFactory('epw', city).enrich() - try: - EnergyBuildingsExportsFactory('trnsys', city, self._output_path).export() - except Exception: - self.fail("Trnsys ExportsFactory raised ExceptionType unexpectedly!") \ No newline at end of file + EnergyBuildingsExportsFactory('trnsys', city, self._output_path).export() \ No newline at end of file