diff --git a/hub/city_model_structure/attributes/polygon.py b/hub/city_model_structure/attributes/polygon.py index c98289be..cada1c29 100644 --- a/hub/city_model_structure/attributes/polygon.py +++ b/hub/city_model_structure/attributes/polygon.py @@ -246,7 +246,9 @@ class Polygon: polygon = shapley_polygon(coordinates) try: + vertices_2d, faces = trimesh.creation.triangulate_polygon(polygon, engine='triangle') + mesh = Trimesh(vertices=vertices, faces=faces) # check orientation diff --git a/hub/city_model_structure/attributes/polyhedron.py b/hub/city_model_structure/attributes/polyhedron.py index 553f1e26..c88e91d0 100644 --- a/hub/city_model_structure/attributes/polyhedron.py +++ b/hub/city_model_structure/attributes/polyhedron.py @@ -92,7 +92,6 @@ class Polyhedron: points = polygon.coordinates if len(points) != 3: sub_polygons = polygon.triangles - # todo: I modified this! To be checked @Guille if len(sub_polygons) >= 1: for sub_polygon in sub_polygons: face = [] diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 04c4703a..4da4ce67 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -494,6 +494,8 @@ class Building(CityObject): """ _usage = '' for internal_zone in self.internal_zones: + if internal_zone.usages is None: + continue for usage in internal_zone.usages: _usage = f'{_usage}{usage.name}_{usage.percentage} ' return _usage.rstrip() diff --git a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py index 6cd228c5..0ecac92e 100644 --- a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py +++ b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py @@ -77,7 +77,7 @@ class InselMonthlyEnergyBalance(Insel): if levels_of_detail.weather < 1: raise Exception(f'Level of detail of weather = {levels_of_detail.weather}. Required minimum level 1') if levels_of_detail.surface_radiation is None: - raise Exception(f'Level of detail of usage not assigned') + raise Exception(f'Level of detail of surface radiation not assigned') if levels_of_detail.surface_radiation < 1: raise Exception(f'Level of detail of surface radiation = {levels_of_detail.surface_radiation}. ' f'Required minimum level 1') diff --git a/hub/exports/db_factory.py b/hub/exports/db_factory.py index 5f4fde9e..959d1906 100644 --- a/hub/exports/db_factory.py +++ b/hub/exports/db_factory.py @@ -37,7 +37,7 @@ class DBFactory: def user_info(self, name, password, application_id): """ Retrieve the user info for the given name and password and application_id - :param name: the user name + :param name: the username :param password: the user password :param application_id: the application id :return: User or None @@ -47,7 +47,7 @@ class DBFactory: def user_login(self, name, password, application_uuid): """ Retrieve the user info - :param name: the user name + :param name: the username :param password: the user password :param application_uuid: the application uuid :return: User or None diff --git a/hub/hub_logger/__init__.py b/hub/hub_logger/__init__.py index d1d11d35..d61ec00c 100644 --- a/hub/hub_logger/__init__.py +++ b/hub/hub_logger/__init__.py @@ -4,10 +4,11 @@ import os import sys -def get_logger(file_logger=False): +def get_logger(file_logger=False, debug_level=logger.ERROR): """ Returns a logging object :param file_logger: a boolean to indicate the kind of logging + :param debug_level: the value for the logger level (default error) object to return, true (default) means a file logger is required :return: """ @@ -21,11 +22,11 @@ def get_logger(file_logger=False): os.mkdir(log_dir) with open(log_file, 'x'): pass - logger.basicConfig(filename=log_file, format=log_format, level=logger.DEBUG) + logger.basicConfig(filename=log_file, format=log_format, level=debug_level) return logger except IOError as err: print(f'I/O exception: {err}') else: logger.getLogger().addHandler(logger.StreamHandler(stream=sys.stdout)) - logger.getLogger().setLevel(logger.DEBUG) + logger.getLogger().setLevel(debug_level) return logger.getLogger() diff --git a/hub/imports/construction/nrcan_physics_parameters.py b/hub/imports/construction/nrcan_physics_parameters.py index d1d5db99..0d563338 100644 --- a/hub/imports/construction/nrcan_physics_parameters.py +++ b/hub/imports/construction/nrcan_physics_parameters.py @@ -35,6 +35,10 @@ class NrcanPhysicsParameters: city = self._city nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog for building in city.buildings: + if building.function not in Dictionaries().hub_function_to_nrcan_construction_function.keys(): + logger.error(f'Building {building.name} has an unknown building function {building.function}\n') + sys.stderr.write(f'Building {building.name} has an unknown building function {building.function}\n') + continue function = Dictionaries().hub_function_to_nrcan_construction_function[building.function] try: archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone) diff --git a/hub/imports/construction/nrel_physics_parameters.py b/hub/imports/construction/nrel_physics_parameters.py index c3dcbe07..9d4bf2f3 100644 --- a/hub/imports/construction/nrel_physics_parameters.py +++ b/hub/imports/construction/nrel_physics_parameters.py @@ -35,21 +35,27 @@ class NrelPhysicsParameters: city = self._city nrel_catalog = ConstructionCatalogFactory('nrel').catalog for building in city.buildings: + if building.function not in Dictionaries().hub_function_to_nrel_construction_function.keys(): + logger.error(f'Building {building.name} has unknown function [{building.function}]') + sys.stderr.write(f'Building {building.name} has unknown function [{building.function}]\n') + continue + if building.function not in Dictionaries().hub_function_to_nrel_construction_function.keys(): + logger.error(f'Building {building.name} has unknown function {building.function}\n') + sys.stderr.write(f'Building {building.name} has unknown function {building.function}\n') + continue function = Dictionaries().hub_function_to_nrel_construction_function[building.function] try: - archetype = self._search_archetype(nrel_catalog, function, building.year_of_construction, - self._climate_zone) + archetype = self._search_archetype(nrel_catalog, function, building.year_of_construction, self._climate_zone) except KeyError: logger.error(f'Building {building.name} has unknown construction archetype for building function: ' - f'{function} [{building.function}], building year of construction: {building.year_of_construction} ' - f'and climate zone {self._climate_zone}\n') + f'{function} [{building.function}], building year of construction: {building.year_of_construction}' + f' and climate zone {self._climate_zone}\n') sys.stderr.write(f'Building {building.name} has unknown construction archetype for building function: ' f'{function} [{building.function}], ' f'building year of construction: {building.year_of_construction} ' f'and climate zone {self._climate_zone}\n') continue - # if building has no thermal zones defined from geometry, and the building will be divided in storeys, # one thermal zone per storey is assigned if len(building.internal_zones) == 1: diff --git a/hub/persistence/models/city_object.py b/hub/persistence/models/city_object.py index 64eb5905..30b05323 100644 --- a/hub/persistence/models/city_object.py +++ b/hub/persistence/models/city_object.py @@ -13,36 +13,57 @@ from sqlalchemy import DateTime from hub.city_model_structure.building import Building from hub.persistence.configuration import Models + class CityObject(Models): - """ + """ A model representation of an application """ - __tablename__ = 'city_object' - id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True) - city_id = Column(Integer, ForeignKey('city.id'), nullable=False) - name = Column(String, nullable=False) - alias = Column(String, nullable=True) - type = Column(String, nullable=False) - year_of_construction = Column(Integer, nullable=True) - function = Column(String, nullable=True) - usage = Column(String, nullable=True) - volume = Column(Float, nullable=False) - area = Column(Float, nullable=False) - total_heating_area = Column(Float, nullable=False) - wall_area = Column(Float, nullable=False) - windows_area = Column(Float, nullable=False) - system_name = Column(String, nullable=False) - created = Column(DateTime, default=datetime.datetime.utcnow) - updated = Column(DateTime, default=datetime.datetime.utcnow) + __tablename__ = 'city_object' + id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True) + city_id = Column(Integer, ForeignKey('city.id'), nullable=False) + name = Column(String, nullable=False) + alias = Column(String, nullable=True) + type = Column(String, nullable=False) + year_of_construction = Column(Integer, nullable=True) + function = Column(String, nullable=True) + usage = Column(String, nullable=True) + volume = Column(Float, nullable=False) + area = Column(Float, nullable=False) + total_heating_area = Column(Float, nullable=False) + wall_area = Column(Float, nullable=False) + windows_area = Column(Float, nullable=False) + system_name = Column(String, nullable=False) + created = Column(DateTime, default=datetime.datetime.utcnow) + updated = Column(DateTime, default=datetime.datetime.utcnow) - # def __init__(self, city_id, name, alias, object_type, year_of_construction, function, usage, volume, area): - def __init__(self, city_id, building: Building): - self.city_id = city_id - self.name = building.name - self.alias = building.alias - self.type = building.type - self.year_of_construction = building.year_of_construction - self.function = building.function - self.usage = building.usages_percentage - self.volume = building.volume - self.area = building.floor_area + # def __init__(self, city_id, name, alias, object_type, year_of_construction, function, usage, volume, area): + def __init__(self, city_id, building: Building): + self.city_id = city_id + self.name = building.name + self.alias = building.alias + self.type = building.type + self.year_of_construction = building.year_of_construction + self.function = building.function + self.usage = building.usages_percentage + self.volume = building.volume + self.area = building.floor_area + storeys = building.storeys_above_ground + if storeys is None: + print(building.average_storey_height) + storeys = building.max_height / building.average_storey_height + self.total_heating_area = building.floor_area * storeys + wall_area = 0 + for wall in building.walls: + wall_area += wall.solid_polygon.area + self.wall_area = wall_area + window_ratio = 0 + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + for thermal_boundary in thermal_zone.thermal_boundaries: + window_ratio = thermal_boundary.window_ratio + break + self.windows_area = wall_area * window_ratio + system_name = building.energy_systems_archetype_name + if system_name is None: + system_name = '' + self.system_name = system_name diff --git a/hub/persistence/repositories/city.py b/hub/persistence/repositories/city.py index 7515bfef..ac535877 100644 --- a/hub/persistence/repositories/city.py +++ b/hub/persistence/repositories/city.py @@ -76,7 +76,8 @@ class City(Repository): """ try: now = datetime.datetime.utcnow() - self.session.query(Model).filter(Model.id == city_id).update({'name': city.name,'updated': now}) + print(f'{now}') + self.session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now}) self.session.commit() except SQLAlchemyError as err: logger.error(f'Error while updating city: {err}') diff --git a/hub/persistence/repositories/city_object.py b/hub/persistence/repositories/city_object.py index 7ca4d8e6..8a33df7c 100644 --- a/hub/persistence/repositories/city_object.py +++ b/hub/persistence/repositories/city_object.py @@ -41,20 +41,8 @@ class CityObject(Repository): city_object = self.get_by_name_and_city(building.name, city_id) if city_object is None: try: - object_usage = '' - for internal_zone in building.internal_zones: - for usage in internal_zone.usages: - object_usage = f'{object_usage}{usage.name}_{usage.percentage} ' - object_usage = object_usage.rstrip() city_object = Model(city_id=city_id, - name=building.name, - alias=building.alias, - object_type=building.type, - year_of_construction=building.year_of_construction, - function=building.function, - usage=object_usage, - volume=building.volume, - area=building.floor_area) + building=building) self.session.add(city_object) self.session.flush() self.session.commit() diff --git a/hub/unittests/test_construction_factory.py b/hub/unittests/test_construction_factory.py index 1c5c1b52..8a29398b 100644 --- a/hub/unittests/test_construction_factory.py +++ b/hub/unittests/test_construction_factory.py @@ -284,12 +284,3 @@ class TestConstructionFactory(TestCase): self.assertIsNotNone(thermal_boundary.layers, 'layers is none') self._check_thermal_openings(thermal_boundary) self._check_surfaces(thermal_boundary) - - def test_archetype_not_found(self): - file = 'pluto_building.gml' - city = self._get_citygml(file) - for building in city.buildings: - building.year_of_construction = 1990 - building.function = 'office' - ConstructionFactory('nrel', city).enrich() - diff --git a/hub/unittests/test_db_factory.py b/hub/unittests/test_db_factory.py index 6e92c294..391f21ff 100644 --- a/hub/unittests/test_db_factory.py +++ b/hub/unittests/test_db_factory.py @@ -11,6 +11,8 @@ from pathlib import Path import sqlalchemy.exc from hub.imports.geometry_factory import GeometryFactory +from hub.imports.construction_factory import ConstructionFactory +from hub.imports.usage_factory import UsageFactory from hub.imports.db_factory import DBFactory as ImportDBFactory from hub.imports.user_factory import UserFactory from hub.exports.db_factory import DBFactory as ExportDBFactory @@ -18,29 +20,34 @@ from hub.persistence.repository import Repository from sqlalchemy import create_engine from hub.persistence.models import City, Application, CityObject from hub.persistence.models import User, UserRoles +from hub.helpers.dictionaries import Dictionaries from sqlalchemy.exc import ProgrammingError import uuid -class Configure: +class Configure: _skip_test = False _skip_reason = 'PostgreSQL not properly installed in host machine' def __init__(self): """ - Test - setup - :return: None - """ +Test +setup +:return: None +""" self._skip_test = False # Create test database - dotenv_path = str(Path("{}/.local/etc/hub/.env".format(os.path.expanduser('~'))).resolve()) - repository = Repository(db_name='hub_unittest', app_env='TEST', dotenv_path=dotenv_path) + dotenv_path = Path("{}/.local/etc/hub/.env".format(os.path.expanduser('~'))).resolve() + if not dotenv_path.exists(): + self._skip_test = True + self._skip_reason = f'.env file missing at {dotenv_path}' + return + dotenv_path = str(dotenv_path) + repository = Repository(db_name='hub_unittests', app_env='TEST', dotenv_path=dotenv_path) engine = create_engine(repository.configuration.connection_string) try: # delete test database if it exists connection = engine.connect() - connection.execute('commit') connection.close() except ProgrammingError: print(f'Database does not exist. Nothing to delete') @@ -55,7 +62,12 @@ class Configure: CityObject.__table__.create(bind=repository.engine, checkfirst=True) city_file = "tests_data/FZK_Haus_LoD_2.gml" - self._city = GeometryFactory('citygml', city_file).city + self._city = GeometryFactory('citygml', + city_file, + function_to_hub=Dictionaries().alkis_function_to_hub_function).city + ConstructionFactory('nrcan', self._city).enrich() + UsageFactory('nrcan', self._city).enrich() + self._import_db_factory = ImportDBFactory( db_name=repository.configuration.db_name, app_env='TEST', @@ -103,7 +115,7 @@ class Configure: @property def message(self): - return self._skip_message + return self._skip_reason @property def city(self): @@ -113,12 +125,14 @@ class Configure: def pickle_path(self): return self._pickle_path + configure = Configure() + class TestDBFactory(TestCase): """ - TestDBFactory - """ +TestDBFactory +""" @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_save_application(self): @@ -129,43 +143,29 @@ class TestDBFactory(TestCase): @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_save_city(self): configure.city.name = "Montreal" - saved_city = configure.import_db_factory.persist_city( + city = configure.import_db_factory.persist_city( configure.city, configure.pickle_path, configure.application.id, configure.user.id) - self.assertEqual(saved_city.name, 'Montreal') - self.assertEqual(saved_city.pickle_path, self.pickle_path) - self.assertEqual(saved_city.level_of_detail, self.city.level_of_detail.geometry) - self._db_factory.delete_city(saved_city.id) - - @unittest.skipIf(configure.skip_test, configure.skip_reason) - def test_get_city_by_name(self): - city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) - retrieved_city = self._export_db_factory.get_city_by_name(city.name) - self.assertEqual(retrieved_city[0].application_id, 1) - self.assertEqual(retrieved_city[0].user_id, self._user.id) - self._db_factory.delete_city(city.id) - - @unittest.skipIf(configure.skip_test, configure.skip_reason) - def test_get_city_by_user(self): - city = self._import_db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) - retrieved_city = self._export_db_factory.get_city_by_user(self._user.id) - self.assertEqual(retrieved_city[0].pickle_path, self.pickle_path) - self._db_factory.delete_city(city.id) - - @unittest.skipIf(configure.skip_test, configure.skip_reason) - def test_get_city_by_id(self): - city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) - retrieved_city = self._export_db_factory.get_city(city.id) - self.assertEqual(retrieved_city.level_of_detail, self.city.level_of_detail.geometry) - self._db_factory.delete_city(city.id) + self.assertEqual(city.name, 'Montreal') + self.assertEqual(city.pickle_path, configure.pickle_path) + self.assertEqual(city.level_of_detail, configure.city.level_of_detail.geometry) + configure.import_db_factory.delete_city(city.id) @unittest.skipIf(configure.skip_test, configure.skip_reason) def test_get_update_city(self): - city = self._db_factory.persist_city(self.city, self.pickle_path, self.application.id, self._user.id) - self.city.name = "Ottawa" - self._db_factory.update_city(city.id, self.city) - updated_city = self._export_db_factory.get_city(city.id) - self.assertEqual(updated_city.name, self.city.name) - self._db_factory.delete_city(city.id) + city = configure.import_db_factory.persist_city(configure.city, + configure.pickle_path, + configure.application.id, + configure._user.id) + city.name = "Ottawa" + configure.import_db_factory.update_city(city.id, configure.city) + cities = configure.export_db_factory.cities_by_user_and_application( + configure.user.id, + configure.application.id) + for updated_city in cities: + if updated_city.id == city.id: + self.assertEqual(updated_city.name, city.name) + break + configure.import_db_factory.delete_city(city.id) diff --git a/hub/unittests/test_insel_exports.py b/hub/unittests/test_insel_exports.py index 2fbc54c1..30d73e1f 100644 --- a/hub/unittests/test_insel_exports.py +++ b/hub/unittests/test_insel_exports.py @@ -63,6 +63,7 @@ class TestExports(TestCase): :parameter city: city :return: none """ + city.level_of_detail.surface_radiation = 2 for radiation in self._read_sra_file: city_object_name = radiation.columns.values.tolist()[1].split(':')[1] building = city.city_object(city_object_name) diff --git a/hub/unittests/tests_data/pickle_path.bz2 b/hub/unittests/tests_data/pickle_path.bz2 index 8a80bdae..df2daadf 100644 Binary files a/hub/unittests/tests_data/pickle_path.bz2 and b/hub/unittests/tests_data/pickle_path.bz2 differ