From bfd4868005dc1df431ec113eb44bd868e2817e15 Mon Sep 17 00:00:00 2001 From: guille Date: Tue, 13 Apr 2021 14:12:45 -0400 Subject: [PATCH] Rollback behnam changes and add sra export to the export factory --- city_model_structure/attributes/surface.py | 7 ++ city_model_structure/city.py | 27 +++++- exports/exports_factory.py | 13 +++ .../formats/simplified_radiosity_algorithm.py | 83 ++++++++++++++++++ helpers/geometry_helper.py | 11 +-- helpers/location.py | 12 +++ imports/geometry_feeders/citygml.py | 85 +++++++++---------- tests/test_exports.py | 6 ++ 8 files changed, 190 insertions(+), 54 deletions(-) create mode 100644 exports/formats/simplified_radiosity_algorithm.py create mode 100644 helpers/location.py diff --git a/city_model_structure/attributes/surface.py b/city_model_structure/attributes/surface.py index 88966dbc..5be1f94d 100644 --- a/city_model_structure/attributes/surface.py +++ b/city_model_structure/attributes/surface.py @@ -42,6 +42,13 @@ class Surface: self._name = uuid.uuid4() return self._name + @property + def id(self): + """ + Surface id + :return str + """ + return str(self.name)[:8] @property def swr(self): diff --git a/city_model_structure/city.py b/city_model_structure/city.py index 8cd6ccff..ba9cf93c 100644 --- a/city_model_structure/city.py +++ b/city_model_structure/city.py @@ -14,6 +14,7 @@ from pyproj import Transformer from city_model_structure.building import Building from city_model_structure.city_object import CityObject from helpers.geometry_helper import GeometryHelper +from helpers.location import Location import math @@ -32,9 +33,11 @@ class City: # todo: right now extracted at city level, in the future should be extracted also at building level if exist self._location = None self._country_code = None + self._climate_reference_city = None + self._climate_file = None - @property - def _get_location(self): + + def _get_location(self) -> Location: if self._location is None: gps = pyproj.CRS('EPSG:4326') # LatLon with WGS84 datum used by GPS units and Google Earth try: @@ -54,7 +57,7 @@ class City: City country code :return: str """ - return self._get_location[0] + return self._get_location().city @property def name(self): @@ -62,7 +65,23 @@ class City: City name :return: str """ - return self._get_location[1] + return self._get_location().city + + @property + def climate_reference_city(self): + return self._climate_reference_city + + @climate_reference_city.setter + def climate_reference_city(self, value): + self._climate_reference_city = value + + @property + def climate_file(self): + return self._climate_file + + @climate_file.setter + def climate_file(self, value): + self._climate_file = value @property def city_objects(self) -> Union[List[CityObject], None]: diff --git a/exports/exports_factory.py b/exports/exports_factory.py index 8924e6eb..583f7a88 100644 --- a/exports/exports_factory.py +++ b/exports/exports_factory.py @@ -7,6 +7,7 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc from exports.formats.stl import Stl from exports.formats.obj import Obj from exports.formats.energy_ade import EnergyAde +from exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm class ExportsFactory: @@ -44,6 +45,14 @@ class ExportsFactory: @property def _obj(self): + """ + Export the city geometry to obj + :return: None + """ + return Obj(self._city, self._path) + + @property + def _grounded_obj(self): """ Export the city geometry to obj :return: None @@ -58,6 +67,10 @@ class ExportsFactory: """ raise NotImplementedError() + @property + def _sra(self): + return SimplifiedRadiosityAlgorithm(self._city, (self._path/ f'{self._city.name}_sra.xml')) + def export(self): """ Export the city model structure to the given export type diff --git a/exports/formats/simplified_radiosity_algorithm.py b/exports/formats/simplified_radiosity_algorithm.py new file mode 100644 index 00000000..a6134494 --- /dev/null +++ b/exports/formats/simplified_radiosity_algorithm.py @@ -0,0 +1,83 @@ +""" +Simplified Radiosity Algorithm +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guillermo.GutierrezMorote@concordia.ca +""" +from pathlib import Path +import xmltodict + +class SimplifiedRadiosityAlgorithm: + + def __init__(self, city, file_name, 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 + self._end_month = end_month + self._end_day = end_day + self._city = city + self._export() + + def _correct_point(self, point): + # correct the x, y, z values into the reference frame set by city lower_corner + x = point[0] - self._city.lower_corner[0] + y = point[1] - self._city.lower_corner[1] + z = point[2] - self._city.lower_corner[2] + return [x, y, z] + + def _export(self): + print('export') + buildings = [] + for building_index, building in enumerate(self._city.buildings): + building_dict = { + '@Name': f'{building.name}', + '@id': f'{building_index}', + '@key': f'{building.name}', + '@Simulate': 'True' + } + walls, roofs, floors = [], [], [] + for surface in building.surfaces: + surface_dict = { + '@id': f'{surface.id}', + '@ShortWaveReflectance': f'{surface.swr}' + } + for point_index, point in enumerate(surface.perimeter_polygon.points): + # todo: check corrected points + point = self._correct_point(point) + surface_dict[f'V{point_index}'] = { + '@x': f'{point[0]}', + '@y': f'{point[1]}', + '@z': f'{point[2]}' + } + if surface.type == 'Wall': + walls.append(surface_dict) + elif surface.type == 'Roof': + roofs.append(surface_dict) + else: + floors.append(surface_dict) + building_dict['Wall'] = walls + building_dict['Roof'] = roofs + building_dict['Floor'] = floors + buildings.append(building_dict) + sra = { + 'CitySim': { + '@name': f'{self._file_name.name}', + 'Simulation': { + '@beginMonth': f'{self._begin_month}', + '@beginDay': f'{self._begin_day}', + '@endMonth': f'{self._end_month}', + '@endDay': f'{self._end_day}', + }, + 'Climate': { + '@location': f'{self._city.climate_file}', + '@city': f'{self._city.climate_reference_city}' + }, + 'District': { + 'FarFieldObstructions': None, + 'Building': buildings + } + } + } + + with open(self._file_name, "w") as file: + file.write(xmltodict.unparse(sra, pretty=True, short_empty_elements=True)) + return diff --git a/helpers/geometry_helper.py b/helpers/geometry_helper.py index 4362b667..bf808702 100644 --- a/helpers/geometry_helper.py +++ b/helpers/geometry_helper.py @@ -12,6 +12,7 @@ from trimesh import intersections from helpers.configuration_helper import ConfigurationHelper from city_model_structure.attributes.polygon import Polygon from city_model_structure.attributes.polyhedron import Polyhedron +from helpers.location import Location class GeometryHelper: @@ -192,12 +193,12 @@ class GeometryHelper: @staticmethod def get_location(latitude, longitude): url = 'https://nominatim.openstreetmap.org/reverse?lat={latitude}&lon={longitude}&format=json' - resp = requests.get(url.format(latitude=latitude, longitude=longitude)) - if resp.status_code != 200: + response = requests.get(url.format(latitude=latitude, longitude=longitude)) + if response.status_code != 200: # This means something went wrong. - raise Exception('GET /tasks/ {}'.format(resp.status_code)) + raise Exception('GET /tasks/ {}'.format(response.status_code)) else: - response = resp.json() + response = response.json() # todo: this is wrong, remove in the future city = 'new_york_city' country = 'us' @@ -205,7 +206,7 @@ class GeometryHelper: city = response['address']['city'] if 'country_code' in response['address']: country = response['address']['country_code'] - return [country, city] + return Location(country, city) @staticmethod def distance_between_points(vertex1, vertex2): diff --git a/helpers/location.py b/helpers/location.py new file mode 100644 index 00000000..05a426c9 --- /dev/null +++ b/helpers/location.py @@ -0,0 +1,12 @@ +class Location: + def __init__(self, country, city): + self._country = country + self._city = city + + @property + def city(self): + return self._city + + @property + def country(self): + return self._country \ No newline at end of file diff --git a/imports/geometry_feeders/citygml.py b/imports/geometry_feeders/citygml.py index 1f4fc486..29f34a63 100644 --- a/imports/geometry_feeders/citygml.py +++ b/imports/geometry_feeders/citygml.py @@ -5,13 +5,13 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc """ import numpy as np import xmltodict -import time from city_model_structure.city import City from city_model_structure.building import Building from city_model_structure.attributes.surface import Surface from helpers.geometry_helper import GeometryHelper from city_model_structure.attributes.polygon import Polygon +from city_model_structure.attributes.thermal_zone import ThermalZone class CityGml: @@ -70,51 +70,48 @@ class CityGml: City model structure enriched with the geometry information :return: City """ - init = time.process_time_ns() if self._city is None: # todo: refactor this method to clearly choose the gml type self._city = City(self._lower_corner, self._upper_corner, self._srs_name) i = 0 for o in self._gml['CityModel']['cityObjectMember']: - gmlbuilding = o['Building'] - for build in gmlbuilding['consistsOfBuildingPart']: - buildingpart = build['BuildingPart'] - i += 1 - lod = 0 - surfaces = [] - if 'lod1Solid' in buildingpart: - lod += 1 - surfaces = CityGml._lod1_solid(o) - elif 'lod1MultiSurface' in buildingpart: - lod += 1 - surfaces = CityGml._lod1_multi_surface(o) - elif 'lod2MultiSurface' in buildingpart: - # todo: check if this is a real case or a miss-formed citygml - lod = 2 - surfaces = surfaces + CityGml._lod2_solid_multi_surface(o) - else: - for bound in buildingpart['boundedBy']: - surface_type = next(iter(bound)) - if 'lod2MultiSurface' in bound[surface_type]: - lod = 2 - surfaces = surfaces + CityGml._lod2(bound) - if 'lod3Solid' in o['Building']: - lod += 4 - if 'lod4Solid' in o['Building']: - lod += 8 - name = o['Building']['@id'] - lod_terrain_str = 'lod' + str(lod) + 'TerrainIntersection' - terrains = [] - if lod_terrain_str in o['Building']: - terrains = self._terrains(o, lod_terrain_str) - year_of_construction = None - function = None - if 'yearOfConstruction' in buildingpart: - year_of_construction = buildingpart['yearOfConstruction'] - if 'function' in o['Building']: - function = o['Building']['function'] - self._city.add_city_object(Building(name, lod, surfaces, terrains, year_of_construction, function, - self._lower_corner)) + i += 1 + lod = 0 + surfaces = [] + if 'lod1Solid' in o['Building']: + lod += 1 + surfaces = CityGml._lod1_solid(o) + elif 'lod1MultiSurface' in o['Building']: + lod += 1 + surfaces = CityGml._lod1_multi_surface(o) + elif 'lod2MultiSurface' in o['Building']: + # todo: check if this is a real case or a miss-formed citygml + lod = 2 + surfaces = surfaces + CityGml._lod2_solid_multi_surface(o) + else: + for bound in o['Building']['boundedBy']: + surface_type = next(iter(bound)) + if 'lod2MultiSurface' in bound[surface_type]: + lod = 2 + surfaces = surfaces + CityGml._lod2(bound) + if 'lod3Solid' in o['Building']: + lod += 4 + if 'lod4Solid' in o['Building']: + lod += 8 + name = o['Building']['@id'] + lod_terrain_str = 'lod' + str(lod) + 'TerrainIntersection' + terrains = [] + if lod_terrain_str in o['Building']: + terrains = self._terrains(o, lod_terrain_str) + year_of_construction = None + function = None + if 'yearOfConstruction' in o['Building']: + year_of_construction = o['Building']['yearOfConstruction'] + if 'function' in o['Building']: + function = o['Building']['function'] + building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains) + self._city.add_city_object(building) + return self._city def _terrains(self, city_object, lod_terrain_str): @@ -150,10 +147,8 @@ class CityGml: @staticmethod def _lod2_solid_multi_surface(o): - for i in buildingpart['boundedBy']: - surfacedata = i polygons = [Polygon(CityGml._remove_last_point(s['Polygon']['exterior']['LinearRing']['posList'])) - for s in surfacedata['OuterCeilingSurface']['lod2MultiSurface']['MultiSurface']['surfaceMember']] + for s in o['Building']['lod2MultiSurface']['MultiSurface']['surfaceMember']] return [Surface(p,p) for p in polygons] @staticmethod @@ -181,7 +176,7 @@ class CityGml: if 'CompositeSurface' in s: surfaces = surfaces + CityGml._lod2_composite_surface(s) else: - surfaces = surfaces + CityGml._lod2_multi_surface(s, surface_type) + surfaces = surfaces + CityGml._lod2_multi_surface(s, GeometryHelper.gml_surface_to_libs(surface_type)) return surfaces @staticmethod diff --git a/tests/test_exports.py b/tests/test_exports.py index 96cf12d8..bad8ba7e 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -35,6 +35,9 @@ class TestExports(TestCase): PhysicsFactory('ca', self._city_gml).enrich() UsageFactory('ca', self._city_gml).enrich() SchedulesFactory('comnet', self._city_gml).enrich() + cli = 'C:\\Users\\Pilar\\PycharmProjects\\monthlyenergybalance\\tests_data\\weather\\inseldb_Summerland.cli' + self._city_gml.climate_file = Path(cli) + self._city_gml.climate_reference_city = 'Summerland' for building in self._city_gml.buildings: building.heating['month'] = pd.DataFrame({'INSEL': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]}) building.cooling['month'] = pd.DataFrame({'INSEL': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]}) @@ -54,3 +57,6 @@ class TestExports(TestCase): def test_energy_ade_export(self): self._export('energy_ade') + + def test_sra_export(self): + self._export('sra') \ No newline at end of file