From 16adfaa1a80a9011eee117f9fcacf31beabd3738 Mon Sep 17 00:00:00 2001 From: guille Date: Thu, 2 Feb 2023 13:00:58 -0500 Subject: [PATCH] Full implementation of basic persistence tasks for the one_shot admin tool. --- hub/exports/db_factory.py | 10 ++++++++ hub/imports/db_factory.py | 17 +++++++++++++- hub/imports/geometry/citygml.py | 17 ++++++++++---- hub/imports/geometry/geojson.py | 11 ++++++++- hub/imports/geometry_factory.py | 16 ++++++++++--- hub/persistence/__init__.py | 2 ++ hub/persistence/repositories/city.py | 23 +++++++++++++++++-- hub/persistence/repositories/city_object.py | 13 +++++++---- .../repositories/simulation_results.py | 8 +++---- hub/persistence/repositories/user.py | 8 +++---- 10 files changed, 102 insertions(+), 23 deletions(-) diff --git a/hub/exports/db_factory.py b/hub/exports/db_factory.py index 406818c3..32906fde 100644 --- a/hub/exports/db_factory.py +++ b/hub/exports/db_factory.py @@ -6,6 +6,8 @@ Project CoderPeter Yefi peteryefi@gmail.com """ from hub.persistence import City from hub.persistence import Application +from hub.persistence import User +from hub.persistence import CityObject class DBFactory: @@ -16,6 +18,8 @@ class DBFactory: def __init__(self, db_name, app_env, dotenv_path): self._city = City(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) self._application = Application(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) + self._user = User(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) + self._city_object = CityObject(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path) def get_city(self, city_id): """ @@ -41,3 +45,9 @@ class DBFactory: def application_info(self, application_uuid): return self._application.get_by_uuid(application_uuid) + def user_info(self, name, password, application_id): + return self._user.get_by_name_application_and_password(name, password, application_id) + + def building_info(self, name, city_id): + return self._city_object.get_by_name_and_city(name, city_id) + diff --git a/hub/imports/db_factory.py b/hub/imports/db_factory.py index d23f2428..7ec0e4f4 100644 --- a/hub/imports/db_factory.py +++ b/hub/imports/db_factory.py @@ -6,6 +6,7 @@ Project CoderPeter Yefi peteryefi@gmail.com """ from hub.city_model_structure.city import City from hub.persistence import City as CityRepository +from hub.persistence import SimulationResults class DBFactory: @@ -15,10 +16,14 @@ class DBFactory: def __init__(self, db_name, dotenv_path, app_env): self._city_repository = CityRepository(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) + self._simulation_results = SimulationResults(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) - def persist_city(self, application_id: int, user_id: int, city: City): + def persist_city(self, city: City, application_id: int, user_id: int): """ Persist city into postgres database + :param city: City to be stored + :param application_id: Application id owning this city + :param user_id: User who create the city """ return self._city_repository.insert(city, application_id, user_id) @@ -36,3 +41,13 @@ class DBFactory: :param city_id: the id of the city to get """ self._city_repository.delete(city_id) + + def add_simulation_results(self, name, values, city_id=None, city_object_id=None): + """ + Add simulation results to the city or to the city_object + :param name: simulation and simulation engine name + :param values: simulation values in json format + :param city_id: city id or None + :param city_object_id: city object id or None + """ + self._simulation_results.insert(name, values,city_id, city_object_id) diff --git a/hub/imports/geometry/citygml.py b/hub/imports/geometry/citygml.py index d6701393..bcd3dec8 100644 --- a/hub/imports/geometry/citygml.py +++ b/hub/imports/geometry/citygml.py @@ -19,12 +19,18 @@ class CityGml: """ CityGml class """ - def __init__(self, path, extrusion_height_field=None, year_of_construction_field=None, function_field=None): + def __init__(self, + path, + extrusion_height_field=None, + year_of_construction_field='yearOfConstruction', + function_field='function', + function_to_hub=None): self._city = None self._lod = None self._lod1_tags = ['lod1Solid', 'lod1MultiSurface'] self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve'] self._extrusion_height_field = extrusion_height_field + self._function_to_hub = function_to_hub self._year_of_construction_field = year_of_construction_field if function_field is None: function_field = 'function' @@ -110,12 +116,15 @@ class CityGml: name = city_object['@id'] function = None year_of_construction = None - if 'yearOfConstruction' in city_object: - year_of_construction = city_object['yearOfConstruction'] - if 'function' in city_object: + if self._year_of_construction_field in city_object: + year_of_construction = city_object[self._year_of_construction_field] + if self._function_field in city_object: function = city_object[self._function_field] if type(function) != str: function = function['#text'] + if self._function_to_hub is not None: + # use the transformation dictionary to retrieve the proper function + function = self._function_to_hub[function] if any(key in city_object for key in self._lod1_tags): if self._lod is None or self._lod > 1: diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index f7c91082..2735982f 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -26,7 +26,12 @@ class Geojson: X = 0 Y = 1 - def __init__(self, path, extrusion_height_field=None, year_of_construction_field=None, function_field=None): + def __init__(self, + path, + extrusion_height_field=None, + year_of_construction_field=None, + function_field=None, + function_to_hub=None): # todo: destination epsg should change according actual the location self._transformer = Transformer.from_crs('epsg:4326', 'epsg:26911') self._min_x = cte.MAX_FLOAT @@ -38,6 +43,7 @@ class Geojson: self._extrusion_height_field = extrusion_height_field self._year_of_construction_field = year_of_construction_field self._function_field = function_field + self._function_to_hub = function_to_hub with open(path) as json_file: self._geojson = json.loads(json_file.read()) @@ -118,6 +124,9 @@ class Geojson: function = None if self._function_field is not None: function = feature['properties'][self._function_field] + if self._function_to_hub is not None: + # use the transformation dictionary to retrieve the proper function + function = self._function_to_hub[function] geometry = feature['geometry'] if 'id' in feature: building_name = feature['id'] diff --git a/hub/imports/geometry_factory.py b/hub/imports/geometry_factory.py index f80580a5..b8ba7e68 100644 --- a/hub/imports/geometry_factory.py +++ b/hub/imports/geometry_factory.py @@ -26,7 +26,8 @@ class GeometryFactory: data_frame=None, height_field=None, year_of_construction_field=None, - function_field=None): + function_field=None, + function_to_hub=None): self._file_type = '_' + file_type.lower() class_funcs = validate_import_export_type(GeometryFactory) if self._file_type not in class_funcs: @@ -38,6 +39,7 @@ class GeometryFactory: self._height_field = height_field self._year_of_construction_field = year_of_construction_field self._function_field = function_field + self._function_to_hub = function_to_hub @property def _citygml(self) -> City: @@ -45,7 +47,11 @@ class GeometryFactory: Enrich the city by using CityGML information as data source :return: City """ - return CityGml(self._path, self._height_field, self._year_of_construction_field, self._function_field).city + return CityGml(self._path, + self._height_field, + self._year_of_construction_field, + self._function_field, + self._function_to_hub).city @property def _obj(self) -> City: @@ -71,7 +77,11 @@ class GeometryFactory: Enrich the city by using Geojson information as data source :return: City """ - return Geojson(self._path, self._height_field, self._year_of_construction_field, self._function_field).city + return Geojson(self._path, + self._height_field, + self._year_of_construction_field, + self._function_field, + self._function_to_hub).city @property def _osm_subway(self) -> City: diff --git a/hub/persistence/__init__.py b/hub/persistence/__init__.py index 264a5ee7..19c4e0dc 100644 --- a/hub/persistence/__init__.py +++ b/hub/persistence/__init__.py @@ -1,6 +1,8 @@ from .repository import Repository from .repositories.city import City from .repositories.application import Application +from .repositories.simulation_results import SimulationResults +from .repositories.city_object import CityObject from .db_setup import DBSetup from .repositories.user import User from .models.user import UserRoles diff --git a/hub/persistence/repositories/city.py b/hub/persistence/repositories/city.py index 592d88df..812f83e8 100644 --- a/hub/persistence/repositories/city.py +++ b/hub/persistence/repositories/city.py @@ -16,6 +16,7 @@ from hub.city_model_structure.city import City as CityHub from hub.hub_logger import logger from hub.persistence import Repository from hub.persistence.models import City as Model +from hub.persistence.models import CityObject from hub.version import __version__ @@ -45,17 +46,35 @@ class City(Repository): db_city = Model( pickle.dumps(city), city.name, - city.level_of_detail, - city.climate_file, + city.level_of_detail.geometry, + 'None' if city.climate_file is None else city.climate_file, application_id, user_id, __version__) self.session.add(db_city) self.session.flush() + for building in city.buildings: + 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() + db_city_object = CityObject(db_city.id, + building.name, + building.alias, + building.type, + building.year_of_construction, + building.function, + object_usage, + building.volume, + building.floor_area) + self.session.add(db_city_object) + self.session.flush() self.session.commit() return db_city except SQLAlchemyError as err: + print(f'An error occurred while creating city: {err}') logger.error(f'An error occurred while creating city: {err}') def update(self, city_id: int, city: CityHub): diff --git a/hub/persistence/repositories/city_object.py b/hub/persistence/repositories/city_object.py index 3f4a6949..099bfcef 100644 --- a/hub/persistence/repositories/city_object.py +++ b/hub/persistence/repositories/city_object.py @@ -77,7 +77,7 @@ class CityObject(Repository): for usage in internal_zone.usages: object_usage = f'{object_usage}{usage.name}_{usage.percentage} ' object_usage = object_usage.rstrip() - self.session.query(Model).filter(Model.name == building.name and Model.city_id == city_id).update( + self.session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update( {'name': building.name, 'alias': building.alias, 'object_type': building.type, @@ -100,7 +100,7 @@ class CityObject(Repository): :return: None """ try: - self.session.query(Model).filter(Model.city_id == city_id and Model.name == name).delete() + self.session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete() self.session.commit() except SQLAlchemyError as err: logger.error(f'Error while deleting application: {err}') @@ -113,8 +113,13 @@ class CityObject(Repository): :return: [CityObject] with the provided name belonging to the city with id city_id """ try: - return self.session.execute(select(Model).where( - Model.name == name and Model.city_id == city_id + _city_object = self.session.execute(select(Model).where( + Model.name == name, Model.city_id == city_id )).first() + print(city_id, name, _city_object) + if _city_object is None: + return None + return _city_object[0] except SQLAlchemyError as err: + print(err) logger.error(f'Error while fetching application by application_uuid: {err}') diff --git a/hub/persistence/repositories/simulation_results.py b/hub/persistence/repositories/simulation_results.py index d163d4da..ce612620 100644 --- a/hub/persistence/repositories/simulation_results.py +++ b/hub/persistence/repositories/simulation_results.py @@ -74,14 +74,14 @@ class SimulationResults(Repository): """ try: if city_id is not None: - self.session.query(Model).filter(Model.name == name and Model.city_id == city_id).update( + self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).update( { 'values': values, 'updated': datetime.datetime.utcnow() }) self.session.commit() elif city_object_id is not None: - self.session.query(Model).filter(Model.name == name and Model.city_object_id == city_object_id).update( + self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update( { 'values': values, 'updated': datetime.datetime.utcnow() @@ -104,10 +104,10 @@ class SimulationResults(Repository): """ try: if city_id is not None: - self.session.query(Model).filter(Model.name == name and Model.city_id == city_id).delete() + self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete() self.session.commit() elif city_object_id is not None: - self.session.query(Model).filter(Model.name == name and Model.city_object_id == city_object_id).delete() + self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete() self.session.commit() else: return {'message': 'Missing either city_id or city_object_id'} diff --git a/hub/persistence/repositories/user.py b/hub/persistence/repositories/user.py index 65807481..dfa797d6 100644 --- a/hub/persistence/repositories/user.py +++ b/hub/persistence/repositories/user.py @@ -94,12 +94,12 @@ class User(Repository): """ try: return self.session.execute( - select(Model).where(Model.name == name and Model.application_id == application_id) + select(Model).where(Model.name == name, Model.application_id == application_id) ).first() except SQLAlchemyError as err: logger.error(f'Error while fetching user by name and application: {err}') - def get_user_by_name_application_and_password(self, name: str, password: str, application_id: int) -> [Model]: + def get_by_name_application_and_password(self, name: str, password: str, application_id: int) -> [Model]: """ Fetch user based on the email and password :param name: User name @@ -110,11 +110,11 @@ class User(Repository): """ try: user = self.session.execute( - select(Model).where(Model.name == name and Model.application_id == application_id) + select(Model).where(Model.name == name, Model.application_id == application_id) ).first() if user: if Auth.check_password(password, user[0].password): - return user + return user[0] return {'message': 'invalid login information'} except SQLAlchemyError as err: logger.error(f'Error while fetching user by email: {err}')