Rename of imports and modified requirements to match this specific library
This commit is contained in:
parent
a296769361
commit
10fe42bbda
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
12
.idea/cerc_persistence.iml
Normal file
12
.idea/cerc_persistence.iml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="jdk" jdkName="cerc_env" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
7
.idea/misc.xml
Normal file
7
.idea/misc.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.11" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="cerc_env" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/cerc_persistence.iml" filepath="$PROJECT_DIR$/.idea/cerc_persistence.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
24
README.md
24
README.md
@ -1,32 +1,47 @@
|
|||||||
|
## TODO
|
||||||
|
|
||||||
|
- Figure out what version to put
|
||||||
|
|
||||||
## Database Persistence ##
|
## Database Persistence ##
|
||||||
|
|
||||||
The persistence package includes classes to store different class objects in a Postgres database.
|
The persistence package includes classes to store different class objects in a Postgres database.
|
||||||
|
|
||||||
### models ###
|
### models ###
|
||||||
|
|
||||||
This defines models for all class objects that we want to persist. It is used for Object Relation Mapping (ORM)
|
This defines models for all class objects that we want to persist. It is used for Object Relation Mapping (ORM)
|
||||||
of the class objects to database table columns
|
of the class objects to database table columns
|
||||||
|
|
||||||
### repositories ###
|
### repositories ###
|
||||||
This defines repository classes that contain CRUD methods for database operations. The constructor of all repositories requires
|
|
||||||
|
This defines repository classes that contain CRUD methods for database operations. The constructor of all repositories
|
||||||
|
requires
|
||||||
The database name to connect to and the application environment (PROD or TEST). Tests use a different database
|
The database name to connect to and the application environment (PROD or TEST). Tests use a different database
|
||||||
from the production environment, which is why this is necessary. An example is shown below
|
from the production environment, which is why this is necessary. An example is shown below
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from hub.persistence import CityRepo
|
from cerc_persistence import CityRepo
|
||||||
|
|
||||||
# instantiate city repo for hub production database
|
# instantiate city repo for hub production database
|
||||||
city_repo = CityRepo(db_name='hub', app_env='PROD')
|
city_repo = CityRepo(db_name='hub', app_env='PROD')
|
||||||
```
|
```
|
||||||
|
|
||||||
All database operations are conducted with the production database (*PROD*) named *hub* in the example above
|
All database operations are conducted with the production database (*PROD*) named *hub* in the example above
|
||||||
|
|
||||||
### config_db ##
|
### config_db ##
|
||||||
|
|
||||||
This Python file is a configuration class that contains variables that map to configuration parameters in a .env file.
|
This Python file is a configuration class that contains variables that map to configuration parameters in a .env file.
|
||||||
It also contains a method ``def conn_string()`` which returns the connection string to a Postgres database.
|
It also contains a method ``def conn_string()`` which returns the connection string to a Postgres database.
|
||||||
|
|
||||||
### Base ##
|
### Base ##
|
||||||
This class has a constructor that establishes a database connection and returns a reference for database-related CRUD operations.
|
|
||||||
|
This class has a constructor that establishes a database connection and returns a reference for database-related CRUD
|
||||||
|
operations.
|
||||||
|
|
||||||
### Database Configuration Parameter ###
|
### Database Configuration Parameter ###
|
||||||
A .env file (or environment variables) with configuration parameters described below are needed to establish a database connection:
|
|
||||||
|
A .env file (or environment variables) with configuration parameters described below are needed to establish a database
|
||||||
|
connection:
|
||||||
|
|
||||||
```
|
```
|
||||||
# production database credentials
|
# production database credentials
|
||||||
PROD_DB_USER=postgres-database-user
|
PROD_DB_USER=postgres-database-user
|
||||||
@ -42,6 +57,7 @@ TEST_DB_PORT=database-port
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Database Related Unit Test
|
### Database Related Unit Test
|
||||||
|
|
||||||
Unit tests that involve database operations require a Postgres database to be set up.
|
Unit tests that involve database operations require a Postgres database to be set up.
|
||||||
The tests connect to the database server using the default postgres user (*postgres*).
|
The tests connect to the database server using the default postgres user (*postgres*).
|
||||||
NB: You can provide any credentials for the test to connect to postgres, just make sure
|
NB: You can provide any credentials for the test to connect to postgres, just make sure
|
||||||
|
67
cerc_persistence/configuration.py
Normal file
67
cerc_persistence/configuration.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
"""
|
||||||
|
Persistence (Postgresql) configuration
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
Models = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class Configuration:
|
||||||
|
"""
|
||||||
|
Configuration class to hold common persistence configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env='TEST'):
|
||||||
|
"""
|
||||||
|
:param db_name: database name
|
||||||
|
:param app_env: application environment, test or production
|
||||||
|
:param dotenv_path: the absolute path to dotenv file
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# load environmental variables
|
||||||
|
if not Path(dotenv_path).exists():
|
||||||
|
error_message = f'dotenv file doesn\'t exists at {dotenv_path}'
|
||||||
|
logging.error(error_message)
|
||||||
|
raise FileNotFoundError(error_message)
|
||||||
|
load_dotenv(dotenv_path=dotenv_path)
|
||||||
|
|
||||||
|
self._db_name = db_name
|
||||||
|
self._db_host = os.getenv(f'{app_env}_DB_HOST')
|
||||||
|
self._db_user = os.getenv(f'{app_env}_DB_USER')
|
||||||
|
self._db_pass = os.getenv(f'{app_env}_DB_PASSWORD')
|
||||||
|
self._db_port = os.getenv(f'{app_env}_DB_PORT')
|
||||||
|
self.hub_token = os.getenv('HUB_TOKEN')
|
||||||
|
except KeyError as err:
|
||||||
|
logging.error('Error with credentials: %s', err)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def connection_string(self):
|
||||||
|
"""
|
||||||
|
Returns a connection string postgresql
|
||||||
|
:return: connection string
|
||||||
|
"""
|
||||||
|
if self._db_pass:
|
||||||
|
return f'postgresql://{self._db_user}:{self._db_pass}@{self._db_host}:{self._db_port}/{self._db_name}'
|
||||||
|
return f'postgresql://{self._db_user}@{self._db_host}:{self._db_port}/{self._db_name}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db_user(self):
|
||||||
|
"""
|
||||||
|
retrieve the configured username
|
||||||
|
"""
|
||||||
|
return self._db_user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db_name(self):
|
||||||
|
"""
|
||||||
|
retrieve the configured database name
|
||||||
|
"""
|
||||||
|
return self._db_name
|
248
cerc_persistence/db_control.py
Normal file
248
cerc_persistence/db_control.py
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
"""
|
||||||
|
DBFactory performs read related operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project CoderPeter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from cerc_persistence.repositories.application import Application
|
||||||
|
from cerc_persistence.repositories.city import City
|
||||||
|
from cerc_persistence.repositories.city_object import CityObject
|
||||||
|
from cerc_persistence.repositories.simulation_results import SimulationResults
|
||||||
|
from cerc_persistence.repositories.user import User
|
||||||
|
from cerc_persistence.repositories.user import UserRoles
|
||||||
|
|
||||||
|
|
||||||
|
class DBControl:
|
||||||
|
"""
|
||||||
|
DBFactory class
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_name, app_env, dotenv_path):
|
||||||
|
self._city = City(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env)
|
||||||
|
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)
|
||||||
|
self._simulation_results = SimulationResults(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env)
|
||||||
|
|
||||||
|
def application_info(self, application_uuid) -> Application:
|
||||||
|
"""
|
||||||
|
Retrieve the application info for the given uuid from the database
|
||||||
|
:param application_uuid: the uuid for the application
|
||||||
|
:return: Application
|
||||||
|
"""
|
||||||
|
return self._application.get_by_uuid(application_uuid)
|
||||||
|
|
||||||
|
def user_info(self, name, password, application_id) -> User:
|
||||||
|
"""
|
||||||
|
Retrieve the user info for the given name and password and application_id from the database
|
||||||
|
:param name: the username
|
||||||
|
:param password: the user password
|
||||||
|
:param application_id: the application id
|
||||||
|
:return: User
|
||||||
|
"""
|
||||||
|
return self._user.get_by_name_application_id_and_password(name, password, application_id)
|
||||||
|
|
||||||
|
def user_login(self, name, password, application_uuid) -> User:
|
||||||
|
"""
|
||||||
|
Retrieve the user info from the database
|
||||||
|
:param name: the username
|
||||||
|
:param password: the user password
|
||||||
|
:param application_uuid: the application uuid
|
||||||
|
:return: User
|
||||||
|
"""
|
||||||
|
return self._user.get_by_name_application_uuid_and_password(name, password, application_uuid)
|
||||||
|
|
||||||
|
def cities_by_user_and_application(self, user_id, application_id) -> [City]:
|
||||||
|
"""
|
||||||
|
Retrieve the cities belonging to the user and the application from the database
|
||||||
|
:param user_id: User id
|
||||||
|
:param application_id: Application id
|
||||||
|
:return: [City]
|
||||||
|
"""
|
||||||
|
return self._city.get_by_user_id_and_application_id(user_id, application_id)
|
||||||
|
|
||||||
|
def building(self, name, user_id, application_id, scenario) -> CityObject:
|
||||||
|
"""
|
||||||
|
Retrieve the building from the database
|
||||||
|
:param name: Building name
|
||||||
|
:param user_id: User id
|
||||||
|
:param application_id: Application id
|
||||||
|
:param scenario: Scenario
|
||||||
|
:
|
||||||
|
"""
|
||||||
|
cities = self._city.get_by_user_id_application_id_and_scenario(user_id, application_id, scenario)
|
||||||
|
for city in cities:
|
||||||
|
result = self.building_info(name, city[0].id)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
return None
|
||||||
|
|
||||||
|
def building_info(self, name, city_id) -> CityObject:
|
||||||
|
"""
|
||||||
|
Retrieve the building info from the database
|
||||||
|
:param name: Building name
|
||||||
|
:param city_id: City ID
|
||||||
|
:return: CityObject
|
||||||
|
"""
|
||||||
|
return self._city_object.get_by_name_or_alias_and_city(name, city_id)
|
||||||
|
|
||||||
|
def buildings_info(self, request_values, city_id) -> [CityObject]:
|
||||||
|
"""
|
||||||
|
Retrieve the buildings info from the database
|
||||||
|
:param request_values: Building names
|
||||||
|
:param city_id: City ID
|
||||||
|
:return: [CityObject]
|
||||||
|
"""
|
||||||
|
buildings = []
|
||||||
|
for name in request_values['names']:
|
||||||
|
buildings.append(self.building_info(name, city_id))
|
||||||
|
return buildings
|
||||||
|
|
||||||
|
def results(self, user_id, application_id, request_values, result_names=None) -> Dict:
|
||||||
|
"""
|
||||||
|
Retrieve the simulation results for the given cities from the database
|
||||||
|
:param user_id: the user id owning the results
|
||||||
|
:param application_id: the application id owning the results
|
||||||
|
:param request_values: dictionary containing the scenario and building names to grab the results
|
||||||
|
:param result_names: if given, filter the results to the selected names
|
||||||
|
"""
|
||||||
|
if result_names is None:
|
||||||
|
result_names = []
|
||||||
|
results = {}
|
||||||
|
for scenario in request_values['scenarios']:
|
||||||
|
for scenario_name in scenario.keys():
|
||||||
|
result_sets = self._city.get_by_user_id_application_id_and_scenario(
|
||||||
|
user_id,
|
||||||
|
application_id,
|
||||||
|
scenario_name
|
||||||
|
)
|
||||||
|
if result_sets is None:
|
||||||
|
continue
|
||||||
|
for result_set in result_sets:
|
||||||
|
city_id = result_set[0].id
|
||||||
|
|
||||||
|
results[scenario_name] = []
|
||||||
|
for building_name in scenario[scenario_name]:
|
||||||
|
_building = self._city_object.get_by_name_or_alias_and_city(building_name, city_id)
|
||||||
|
if _building is None:
|
||||||
|
continue
|
||||||
|
city_object_id = _building.id
|
||||||
|
_ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names(
|
||||||
|
city_id,
|
||||||
|
city_object_id,
|
||||||
|
result_names)
|
||||||
|
|
||||||
|
for value in _:
|
||||||
|
values = json.loads(value.values)
|
||||||
|
values["building"] = building_name
|
||||||
|
results[scenario_name].append(values)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int):
|
||||||
|
"""
|
||||||
|
Creates a city into the database
|
||||||
|
:param city: City to be stored
|
||||||
|
:param pickle_path: Path to save the pickle file
|
||||||
|
:param scenario: Simulation scenario name
|
||||||
|
:param application_id: Application id owning this city
|
||||||
|
:param user_id: User who create the city
|
||||||
|
return identity_id
|
||||||
|
"""
|
||||||
|
return self._city.insert(city, pickle_path, scenario, application_id, user_id)
|
||||||
|
|
||||||
|
def update_city(self, city_id, city):
|
||||||
|
"""
|
||||||
|
Update an existing city in the database
|
||||||
|
:param city_id: the id of the city to update
|
||||||
|
:param city: the updated city object
|
||||||
|
"""
|
||||||
|
return self._city.update(city_id, city)
|
||||||
|
|
||||||
|
def persist_application(self, name: str, description: str, application_uuid: str):
|
||||||
|
"""
|
||||||
|
Creates information for an application in the database
|
||||||
|
:param name: name of application
|
||||||
|
:param description: the description of the application
|
||||||
|
:param application_uuid: the uuid of the application to be created
|
||||||
|
"""
|
||||||
|
return self._application.insert(name, description, application_uuid)
|
||||||
|
|
||||||
|
def update_application(self, name: str, description: str, application_uuid: str):
|
||||||
|
"""
|
||||||
|
Update the application information stored in the database
|
||||||
|
:param name: name of application
|
||||||
|
:param description: the description of the application
|
||||||
|
:param application_uuid: the uuid of the application to be created
|
||||||
|
"""
|
||||||
|
return self._application.update(application_uuid, name, description)
|
||||||
|
|
||||||
|
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 to the database
|
||||||
|
: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
|
||||||
|
"""
|
||||||
|
return self._simulation_results.insert(name, values, city_id, city_object_id)
|
||||||
|
|
||||||
|
def create_user(self, name: str, application_id: int, password: str, role: UserRoles):
|
||||||
|
"""
|
||||||
|
Creates a new user in the database
|
||||||
|
:param name: the name of the user
|
||||||
|
:param application_id: the application id of the user
|
||||||
|
:param password: the password of the user
|
||||||
|
:param role: the role of the user
|
||||||
|
"""
|
||||||
|
return self._user.insert(name, password, role, application_id)
|
||||||
|
|
||||||
|
def update_user(self, user_id: int, name: str, password: str, role: UserRoles):
|
||||||
|
"""
|
||||||
|
Updates a user in the database
|
||||||
|
:param user_id: the id of the user
|
||||||
|
:param name: the name of the user
|
||||||
|
:param password: the password of the user
|
||||||
|
:param role: the role of the user
|
||||||
|
"""
|
||||||
|
return self._user.update(user_id, name, password, role)
|
||||||
|
|
||||||
|
def get_by_name_and_application(self, name: str, application: int):
|
||||||
|
"""
|
||||||
|
Retrieve a single user from the database
|
||||||
|
:param name: username
|
||||||
|
:param application: application accessing hub
|
||||||
|
"""
|
||||||
|
return self._user.get_by_name_and_application(name, application)
|
||||||
|
|
||||||
|
def delete_user(self, user_id):
|
||||||
|
"""
|
||||||
|
Delete a single user from the database
|
||||||
|
:param user_id: the id of the user to delete
|
||||||
|
"""
|
||||||
|
self._user.delete(user_id)
|
||||||
|
|
||||||
|
def delete_city(self, city_id):
|
||||||
|
"""
|
||||||
|
Deletes a single city from the database
|
||||||
|
:param city_id: the id of the city to get
|
||||||
|
"""
|
||||||
|
self._city.delete(city_id)
|
||||||
|
|
||||||
|
def delete_results_by_name(self, name, city_id=None, city_object_id=None):
|
||||||
|
"""
|
||||||
|
Deletes city object simulation results from the database
|
||||||
|
:param name: simulation name
|
||||||
|
:param city_id: if given, delete delete the results for the city with id city_id
|
||||||
|
:param city_object_id: if given, delete delete the results for the city object with id city_object_id
|
||||||
|
"""
|
||||||
|
self._simulation_results.delete(name, city_id=city_id, city_object_id=city_object_id)
|
||||||
|
|
||||||
|
def delete_application(self, application_uuid):
|
||||||
|
"""
|
||||||
|
Deletes a single application from the database
|
||||||
|
:param application_uuid: the id of the application to get
|
||||||
|
"""
|
||||||
|
self._application.delete(application_uuid)
|
70
cerc_persistence/db_setup.py
Normal file
70
cerc_persistence/db_setup.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
Database setup
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from cerc_persistence.repository import Repository
|
||||||
|
from cerc_persistence.models import Application
|
||||||
|
from cerc_persistence.models import City
|
||||||
|
from cerc_persistence.models import CityObject
|
||||||
|
from cerc_persistence.models import User
|
||||||
|
from cerc_persistence.models import UserRoles
|
||||||
|
from cerc_persistence.models import SimulationResults
|
||||||
|
from cerc_persistence.repositories.user import User as UserRepository
|
||||||
|
from cerc_persistence.repositories.application import Application as ApplicationRepository
|
||||||
|
|
||||||
|
|
||||||
|
class DBSetup:
|
||||||
|
"""
|
||||||
|
Creates a Persistence database structure
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_name, app_env, dotenv_path, admin_password, application_uuid):
|
||||||
|
"""
|
||||||
|
Creates database tables a default admin user and a default admin app with the given password and uuid
|
||||||
|
:param db_name: database name
|
||||||
|
:param app_env: application environment type [TEST|PROD]
|
||||||
|
:param dotenv_path: .env file path
|
||||||
|
:param admin_password: administrator password for the application uuid
|
||||||
|
:application_uuid: application uuid
|
||||||
|
"""
|
||||||
|
repository = Repository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
|
||||||
|
# Create the tables using the models
|
||||||
|
Application.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
User.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
City.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
CityObject.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
SimulationResults.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
|
||||||
|
self._user_repo = UserRepository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
self._application_repo = ApplicationRepository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
||||||
|
application_id = self._create_admin_app(self._application_repo, application_uuid)
|
||||||
|
self._create_admin_user(self._user_repo, admin_password, application_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_admin_app(application_repo, application_uuid):
|
||||||
|
name = 'AdminTool'
|
||||||
|
description = 'Admin tool to control city persistence and to test the API v1.4'
|
||||||
|
logging.info('Creating default admin tool application...')
|
||||||
|
application = application_repo.insert(name, description, application_uuid)
|
||||||
|
|
||||||
|
if isinstance(application, dict):
|
||||||
|
logging.info(application)
|
||||||
|
else:
|
||||||
|
msg = f'Created Admin tool with application_uuid: {application_uuid}'
|
||||||
|
logging.info(msg)
|
||||||
|
return application.id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_admin_user(user_repo, admin_password, application_id):
|
||||||
|
password = admin_password
|
||||||
|
logging.info('Creating default admin user...')
|
||||||
|
user = user_repo.insert('Administrator', password, UserRoles.Admin, application_id)
|
||||||
|
if isinstance(user, dict):
|
||||||
|
logging.info(user)
|
||||||
|
else:
|
||||||
|
logging.info('Created Admin user')
|
32
cerc_persistence/models/application.py
Normal file
32
cerc_persistence/models/application.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""
|
||||||
|
Model representation of an application
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Sequence
|
||||||
|
from sqlalchemy import DateTime
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
|
||||||
|
from cerc_persistence.configuration import Models
|
||||||
|
|
||||||
|
|
||||||
|
class Application(Models):
|
||||||
|
"""
|
||||||
|
A model representation of an application
|
||||||
|
"""
|
||||||
|
__tablename__ = 'application'
|
||||||
|
id = Column(Integer, Sequence('application_id_seq'), primary_key=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
description = Column(String, nullable=False)
|
||||||
|
application_uuid = Column(UUID(as_uuid=True), nullable=False)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
def __init__(self, name, description, application_uuid):
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.application_uuid = application_uuid
|
36
cerc_persistence/models/city.py
Normal file
36
cerc_persistence/models/city.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"""
|
||||||
|
Model representation of a City
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
|
||||||
|
from sqlalchemy import DateTime
|
||||||
|
|
||||||
|
from cerc_persistence.configuration import Models
|
||||||
|
|
||||||
|
|
||||||
|
class City(Models):
|
||||||
|
"""A model representation of a city
|
||||||
|
"""
|
||||||
|
__tablename__ = 'city'
|
||||||
|
id = Column(Integer, Sequence('city_id_seq'), primary_key=True)
|
||||||
|
pickle_path = Column(String, nullable=False)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
scenario = Column(String, nullable=False)
|
||||||
|
application_id = Column(Integer, ForeignKey('application.id', ondelete='CASCADE'), nullable=False)
|
||||||
|
user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE'), nullable=True)
|
||||||
|
hub_release = Column(String, nullable=False)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
def __init__(self, pickle_path, name, scenario, application_id, user_id, hub_release):
|
||||||
|
self.pickle_path = str(pickle_path)
|
||||||
|
self.name = name
|
||||||
|
self.scenario = scenario
|
||||||
|
self.application_id = application_id
|
||||||
|
self.user_id = user_id
|
||||||
|
self.hub_release = hub_release
|
81
cerc_persistence/models/city_object.py
Normal file
81
cerc_persistence/models/city_object.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"""
|
||||||
|
Model representation of a city object
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float
|
||||||
|
from sqlalchemy import DateTime
|
||||||
|
|
||||||
|
from hub.city_model_structure.building import Building
|
||||||
|
from cerc_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', ondelete='CASCADE'), nullable=False)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
aliases = 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)
|
||||||
|
roof_area = Column(Float, nullable=False)
|
||||||
|
total_pv_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, building: Building):
|
||||||
|
self.city_id = city_id
|
||||||
|
self.name = building.name
|
||||||
|
self.aliases = building.aliases
|
||||||
|
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
|
||||||
|
self.roof_area = sum(roof.solid_polygon.area for roof in building.roofs)
|
||||||
|
self.total_pv_area = sum(
|
||||||
|
roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor for roof in building.roofs)
|
||||||
|
storeys = building.storeys_above_ground
|
||||||
|
wall_area = 0
|
||||||
|
window_ratio = 0
|
||||||
|
try:
|
||||||
|
if storeys is None:
|
||||||
|
storeys = building.max_height / building.average_storey_height
|
||||||
|
for internal_zone in building.internal_zones:
|
||||||
|
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||||
|
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||||
|
window_ratio = thermal_boundary.window_ratio
|
||||||
|
break
|
||||||
|
except TypeError:
|
||||||
|
storeys = 0
|
||||||
|
logging.warning(
|
||||||
|
'building %s has no storey height so heating area, storeys and window ratio cannot be calculated',
|
||||||
|
self.name
|
||||||
|
)
|
||||||
|
self.total_heating_area = building.floor_area * storeys
|
||||||
|
|
||||||
|
for wall in building.walls:
|
||||||
|
wall_area += wall.solid_polygon.area
|
||||||
|
self.wall_area = wall_area
|
||||||
|
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
|
33
cerc_persistence/models/simulation_results.py
Normal file
33
cerc_persistence/models/simulation_results.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
Model representation of simulation results
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
|
||||||
|
from sqlalchemy import DateTime
|
||||||
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
|
from cerc_persistence.configuration import Models
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationResults(Models):
|
||||||
|
"""
|
||||||
|
A model representation of an application
|
||||||
|
"""
|
||||||
|
__tablename__ = 'simulation_results'
|
||||||
|
id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True)
|
||||||
|
city_id = Column(Integer, ForeignKey('city.id', ondelete='CASCADE'), nullable=True)
|
||||||
|
city_object_id = Column(Integer, ForeignKey('city_object.id', ondelete='CASCADE'), nullable=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
values = Column(JSONB, nullable=False)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
def __init__(self, name, values, city_id=None, city_object_id=None):
|
||||||
|
self.name = name
|
||||||
|
self.values = values
|
||||||
|
self.city_id = city_id
|
||||||
|
self.city_object_id = city_object_id
|
42
cerc_persistence/models/user.py
Normal file
42
cerc_persistence/models/user.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
Model representation of a User
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import enum
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, Sequence
|
||||||
|
from sqlalchemy import DateTime, Enum
|
||||||
|
|
||||||
|
from cerc_persistence.configuration import Models
|
||||||
|
|
||||||
|
|
||||||
|
class UserRoles(enum.Enum):
|
||||||
|
"""
|
||||||
|
User roles enum
|
||||||
|
"""
|
||||||
|
Admin = 'Admin'
|
||||||
|
Hub_Reader = 'Hub_Reader'
|
||||||
|
|
||||||
|
|
||||||
|
class User(Models):
|
||||||
|
"""
|
||||||
|
A model representation of a city
|
||||||
|
"""
|
||||||
|
__tablename__ = 'user'
|
||||||
|
id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
password = Column(String, nullable=False)
|
||||||
|
role = Column(Enum(UserRoles), nullable=False, default=UserRoles.Hub_Reader)
|
||||||
|
application_id = Column(Integer, nullable=False)
|
||||||
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
||||||
|
def __init__(self, name, password, role, application_id):
|
||||||
|
self.name = name
|
||||||
|
self.password = password
|
||||||
|
self.role = role
|
||||||
|
self.application_id = application_id
|
111
cerc_persistence/repositories/application.py
Normal file
111
cerc_persistence/repositories/application.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
Application repository with database CRUD operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
from cerc_persistence.repository import Repository
|
||||||
|
from cerc_persistence.models import Application as Model
|
||||||
|
|
||||||
|
|
||||||
|
class Application(Repository):
|
||||||
|
"""
|
||||||
|
Application repository
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
||||||
|
super().__init__(db_name, dotenv_path, app_env)
|
||||||
|
|
||||||
|
def __new__(cls, db_name, dotenv_path, app_env):
|
||||||
|
"""
|
||||||
|
Implemented for a singleton pattern
|
||||||
|
"""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(Application, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def insert(self, name: str, description: str, application_uuid: str):
|
||||||
|
"""
|
||||||
|
Inserts a new application
|
||||||
|
:param name: Application name
|
||||||
|
:param description: Application description
|
||||||
|
:param application_uuid: Unique identifier for the application
|
||||||
|
:return: Identity id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
application = self.get_by_uuid(application_uuid)
|
||||||
|
if application is not None:
|
||||||
|
raise SQLAlchemyError('application already exists')
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
application = Model(name=name, description=description, application_uuid=application_uuid)
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.add(application)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(application)
|
||||||
|
return application.id
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('An error occurred while creating application %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def update(self, application_uuid: str, name: str, description: str):
|
||||||
|
"""
|
||||||
|
Updates an application
|
||||||
|
:param application_uuid: the application uuid of the application to be updated
|
||||||
|
:param name: the application name
|
||||||
|
:param description: the application description
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.query(Model).filter(
|
||||||
|
Model.application_uuid == application_uuid
|
||||||
|
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while updating application %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def delete(self, application_uuid: str):
|
||||||
|
"""
|
||||||
|
Deletes an application with the application_uuid
|
||||||
|
:param application_uuid: The application uuid
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.query(Model).filter(Model.application_uuid == application_uuid).delete()
|
||||||
|
session.flush()
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while deleting application %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def get_by_uuid(self, application_uuid: str) -> Model:
|
||||||
|
"""
|
||||||
|
Fetch Application based on the application uuid
|
||||||
|
:param application_uuid: the application uuid
|
||||||
|
:return: Application with the provided application_uuid
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
result_set = session.execute(select(Model).where(
|
||||||
|
Model.application_uuid == application_uuid)
|
||||||
|
).first()
|
||||||
|
return result_set[0]
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching application by application_uuid %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
except TypeError as err:
|
||||||
|
logging.error('Error while fetching application, empty result %s', err)
|
||||||
|
raise TypeError from err
|
138
cerc_persistence/repositories/city.py
Normal file
138
cerc_persistence/repositories/city.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
"""
|
||||||
|
City repository with database CRUD operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from hub.city_model_structure.city import City as CityHub
|
||||||
|
from cerc_persistence.repository import Repository
|
||||||
|
from cerc_persistence.models import City as Model
|
||||||
|
from cerc_persistence.models import CityObject
|
||||||
|
from hub.version import __version__
|
||||||
|
|
||||||
|
|
||||||
|
class City(Repository):
|
||||||
|
"""
|
||||||
|
City repository
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
||||||
|
super().__init__(db_name, dotenv_path, app_env)
|
||||||
|
|
||||||
|
def __new__(cls, db_name, dotenv_path, app_env):
|
||||||
|
"""
|
||||||
|
Implemented for a singleton pattern
|
||||||
|
"""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(City, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def insert(self, city: CityHub, pickle_path, scenario, application_id, user_id: int):
|
||||||
|
"""
|
||||||
|
Inserts a city
|
||||||
|
:param city: The complete city instance
|
||||||
|
:param pickle_path: Path to the pickle
|
||||||
|
:param scenario: Simulation scenario name
|
||||||
|
:param application_id: Application id owning the instance
|
||||||
|
:param user_id: User id owning the instance
|
||||||
|
:return: Identity id
|
||||||
|
"""
|
||||||
|
city.save_compressed(pickle_path)
|
||||||
|
try:
|
||||||
|
db_city = Model(
|
||||||
|
pickle_path,
|
||||||
|
city.name,
|
||||||
|
scenario,
|
||||||
|
application_id,
|
||||||
|
user_id,
|
||||||
|
__version__)
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.add(db_city)
|
||||||
|
session.flush()
|
||||||
|
session.commit()
|
||||||
|
for building in city.buildings:
|
||||||
|
db_city_object = CityObject(db_city.id,
|
||||||
|
building)
|
||||||
|
session.add(db_city_object)
|
||||||
|
session.flush()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(db_city)
|
||||||
|
return db_city.id
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('An error occurred while creating a city %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def update(self, city_id: int, city: CityHub):
|
||||||
|
"""
|
||||||
|
Updates a city name (other updates makes no sense)
|
||||||
|
:param city_id: the id of the city to be updated
|
||||||
|
:param city: the city object
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while updating city %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def delete(self, city_id: int):
|
||||||
|
"""
|
||||||
|
Deletes a City with the id
|
||||||
|
:param city_id: the city id
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.query(CityObject).filter(CityObject.city_id == city_id).delete()
|
||||||
|
session.query(Model).filter(Model.id == city_id).delete()
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def get_by_user_id_application_id_and_scenario(self, user_id, application_id, scenario) -> [Model]:
|
||||||
|
"""
|
||||||
|
Fetch city based on the user who created it
|
||||||
|
:param user_id: the user id
|
||||||
|
:param application_id: the application id
|
||||||
|
:param scenario: simulation scenario name
|
||||||
|
:return: [ModelCity]
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
result_set = session.execute(select(Model).where(Model.user_id == user_id,
|
||||||
|
Model.application_id == application_id,
|
||||||
|
Model.scenario == scenario
|
||||||
|
)).all()
|
||||||
|
return result_set
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city by name %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def get_by_user_id_and_application_id(self, user_id, application_id) -> [Model]:
|
||||||
|
"""
|
||||||
|
Fetch city based on the user who created it
|
||||||
|
:param user_id: the user id
|
||||||
|
:param application_id: the application id
|
||||||
|
:return: ModelCity
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
result_set = session.execute(
|
||||||
|
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
|
||||||
|
)
|
||||||
|
return [r[0] for r in result_set]
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city by name %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
133
cerc_persistence/repositories/city_object.py
Normal file
133
cerc_persistence/repositories/city_object.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
"""
|
||||||
|
City Object repository with database CRUD operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import select, or_
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from hub.city_model_structure.building import Building
|
||||||
|
from cerc_persistence.repository import Repository
|
||||||
|
from cerc_persistence.models import CityObject as Model
|
||||||
|
|
||||||
|
|
||||||
|
class CityObject(Repository):
|
||||||
|
"""
|
||||||
|
City object repository
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
||||||
|
super().__init__(db_name, dotenv_path, app_env)
|
||||||
|
|
||||||
|
def __new__(cls, db_name, dotenv_path, app_env):
|
||||||
|
"""
|
||||||
|
Implemented for a singleton pattern
|
||||||
|
"""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(CityObject, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def insert(self, city_id: int, building: Building):
|
||||||
|
"""
|
||||||
|
Inserts a new city object
|
||||||
|
:param city_id: city id for the city owning this city object
|
||||||
|
:param building: the city object (only building for now) to be inserted
|
||||||
|
return Identity id
|
||||||
|
"""
|
||||||
|
city_object = self.get_by_name_or_alias_and_city(building.name, city_id)
|
||||||
|
if city_object is not None:
|
||||||
|
raise SQLAlchemyError(f'A city_object named {building.name} already exists in that city')
|
||||||
|
try:
|
||||||
|
city_object = Model(city_id=city_id,
|
||||||
|
building=building)
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.add(city_object)
|
||||||
|
session.flush()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(city_object)
|
||||||
|
return city_object.id
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('An error occurred while creating city_object %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def update(self, city_id: int, building: Building):
|
||||||
|
"""
|
||||||
|
Updates an application
|
||||||
|
:param city_id: the city id of the city owning the city object
|
||||||
|
:param building: the city object
|
||||||
|
:return: 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()
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
|
||||||
|
{'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,
|
||||||
|
'updated': datetime.datetime.utcnow()})
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while updating city object %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def delete(self, city_id: int, name: str):
|
||||||
|
"""
|
||||||
|
Deletes an application with the application_uuid
|
||||||
|
:param city_id: The id for the city owning the city object
|
||||||
|
:param name: The city object name
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while deleting application %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def get_by_name_or_alias_and_city(self, name, city_id) -> Model:
|
||||||
|
"""
|
||||||
|
Fetch a city object based on name and city id
|
||||||
|
:param name: city object name
|
||||||
|
:param city_id: a city identifier
|
||||||
|
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# search by name first
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
|
||||||
|
if city_object is not None:
|
||||||
|
return city_object[0]
|
||||||
|
# name not found, so search by alias instead
|
||||||
|
city_objects = session.execute(
|
||||||
|
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
|
||||||
|
).all()
|
||||||
|
for city_object in city_objects:
|
||||||
|
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
||||||
|
for alias in aliases:
|
||||||
|
if alias == name:
|
||||||
|
# force the name as the alias
|
||||||
|
city_object[0].name = name
|
||||||
|
return city_object[0]
|
||||||
|
return None
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city object by name and city: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
except IndexError as err:
|
||||||
|
logging.error('Error while fetching city object by name and city, empty result %s', err)
|
||||||
|
raise IndexError from err
|
170
cerc_persistence/repositories/simulation_results.py
Normal file
170
cerc_persistence/repositories/simulation_results.py
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
"""
|
||||||
|
Simulation results repository with database CRUD operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import or_
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from cerc_persistence.repository import Repository
|
||||||
|
from cerc_persistence.models import City
|
||||||
|
from cerc_persistence.models import CityObject
|
||||||
|
from cerc_persistence.models import SimulationResults as Model
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationResults(Repository):
|
||||||
|
"""
|
||||||
|
Simulation results repository
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
||||||
|
super().__init__(db_name, dotenv_path, app_env)
|
||||||
|
|
||||||
|
def __new__(cls, db_name, dotenv_path, app_env):
|
||||||
|
"""
|
||||||
|
Implemented for a singleton pattern
|
||||||
|
"""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(SimulationResults, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def insert(self, name: str, values: str, city_id=None, city_object_id=None):
|
||||||
|
"""
|
||||||
|
Inserts simulations results linked either with a city as a whole or with a city object
|
||||||
|
:param name: results name
|
||||||
|
:param values: the simulation results in json format
|
||||||
|
:param city_id: optional city id
|
||||||
|
:param city_object_id: optional city object id
|
||||||
|
:return: Identity id
|
||||||
|
"""
|
||||||
|
if city_id is not None:
|
||||||
|
_ = self._get_city(city_id)
|
||||||
|
else:
|
||||||
|
_ = self._get_city_object(city_object_id)
|
||||||
|
try:
|
||||||
|
simulation_result = Model(name=name,
|
||||||
|
values=values,
|
||||||
|
city_id=city_id,
|
||||||
|
city_object_id=city_object_id)
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.add(simulation_result)
|
||||||
|
session.flush()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(simulation_result)
|
||||||
|
return simulation_result.id
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('An error occurred while creating city_object %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def update(self, name: str, values: str, city_id=None, city_object_id=None):
|
||||||
|
"""
|
||||||
|
Updates simulation results for a city or a city object
|
||||||
|
:param name: The simulation results tool and workflow name
|
||||||
|
:param values: the simulation results in json format
|
||||||
|
:param city_id: optional city id
|
||||||
|
:param city_object_id: optional city object id
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
if city_id is not None:
|
||||||
|
session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
|
||||||
|
{
|
||||||
|
'values': values,
|
||||||
|
'updated': datetime.datetime.utcnow()
|
||||||
|
})
|
||||||
|
session.commit()
|
||||||
|
elif city_object_id is not None:
|
||||||
|
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
|
||||||
|
{
|
||||||
|
'values': values,
|
||||||
|
'updated': datetime.datetime.utcnow()
|
||||||
|
})
|
||||||
|
session.commit()
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Missing either city_id or city_object_id')
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while updating city object %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def delete(self, name: str, city_id=None, city_object_id=None):
|
||||||
|
"""
|
||||||
|
Deletes an application with the application_uuid
|
||||||
|
:param name: The simulation results tool and workflow name
|
||||||
|
:param city_id: The id for the city owning the simulation results
|
||||||
|
:param city_object_id: the id for the city_object owning these simulation results
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
if city_id is not None:
|
||||||
|
session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
|
||||||
|
session.commit()
|
||||||
|
elif city_object_id is not None:
|
||||||
|
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
|
||||||
|
session.commit()
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Missing either city_id or city_object_id')
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while deleting application: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def _get_city(self, city_id) -> City:
|
||||||
|
"""
|
||||||
|
Fetch a city object based city id
|
||||||
|
:param city_id: a city identifier
|
||||||
|
:return: [City] with the provided city_id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
return session.execute(select(City).where(City.id == city_id)).first()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city by city_id: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def _get_city_object(self, city_object_id) -> [CityObject]:
|
||||||
|
"""
|
||||||
|
Fetch a city object based city id
|
||||||
|
:param city_object_id: a city object identifier
|
||||||
|
:return: [CityObject] with the provided city_object_id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
return session.execute(select(CityObject).where(CityObject.id == city_object_id)).first()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city by city_id: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def get_simulation_results_by_city_id_city_object_id_and_names(self, city_id, city_object_id,
|
||||||
|
result_names=None) -> [Model]:
|
||||||
|
"""
|
||||||
|
Fetch the simulation results based in the city_id or city_object_id with the given names or all
|
||||||
|
:param city_id: the city id
|
||||||
|
:param city_object_id: the city object id
|
||||||
|
:param result_names: if given filter the results
|
||||||
|
:return: [SimulationResult]
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
result_set = session.execute(select(Model).where(or_(
|
||||||
|
Model.city_id == city_id,
|
||||||
|
Model.city_object_id == city_object_id
|
||||||
|
)))
|
||||||
|
results = [r[0] for r in result_set]
|
||||||
|
if not result_names:
|
||||||
|
return results
|
||||||
|
filtered_results = []
|
||||||
|
for result in results:
|
||||||
|
if result.name in result_names:
|
||||||
|
filtered_results.append(result)
|
||||||
|
return filtered_results
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city by city_id: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
159
cerc_persistence/repositories/user.py
Normal file
159
cerc_persistence/repositories/user.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
"""
|
||||||
|
User repository with database CRUD operations
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from hub.helpers.auth import Auth
|
||||||
|
from cerc_persistence.repository import Repository
|
||||||
|
from cerc_persistence.models import User as Model, Application as ApplicationModel, UserRoles
|
||||||
|
|
||||||
|
|
||||||
|
class User(Repository):
|
||||||
|
"""
|
||||||
|
User class
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
||||||
|
super().__init__(db_name, dotenv_path, app_env)
|
||||||
|
|
||||||
|
def __new__(cls, db_name, dotenv_path, app_env):
|
||||||
|
"""
|
||||||
|
Implemented for a singleton pattern
|
||||||
|
"""
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(User, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def insert(self, name: str, password: str, role: UserRoles, application_id: int):
|
||||||
|
"""
|
||||||
|
Inserts a new user
|
||||||
|
:param name: username
|
||||||
|
:param password: user password
|
||||||
|
:param role: user rol [Admin or Hub_Reader]
|
||||||
|
:param application_id: user application id
|
||||||
|
:return: Identity id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = self.get_by_name_and_application(name, application_id)
|
||||||
|
if user is not None:
|
||||||
|
raise SQLAlchemyError(f'A user named {user.name} already exists for that application')
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id)
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.add(user)
|
||||||
|
session.flush()
|
||||||
|
session.commit()
|
||||||
|
session.refresh(user)
|
||||||
|
return user.id
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('An error occurred while creating user %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def update(self, user_id: int, name: str, password: str, role: UserRoles):
|
||||||
|
"""
|
||||||
|
Updates a user
|
||||||
|
:param user_id: the id of the user to be updated
|
||||||
|
:param name: the name of the user
|
||||||
|
:param password: the password of the user
|
||||||
|
:param role: the role of the user
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.query(Model).filter(Model.id == user_id).update({
|
||||||
|
'name': name,
|
||||||
|
'password': Auth.hash_password(password),
|
||||||
|
'role': role,
|
||||||
|
'updated': datetime.datetime.utcnow()
|
||||||
|
})
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while updating user: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def delete(self, user_id: int):
|
||||||
|
"""
|
||||||
|
Deletes a user with the id
|
||||||
|
:param user_id: the user id
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
session.query(Model).filter(Model.id == user_id).delete()
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching user: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def get_by_name_and_application(self, name: str, application_id: int) -> Model:
|
||||||
|
"""
|
||||||
|
Fetch user based on the email address
|
||||||
|
:param name: Username
|
||||||
|
:param application_id: User application name
|
||||||
|
:return: User matching the search criteria or None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
user = session.execute(
|
||||||
|
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||||
|
).first()
|
||||||
|
session.commit()
|
||||||
|
return user[0]
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching user by name and application: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
except TypeError as err:
|
||||||
|
logging.error('Error while fetching user, empty result %s', err)
|
||||||
|
raise TypeError from err
|
||||||
|
|
||||||
|
def get_by_name_application_id_and_password(self, name: str, password: str, application_id: int) -> Model:
|
||||||
|
"""
|
||||||
|
Fetch user based on the name, password and application id
|
||||||
|
:param name: Username
|
||||||
|
:param password: User password
|
||||||
|
:param application_id: Application id
|
||||||
|
:return: User
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
user = session.execute(
|
||||||
|
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||||
|
).first()
|
||||||
|
if user:
|
||||||
|
if Auth.check_password(password, user[0].password):
|
||||||
|
return user[0]
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching user by name: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
raise ValueError('Unauthorized')
|
||||||
|
|
||||||
|
def get_by_name_application_uuid_and_password(self, name: str, password: str, application_uuid: str) -> Model:
|
||||||
|
"""
|
||||||
|
Fetch user based on the email and password
|
||||||
|
:param name: Username
|
||||||
|
:param password: User password
|
||||||
|
:param application_uuid: Application uuid
|
||||||
|
:return: User
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
application = session.execute(
|
||||||
|
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
|
||||||
|
).first()
|
||||||
|
return self.get_by_name_application_id_and_password(name, password, application[0].id)
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching user by name: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
except ValueError as err:
|
||||||
|
raise ValueError from err
|
22
cerc_persistence/repository.py
Normal file
22
cerc_persistence/repository.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Base repository class to establish db connection
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from cerc_persistence.configuration import Configuration
|
||||||
|
|
||||||
|
|
||||||
|
class Repository:
|
||||||
|
"""
|
||||||
|
Base repository class to establish db connection
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_name, dotenv_path: str, app_env='TEST'):
|
||||||
|
try:
|
||||||
|
self.configuration = Configuration(db_name, dotenv_path, app_env)
|
||||||
|
self.engine = create_engine(self.configuration.connection_string)
|
||||||
|
except ValueError as err:
|
||||||
|
logging.error('Missing value for credentials: %s', err)
|
4
cerc_persistence/version.py
Normal file
4
cerc_persistence/version.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
"""
|
||||||
|
CERC Persistence version number
|
||||||
|
"""
|
||||||
|
__version__ = '0.1.0'
|
@ -1,67 +0,0 @@
|
|||||||
"""
|
|
||||||
Persistence (Postgresql) configuration
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
|
|
||||||
Models = declarative_base()
|
|
||||||
|
|
||||||
|
|
||||||
class Configuration:
|
|
||||||
"""
|
|
||||||
Configuration class to hold common persistence configuration
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, db_name: str, dotenv_path: str, app_env='TEST'):
|
|
||||||
"""
|
|
||||||
:param db_name: database name
|
|
||||||
:param app_env: application environment, test or production
|
|
||||||
:param dotenv_path: the absolute path to dotenv file
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# load environmental variables
|
|
||||||
if not Path(dotenv_path).exists():
|
|
||||||
error_message = f'dotenv file doesn\'t exists at {dotenv_path}'
|
|
||||||
logging.error(error_message)
|
|
||||||
raise FileNotFoundError(error_message)
|
|
||||||
load_dotenv(dotenv_path=dotenv_path)
|
|
||||||
|
|
||||||
self._db_name = db_name
|
|
||||||
self._db_host = os.getenv(f'{app_env}_DB_HOST')
|
|
||||||
self._db_user = os.getenv(f'{app_env}_DB_USER')
|
|
||||||
self._db_pass = os.getenv(f'{app_env}_DB_PASSWORD')
|
|
||||||
self._db_port = os.getenv(f'{app_env}_DB_PORT')
|
|
||||||
self.hub_token = os.getenv('HUB_TOKEN')
|
|
||||||
except KeyError as err:
|
|
||||||
logging.error('Error with credentials: %s', err)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def connection_string(self):
|
|
||||||
"""
|
|
||||||
Returns a connection string postgresql
|
|
||||||
:return: connection string
|
|
||||||
"""
|
|
||||||
if self._db_pass:
|
|
||||||
return f'postgresql://{self._db_user}:{self._db_pass}@{self._db_host}:{self._db_port}/{self._db_name}'
|
|
||||||
return f'postgresql://{self._db_user}@{self._db_host}:{self._db_port}/{self._db_name}'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def db_user(self):
|
|
||||||
"""
|
|
||||||
retrieve the configured username
|
|
||||||
"""
|
|
||||||
return self._db_user
|
|
||||||
|
|
||||||
@property
|
|
||||||
def db_name(self):
|
|
||||||
"""
|
|
||||||
retrieve the configured database name
|
|
||||||
"""
|
|
||||||
return self._db_name
|
|
249
db_control.py
249
db_control.py
@ -1,249 +0,0 @@
|
|||||||
"""
|
|
||||||
DBFactory performs read related operations
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project CoderPeter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
|
|
||||||
from hub.persistence.repositories.application import Application
|
|
||||||
from hub.persistence.repositories.city import City
|
|
||||||
from hub.persistence.repositories.city_object import CityObject
|
|
||||||
from hub.persistence.repositories.simulation_results import SimulationResults
|
|
||||||
from hub.persistence.repositories.user import User
|
|
||||||
from hub.persistence.repositories.user import UserRoles
|
|
||||||
|
|
||||||
|
|
||||||
class DBControl:
|
|
||||||
"""
|
|
||||||
DBFactory class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, db_name, app_env, dotenv_path):
|
|
||||||
self._city = City(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env)
|
|
||||||
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)
|
|
||||||
self._simulation_results = SimulationResults(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env)
|
|
||||||
|
|
||||||
def application_info(self, application_uuid) -> Application:
|
|
||||||
"""
|
|
||||||
Retrieve the application info for the given uuid from the database
|
|
||||||
:param application_uuid: the uuid for the application
|
|
||||||
:return: Application
|
|
||||||
"""
|
|
||||||
return self._application.get_by_uuid(application_uuid)
|
|
||||||
|
|
||||||
def user_info(self, name, password, application_id) -> User:
|
|
||||||
"""
|
|
||||||
Retrieve the user info for the given name and password and application_id from the database
|
|
||||||
:param name: the username
|
|
||||||
:param password: the user password
|
|
||||||
:param application_id: the application id
|
|
||||||
:return: User
|
|
||||||
"""
|
|
||||||
return self._user.get_by_name_application_id_and_password(name, password, application_id)
|
|
||||||
|
|
||||||
def user_login(self, name, password, application_uuid) -> User:
|
|
||||||
"""
|
|
||||||
Retrieve the user info from the database
|
|
||||||
:param name: the username
|
|
||||||
:param password: the user password
|
|
||||||
:param application_uuid: the application uuid
|
|
||||||
:return: User
|
|
||||||
"""
|
|
||||||
return self._user.get_by_name_application_uuid_and_password(name, password, application_uuid)
|
|
||||||
|
|
||||||
def cities_by_user_and_application(self, user_id, application_id) -> [City]:
|
|
||||||
"""
|
|
||||||
Retrieve the cities belonging to the user and the application from the database
|
|
||||||
:param user_id: User id
|
|
||||||
:param application_id: Application id
|
|
||||||
:return: [City]
|
|
||||||
"""
|
|
||||||
return self._city.get_by_user_id_and_application_id(user_id, application_id)
|
|
||||||
|
|
||||||
def building(self, name, user_id, application_id, scenario) -> CityObject:
|
|
||||||
"""
|
|
||||||
Retrieve the building from the database
|
|
||||||
:param name: Building name
|
|
||||||
:param user_id: User id
|
|
||||||
:param application_id: Application id
|
|
||||||
:param scenario: Scenario
|
|
||||||
:
|
|
||||||
"""
|
|
||||||
cities = self._city.get_by_user_id_application_id_and_scenario(user_id, application_id, scenario)
|
|
||||||
for city in cities:
|
|
||||||
result = self.building_info(name, city[0].id)
|
|
||||||
if result is not None:
|
|
||||||
return result
|
|
||||||
return None
|
|
||||||
|
|
||||||
def building_info(self, name, city_id) -> CityObject:
|
|
||||||
"""
|
|
||||||
Retrieve the building info from the database
|
|
||||||
:param name: Building name
|
|
||||||
:param city_id: City ID
|
|
||||||
:return: CityObject
|
|
||||||
"""
|
|
||||||
return self._city_object.get_by_name_or_alias_and_city(name, city_id)
|
|
||||||
|
|
||||||
def buildings_info(self, request_values, city_id) -> [CityObject]:
|
|
||||||
"""
|
|
||||||
Retrieve the buildings info from the database
|
|
||||||
:param request_values: Building names
|
|
||||||
:param city_id: City ID
|
|
||||||
:return: [CityObject]
|
|
||||||
"""
|
|
||||||
buildings = []
|
|
||||||
for name in request_values['names']:
|
|
||||||
buildings.append(self.building_info(name, city_id))
|
|
||||||
return buildings
|
|
||||||
|
|
||||||
def results(self, user_id, application_id, request_values, result_names=None) -> Dict:
|
|
||||||
"""
|
|
||||||
Retrieve the simulation results for the given cities from the database
|
|
||||||
:param user_id: the user id owning the results
|
|
||||||
:param application_id: the application id owning the results
|
|
||||||
:param request_values: dictionary containing the scenario and building names to grab the results
|
|
||||||
:param result_names: if given, filter the results to the selected names
|
|
||||||
"""
|
|
||||||
if result_names is None:
|
|
||||||
result_names = []
|
|
||||||
results = {}
|
|
||||||
for scenario in request_values['scenarios']:
|
|
||||||
for scenario_name in scenario.keys():
|
|
||||||
result_sets = self._city.get_by_user_id_application_id_and_scenario(
|
|
||||||
user_id,
|
|
||||||
application_id,
|
|
||||||
scenario_name
|
|
||||||
)
|
|
||||||
if result_sets is None:
|
|
||||||
continue
|
|
||||||
for result_set in result_sets:
|
|
||||||
city_id = result_set[0].id
|
|
||||||
|
|
||||||
results[scenario_name] = []
|
|
||||||
for building_name in scenario[scenario_name]:
|
|
||||||
_building = self._city_object.get_by_name_or_alias_and_city(building_name, city_id)
|
|
||||||
if _building is None:
|
|
||||||
continue
|
|
||||||
city_object_id = _building.id
|
|
||||||
_ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names(
|
|
||||||
city_id,
|
|
||||||
city_object_id,
|
|
||||||
result_names)
|
|
||||||
|
|
||||||
for value in _:
|
|
||||||
values = json.loads(value.values)
|
|
||||||
values["building"] = building_name
|
|
||||||
results[scenario_name].append(values)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int):
|
|
||||||
"""
|
|
||||||
Creates a city into the database
|
|
||||||
:param city: City to be stored
|
|
||||||
:param pickle_path: Path to save the pickle file
|
|
||||||
:param scenario: Simulation scenario name
|
|
||||||
:param application_id: Application id owning this city
|
|
||||||
:param user_id: User who create the city
|
|
||||||
return identity_id
|
|
||||||
"""
|
|
||||||
return self._city.insert(city, pickle_path, scenario, application_id, user_id)
|
|
||||||
|
|
||||||
def update_city(self, city_id, city):
|
|
||||||
"""
|
|
||||||
Update an existing city in the database
|
|
||||||
:param city_id: the id of the city to update
|
|
||||||
:param city: the updated city object
|
|
||||||
"""
|
|
||||||
return self._city.update(city_id, city)
|
|
||||||
|
|
||||||
def persist_application(self, name: str, description: str, application_uuid: str):
|
|
||||||
"""
|
|
||||||
Creates information for an application in the database
|
|
||||||
:param name: name of application
|
|
||||||
:param description: the description of the application
|
|
||||||
:param application_uuid: the uuid of the application to be created
|
|
||||||
"""
|
|
||||||
return self._application.insert(name, description, application_uuid)
|
|
||||||
|
|
||||||
def update_application(self, name: str, description: str, application_uuid: str):
|
|
||||||
"""
|
|
||||||
Update the application information stored in the database
|
|
||||||
:param name: name of application
|
|
||||||
:param description: the description of the application
|
|
||||||
:param application_uuid: the uuid of the application to be created
|
|
||||||
"""
|
|
||||||
return self._application.update(application_uuid, name, description)
|
|
||||||
|
|
||||||
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 to the database
|
|
||||||
: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
|
|
||||||
"""
|
|
||||||
return self._simulation_results.insert(name, values, city_id, city_object_id)
|
|
||||||
|
|
||||||
def create_user(self, name: str, application_id: int, password: str, role: UserRoles):
|
|
||||||
"""
|
|
||||||
Creates a new user in the database
|
|
||||||
:param name: the name of the user
|
|
||||||
:param application_id: the application id of the user
|
|
||||||
:param password: the password of the user
|
|
||||||
:param role: the role of the user
|
|
||||||
"""
|
|
||||||
return self._user.insert(name, password, role, application_id)
|
|
||||||
|
|
||||||
def update_user(self, user_id: int, name: str, password: str, role: UserRoles):
|
|
||||||
"""
|
|
||||||
Updates a user in the database
|
|
||||||
:param user_id: the id of the user
|
|
||||||
:param name: the name of the user
|
|
||||||
:param password: the password of the user
|
|
||||||
:param role: the role of the user
|
|
||||||
"""
|
|
||||||
return self._user.update(user_id, name, password, role)
|
|
||||||
|
|
||||||
def get_by_name_and_application(self, name: str, application: int):
|
|
||||||
"""
|
|
||||||
Retrieve a single user from the database
|
|
||||||
:param name: username
|
|
||||||
:param application: application accessing hub
|
|
||||||
"""
|
|
||||||
return self._user.get_by_name_and_application(name, application)
|
|
||||||
|
|
||||||
def delete_user(self, user_id):
|
|
||||||
"""
|
|
||||||
Delete a single user from the database
|
|
||||||
:param user_id: the id of the user to delete
|
|
||||||
"""
|
|
||||||
self._user.delete(user_id)
|
|
||||||
|
|
||||||
def delete_city(self, city_id):
|
|
||||||
"""
|
|
||||||
Deletes a single city from the database
|
|
||||||
:param city_id: the id of the city to get
|
|
||||||
"""
|
|
||||||
self._city.delete(city_id)
|
|
||||||
|
|
||||||
def delete_results_by_name(self, name, city_id=None, city_object_id=None):
|
|
||||||
"""
|
|
||||||
Deletes city object simulation results from the database
|
|
||||||
:param name: simulation name
|
|
||||||
:param city_id: if given, delete delete the results for the city with id city_id
|
|
||||||
:param city_object_id: if given, delete delete the results for the city object with id city_object_id
|
|
||||||
"""
|
|
||||||
self._simulation_results.delete(name, city_id=city_id, city_object_id=city_object_id)
|
|
||||||
|
|
||||||
def delete_application(self, application_uuid):
|
|
||||||
"""
|
|
||||||
Deletes a single application from the database
|
|
||||||
:param application_uuid: the id of the application to get
|
|
||||||
"""
|
|
||||||
self._application.delete(application_uuid)
|
|
70
db_setup.py
70
db_setup.py
@ -1,70 +0,0 @@
|
|||||||
"""
|
|
||||||
Database setup
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from hub.persistence.repository import Repository
|
|
||||||
from hub.persistence.models import Application
|
|
||||||
from hub.persistence.models import City
|
|
||||||
from hub.persistence.models import CityObject
|
|
||||||
from hub.persistence.models import User
|
|
||||||
from hub.persistence.models import UserRoles
|
|
||||||
from hub.persistence.models import SimulationResults
|
|
||||||
from hub.persistence.repositories.user import User as UserRepository
|
|
||||||
from hub.persistence.repositories.application import Application as ApplicationRepository
|
|
||||||
|
|
||||||
|
|
||||||
class DBSetup:
|
|
||||||
"""
|
|
||||||
Creates a Persistence database structure
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, db_name, app_env, dotenv_path, admin_password, application_uuid):
|
|
||||||
"""
|
|
||||||
Creates database tables a default admin user and a default admin app with the given password and uuid
|
|
||||||
:param db_name: database name
|
|
||||||
:param app_env: application environment type [TEST|PROD]
|
|
||||||
:param dotenv_path: .env file path
|
|
||||||
:param admin_password: administrator password for the application uuid
|
|
||||||
:application_uuid: application uuid
|
|
||||||
"""
|
|
||||||
repository = Repository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
|
||||||
|
|
||||||
# Create the tables using the models
|
|
||||||
Application.__table__.create(bind=repository.engine, checkfirst=True)
|
|
||||||
User.__table__.create(bind=repository.engine, checkfirst=True)
|
|
||||||
City.__table__.create(bind=repository.engine, checkfirst=True)
|
|
||||||
CityObject.__table__.create(bind=repository.engine, checkfirst=True)
|
|
||||||
SimulationResults.__table__.create(bind=repository.engine, checkfirst=True)
|
|
||||||
|
|
||||||
self._user_repo = UserRepository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
|
||||||
self._application_repo = ApplicationRepository(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
|
|
||||||
application_id = self._create_admin_app(self._application_repo, application_uuid)
|
|
||||||
self._create_admin_user(self._user_repo, admin_password, application_id)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_admin_app(application_repo, application_uuid):
|
|
||||||
name = 'AdminTool'
|
|
||||||
description = 'Admin tool to control city persistence and to test the API v1.4'
|
|
||||||
logging.info('Creating default admin tool application...')
|
|
||||||
application = application_repo.insert(name, description, application_uuid)
|
|
||||||
|
|
||||||
if isinstance(application, dict):
|
|
||||||
logging.info(application)
|
|
||||||
else:
|
|
||||||
msg = f'Created Admin tool with application_uuid: {application_uuid}'
|
|
||||||
logging.info(msg)
|
|
||||||
return application.id
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_admin_user(user_repo, admin_password, application_id):
|
|
||||||
password = admin_password
|
|
||||||
logging.info('Creating default admin user...')
|
|
||||||
user = user_repo.insert('Administrator', password, UserRoles.Admin, application_id)
|
|
||||||
if isinstance(user, dict):
|
|
||||||
logging.info(user)
|
|
||||||
else:
|
|
||||||
logging.info('Created Admin user')
|
|
@ -1,32 +0,0 @@
|
|||||||
"""
|
|
||||||
Model representation of an application
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence
|
|
||||||
from sqlalchemy import DateTime
|
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
|
||||||
|
|
||||||
from hub.persistence.configuration import Models
|
|
||||||
|
|
||||||
|
|
||||||
class Application(Models):
|
|
||||||
"""
|
|
||||||
A model representation of an application
|
|
||||||
"""
|
|
||||||
__tablename__ = 'application'
|
|
||||||
id = Column(Integer, Sequence('application_id_seq'), primary_key=True)
|
|
||||||
name = Column(String, nullable=False)
|
|
||||||
description = Column(String, nullable=False)
|
|
||||||
application_uuid = Column(UUID(as_uuid=True), nullable=False)
|
|
||||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
|
||||||
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
|
||||||
|
|
||||||
def __init__(self, name, description, application_uuid):
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
self.application_uuid = application_uuid
|
|
@ -1,36 +0,0 @@
|
|||||||
"""
|
|
||||||
Model representation of a City
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
|
|
||||||
from sqlalchemy import DateTime
|
|
||||||
|
|
||||||
from hub.persistence.configuration import Models
|
|
||||||
|
|
||||||
|
|
||||||
class City(Models):
|
|
||||||
"""A model representation of a city
|
|
||||||
"""
|
|
||||||
__tablename__ = 'city'
|
|
||||||
id = Column(Integer, Sequence('city_id_seq'), primary_key=True)
|
|
||||||
pickle_path = Column(String, nullable=False)
|
|
||||||
name = Column(String, nullable=False)
|
|
||||||
scenario = Column(String, nullable=False)
|
|
||||||
application_id = Column(Integer, ForeignKey('application.id', ondelete='CASCADE'), nullable=False)
|
|
||||||
user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE'), nullable=True)
|
|
||||||
hub_release = Column(String, nullable=False)
|
|
||||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
|
||||||
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
|
||||||
|
|
||||||
def __init__(self, pickle_path, name, scenario, application_id, user_id, hub_release):
|
|
||||||
self.pickle_path = str(pickle_path)
|
|
||||||
self.name = name
|
|
||||||
self.scenario = scenario
|
|
||||||
self.application_id = application_id
|
|
||||||
self.user_id = user_id
|
|
||||||
self.hub_release = hub_release
|
|
@ -1,80 +0,0 @@
|
|||||||
"""
|
|
||||||
Model representation of a city object
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float
|
|
||||||
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', ondelete='CASCADE'), nullable=False)
|
|
||||||
name = Column(String, nullable=False)
|
|
||||||
aliases = 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)
|
|
||||||
roof_area = Column(Float, nullable=False)
|
|
||||||
total_pv_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, building: Building):
|
|
||||||
self.city_id = city_id
|
|
||||||
self.name = building.name
|
|
||||||
self.aliases = building.aliases
|
|
||||||
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
|
|
||||||
self.roof_area = sum(roof.solid_polygon.area for roof in building.roofs)
|
|
||||||
self.total_pv_area = sum(roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor for roof in building.roofs)
|
|
||||||
storeys = building.storeys_above_ground
|
|
||||||
wall_area = 0
|
|
||||||
window_ratio = 0
|
|
||||||
try:
|
|
||||||
if storeys is None:
|
|
||||||
storeys = building.max_height / building.average_storey_height
|
|
||||||
for internal_zone in building.internal_zones:
|
|
||||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
window_ratio = thermal_boundary.window_ratio
|
|
||||||
break
|
|
||||||
except TypeError:
|
|
||||||
storeys = 0
|
|
||||||
logging.warning(
|
|
||||||
'building %s has no storey height so heating area, storeys and window ratio cannot be calculated',
|
|
||||||
self.name
|
|
||||||
)
|
|
||||||
self.total_heating_area = building.floor_area * storeys
|
|
||||||
|
|
||||||
for wall in building.walls:
|
|
||||||
wall_area += wall.solid_polygon.area
|
|
||||||
self.wall_area = wall_area
|
|
||||||
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
|
|
@ -1,33 +0,0 @@
|
|||||||
"""
|
|
||||||
Model representation of simulation results
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
|
|
||||||
from sqlalchemy import DateTime
|
|
||||||
from sqlalchemy.dialects.postgresql import JSONB
|
|
||||||
from hub.persistence.configuration import Models
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationResults(Models):
|
|
||||||
"""
|
|
||||||
A model representation of an application
|
|
||||||
"""
|
|
||||||
__tablename__ = 'simulation_results'
|
|
||||||
id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True)
|
|
||||||
city_id = Column(Integer, ForeignKey('city.id', ondelete='CASCADE'), nullable=True)
|
|
||||||
city_object_id = Column(Integer, ForeignKey('city_object.id', ondelete='CASCADE'), nullable=True)
|
|
||||||
name = Column(String, nullable=False)
|
|
||||||
values = Column(JSONB, nullable=False)
|
|
||||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
|
||||||
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
|
||||||
|
|
||||||
def __init__(self, name, values, city_id=None, city_object_id=None):
|
|
||||||
self.name = name
|
|
||||||
self.values = values
|
|
||||||
self.city_id = city_id
|
|
||||||
self.city_object_id = city_object_id
|
|
@ -1,42 +0,0 @@
|
|||||||
"""
|
|
||||||
Model representation of a User
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import enum
|
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence
|
|
||||||
from sqlalchemy import DateTime, Enum
|
|
||||||
|
|
||||||
from hub.persistence.configuration import Models
|
|
||||||
|
|
||||||
|
|
||||||
class UserRoles(enum.Enum):
|
|
||||||
"""
|
|
||||||
User roles enum
|
|
||||||
"""
|
|
||||||
Admin = 'Admin'
|
|
||||||
Hub_Reader = 'Hub_Reader'
|
|
||||||
|
|
||||||
|
|
||||||
class User(Models):
|
|
||||||
"""
|
|
||||||
A model representation of a city
|
|
||||||
"""
|
|
||||||
__tablename__ = 'user'
|
|
||||||
id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
|
|
||||||
name = Column(String, nullable=False)
|
|
||||||
password = Column(String, nullable=False)
|
|
||||||
role = Column(Enum(UserRoles), nullable=False, default=UserRoles.Hub_Reader)
|
|
||||||
application_id = Column(Integer, nullable=False)
|
|
||||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
|
||||||
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
|
||||||
|
|
||||||
def __init__(self, name, password, role, application_id):
|
|
||||||
self.name = name
|
|
||||||
self.password = password
|
|
||||||
self.role = role
|
|
||||||
self.application_id = application_id
|
|
8
pyproject.toml
Normal file
8
pyproject.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# pyproject.toml
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[options.packages.find_namespace]
|
||||||
|
where = "hub"
|
@ -1,111 +0,0 @@
|
|||||||
"""
|
|
||||||
Application repository with database CRUD operations
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
|
||||||
from sqlalchemy.orm.session import Session
|
|
||||||
|
|
||||||
from hub.persistence.repository import Repository
|
|
||||||
from hub.persistence.models import Application as Model
|
|
||||||
|
|
||||||
|
|
||||||
class Application(Repository):
|
|
||||||
"""
|
|
||||||
Application repository
|
|
||||||
"""
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
|
||||||
super().__init__(db_name, dotenv_path, app_env)
|
|
||||||
|
|
||||||
def __new__(cls, db_name, dotenv_path, app_env):
|
|
||||||
"""
|
|
||||||
Implemented for a singleton pattern
|
|
||||||
"""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super(Application, cls).__new__(cls)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def insert(self, name: str, description: str, application_uuid: str):
|
|
||||||
"""
|
|
||||||
Inserts a new application
|
|
||||||
:param name: Application name
|
|
||||||
:param description: Application description
|
|
||||||
:param application_uuid: Unique identifier for the application
|
|
||||||
:return: Identity id
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
application = self.get_by_uuid(application_uuid)
|
|
||||||
if application is not None:
|
|
||||||
raise SQLAlchemyError('application already exists')
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
application = Model(name=name, description=description, application_uuid=application_uuid)
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.add(application)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(application)
|
|
||||||
return application.id
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('An error occurred while creating application %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def update(self, application_uuid: str, name: str, description: str):
|
|
||||||
"""
|
|
||||||
Updates an application
|
|
||||||
:param application_uuid: the application uuid of the application to be updated
|
|
||||||
:param name: the application name
|
|
||||||
:param description: the application description
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.query(Model).filter(
|
|
||||||
Model.application_uuid == application_uuid
|
|
||||||
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while updating application %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def delete(self, application_uuid: str):
|
|
||||||
"""
|
|
||||||
Deletes an application with the application_uuid
|
|
||||||
:param application_uuid: The application uuid
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.query(Model).filter(Model.application_uuid == application_uuid).delete()
|
|
||||||
session.flush()
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while deleting application %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def get_by_uuid(self, application_uuid: str) -> Model:
|
|
||||||
"""
|
|
||||||
Fetch Application based on the application uuid
|
|
||||||
:param application_uuid: the application uuid
|
|
||||||
:return: Application with the provided application_uuid
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
result_set = session.execute(select(Model).where(
|
|
||||||
Model.application_uuid == application_uuid)
|
|
||||||
).first()
|
|
||||||
return result_set[0]
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching application by application_uuid %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
except TypeError as err:
|
|
||||||
logging.error('Error while fetching application, empty result %s', err)
|
|
||||||
raise TypeError from err
|
|
@ -1,139 +0,0 @@
|
|||||||
"""
|
|
||||||
City repository with database CRUD operations
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from hub.city_model_structure.city import City as CityHub
|
|
||||||
from hub.persistence.repository import Repository
|
|
||||||
from hub.persistence.models import City as Model
|
|
||||||
from hub.persistence.models import CityObject
|
|
||||||
from hub.version import __version__
|
|
||||||
|
|
||||||
|
|
||||||
class City(Repository):
|
|
||||||
"""
|
|
||||||
City repository
|
|
||||||
"""
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
|
||||||
super().__init__(db_name, dotenv_path, app_env)
|
|
||||||
|
|
||||||
def __new__(cls, db_name, dotenv_path, app_env):
|
|
||||||
"""
|
|
||||||
Implemented for a singleton pattern
|
|
||||||
"""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super(City, cls).__new__(cls)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def insert(self, city: CityHub, pickle_path, scenario, application_id, user_id: int):
|
|
||||||
"""
|
|
||||||
Inserts a city
|
|
||||||
:param city: The complete city instance
|
|
||||||
:param pickle_path: Path to the pickle
|
|
||||||
:param scenario: Simulation scenario name
|
|
||||||
:param application_id: Application id owning the instance
|
|
||||||
:param user_id: User id owning the instance
|
|
||||||
:return: Identity id
|
|
||||||
"""
|
|
||||||
city.save_compressed(pickle_path)
|
|
||||||
try:
|
|
||||||
db_city = Model(
|
|
||||||
pickle_path,
|
|
||||||
city.name,
|
|
||||||
scenario,
|
|
||||||
application_id,
|
|
||||||
user_id,
|
|
||||||
__version__)
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.add(db_city)
|
|
||||||
session.flush()
|
|
||||||
session.commit()
|
|
||||||
for building in city.buildings:
|
|
||||||
db_city_object = CityObject(db_city.id,
|
|
||||||
building)
|
|
||||||
session.add(db_city_object)
|
|
||||||
session.flush()
|
|
||||||
session.commit()
|
|
||||||
session.refresh(db_city)
|
|
||||||
return db_city.id
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('An error occurred while creating a city %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def update(self, city_id: int, city: CityHub):
|
|
||||||
"""
|
|
||||||
Updates a city name (other updates makes no sense)
|
|
||||||
:param city_id: the id of the city to be updated
|
|
||||||
:param city: the city object
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
now = datetime.datetime.utcnow()
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while updating city %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def delete(self, city_id: int):
|
|
||||||
"""
|
|
||||||
Deletes a City with the id
|
|
||||||
:param city_id: the city id
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.query(CityObject).filter(CityObject.city_id == city_id).delete()
|
|
||||||
session.query(Model).filter(Model.id == city_id).delete()
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching city %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def get_by_user_id_application_id_and_scenario(self, user_id, application_id, scenario) -> [Model]:
|
|
||||||
"""
|
|
||||||
Fetch city based on the user who created it
|
|
||||||
:param user_id: the user id
|
|
||||||
:param application_id: the application id
|
|
||||||
:param scenario: simulation scenario name
|
|
||||||
:return: [ModelCity]
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
result_set = session.execute(select(Model).where(Model.user_id == user_id,
|
|
||||||
Model.application_id == application_id,
|
|
||||||
Model.scenario == scenario
|
|
||||||
)).all()
|
|
||||||
return result_set
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching city by name %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def get_by_user_id_and_application_id(self, user_id, application_id) -> [Model]:
|
|
||||||
"""
|
|
||||||
Fetch city based on the user who created it
|
|
||||||
:param user_id: the user id
|
|
||||||
:param application_id: the application id
|
|
||||||
:return: ModelCity
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
result_set = session.execute(
|
|
||||||
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
|
|
||||||
)
|
|
||||||
return [r[0] for r in result_set]
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching city by name %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
|||||||
"""
|
|
||||||
City Object repository with database CRUD operations
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
|
||||||
"""
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sqlalchemy import select, or_
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from hub.city_model_structure.building import Building
|
|
||||||
from hub.persistence.repository import Repository
|
|
||||||
from hub.persistence.models import CityObject as Model
|
|
||||||
|
|
||||||
|
|
||||||
class CityObject(Repository):
|
|
||||||
"""
|
|
||||||
City object repository
|
|
||||||
"""
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
|
||||||
super().__init__(db_name, dotenv_path, app_env)
|
|
||||||
|
|
||||||
def __new__(cls, db_name, dotenv_path, app_env):
|
|
||||||
"""
|
|
||||||
Implemented for a singleton pattern
|
|
||||||
"""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super(CityObject, cls).__new__(cls)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def insert(self, city_id: int, building: Building):
|
|
||||||
"""
|
|
||||||
Inserts a new city object
|
|
||||||
:param city_id: city id for the city owning this city object
|
|
||||||
:param building: the city object (only building for now) to be inserted
|
|
||||||
return Identity id
|
|
||||||
"""
|
|
||||||
city_object = self.get_by_name_or_alias_and_city(building.name, city_id)
|
|
||||||
if city_object is not None:
|
|
||||||
raise SQLAlchemyError(f'A city_object named {building.name} already exists in that city')
|
|
||||||
try:
|
|
||||||
city_object = Model(city_id=city_id,
|
|
||||||
building=building)
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.add(city_object)
|
|
||||||
session.flush()
|
|
||||||
session.commit()
|
|
||||||
session.refresh(city_object)
|
|
||||||
return city_object.id
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('An error occurred while creating city_object %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def update(self, city_id: int, building: Building):
|
|
||||||
"""
|
|
||||||
Updates an application
|
|
||||||
:param city_id: the city id of the city owning the city object
|
|
||||||
:param building: the city object
|
|
||||||
:return: 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()
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
|
|
||||||
{'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,
|
|
||||||
'updated': datetime.datetime.utcnow()})
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while updating city object %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def delete(self, city_id: int, name: str):
|
|
||||||
"""
|
|
||||||
Deletes an application with the application_uuid
|
|
||||||
:param city_id: The id for the city owning the city object
|
|
||||||
:param name: The city object name
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while deleting application %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def get_by_name_or_alias_and_city(self, name, city_id) -> Model:
|
|
||||||
"""
|
|
||||||
Fetch a city object based on name and city id
|
|
||||||
:param name: city object name
|
|
||||||
:param city_id: a city identifier
|
|
||||||
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# search by name first
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
|
|
||||||
if city_object is not None:
|
|
||||||
return city_object[0]
|
|
||||||
# name not found, so search by alias instead
|
|
||||||
city_objects = session.execute(
|
|
||||||
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
|
|
||||||
).all()
|
|
||||||
for city_object in city_objects:
|
|
||||||
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
|
||||||
for alias in aliases:
|
|
||||||
if alias == name:
|
|
||||||
# force the name as the alias
|
|
||||||
city_object[0].name = name
|
|
||||||
return city_object[0]
|
|
||||||
return None
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching city object by name and city: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
except IndexError as err:
|
|
||||||
logging.error('Error while fetching city object by name and city, empty result %s', err)
|
|
||||||
raise IndexError from err
|
|
@ -1,169 +0,0 @@
|
|||||||
"""
|
|
||||||
Simulation results repository with database CRUD operations
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
|
||||||
"""
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sqlalchemy import or_
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from hub.persistence.repository import Repository
|
|
||||||
from hub.persistence.models import City
|
|
||||||
from hub.persistence.models import CityObject
|
|
||||||
from hub.persistence.models import SimulationResults as Model
|
|
||||||
|
|
||||||
|
|
||||||
class SimulationResults(Repository):
|
|
||||||
"""
|
|
||||||
Simulation results repository
|
|
||||||
"""
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
|
||||||
super().__init__(db_name, dotenv_path, app_env)
|
|
||||||
|
|
||||||
def __new__(cls, db_name, dotenv_path, app_env):
|
|
||||||
"""
|
|
||||||
Implemented for a singleton pattern
|
|
||||||
"""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super(SimulationResults, cls).__new__(cls)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def insert(self, name: str, values: str, city_id=None, city_object_id=None):
|
|
||||||
"""
|
|
||||||
Inserts simulations results linked either with a city as a whole or with a city object
|
|
||||||
:param name: results name
|
|
||||||
:param values: the simulation results in json format
|
|
||||||
:param city_id: optional city id
|
|
||||||
:param city_object_id: optional city object id
|
|
||||||
:return: Identity id
|
|
||||||
"""
|
|
||||||
if city_id is not None:
|
|
||||||
_ = self._get_city(city_id)
|
|
||||||
else:
|
|
||||||
_ = self._get_city_object(city_object_id)
|
|
||||||
try:
|
|
||||||
simulation_result = Model(name=name,
|
|
||||||
values=values,
|
|
||||||
city_id=city_id,
|
|
||||||
city_object_id=city_object_id)
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.add(simulation_result)
|
|
||||||
session.flush()
|
|
||||||
session.commit()
|
|
||||||
session.refresh(simulation_result)
|
|
||||||
return simulation_result.id
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('An error occurred while creating city_object %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def update(self, name: str, values: str, city_id=None, city_object_id=None):
|
|
||||||
"""
|
|
||||||
Updates simulation results for a city or a city object
|
|
||||||
:param name: The simulation results tool and workflow name
|
|
||||||
:param values: the simulation results in json format
|
|
||||||
:param city_id: optional city id
|
|
||||||
:param city_object_id: optional city object id
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
if city_id is not None:
|
|
||||||
session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
|
|
||||||
{
|
|
||||||
'values': values,
|
|
||||||
'updated': datetime.datetime.utcnow()
|
|
||||||
})
|
|
||||||
session.commit()
|
|
||||||
elif city_object_id is not None:
|
|
||||||
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
|
|
||||||
{
|
|
||||||
'values': values,
|
|
||||||
'updated': datetime.datetime.utcnow()
|
|
||||||
})
|
|
||||||
session.commit()
|
|
||||||
else:
|
|
||||||
raise NotImplementedError('Missing either city_id or city_object_id')
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while updating city object %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def delete(self, name: str, city_id=None, city_object_id=None):
|
|
||||||
"""
|
|
||||||
Deletes an application with the application_uuid
|
|
||||||
:param name: The simulation results tool and workflow name
|
|
||||||
:param city_id: The id for the city owning the simulation results
|
|
||||||
:param city_object_id: the id for the city_object owning these simulation results
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
if city_id is not None:
|
|
||||||
session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
|
|
||||||
session.commit()
|
|
||||||
elif city_object_id is not None:
|
|
||||||
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
|
|
||||||
session.commit()
|
|
||||||
else:
|
|
||||||
raise NotImplementedError('Missing either city_id or city_object_id')
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while deleting application: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def _get_city(self, city_id) -> City:
|
|
||||||
"""
|
|
||||||
Fetch a city object based city id
|
|
||||||
:param city_id: a city identifier
|
|
||||||
:return: [City] with the provided city_id
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
return session.execute(select(City).where(City.id == city_id)).first()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching city by city_id: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def _get_city_object(self, city_object_id) -> [CityObject]:
|
|
||||||
"""
|
|
||||||
Fetch a city object based city id
|
|
||||||
:param city_object_id: a city object identifier
|
|
||||||
:return: [CityObject] with the provided city_object_id
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
return session.execute(select(CityObject).where(CityObject.id == city_object_id)).first()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching city by city_id: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def get_simulation_results_by_city_id_city_object_id_and_names(self, city_id, city_object_id, result_names=None) -> [Model]:
|
|
||||||
"""
|
|
||||||
Fetch the simulation results based in the city_id or city_object_id with the given names or all
|
|
||||||
:param city_id: the city id
|
|
||||||
:param city_object_id: the city object id
|
|
||||||
:param result_names: if given filter the results
|
|
||||||
:return: [SimulationResult]
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
result_set = session.execute(select(Model).where(or_(
|
|
||||||
Model.city_id == city_id,
|
|
||||||
Model.city_object_id == city_object_id
|
|
||||||
)))
|
|
||||||
results = [r[0] for r in result_set]
|
|
||||||
if not result_names:
|
|
||||||
return results
|
|
||||||
filtered_results = []
|
|
||||||
for result in results:
|
|
||||||
if result.name in result_names:
|
|
||||||
filtered_results.append(result)
|
|
||||||
return filtered_results
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching city by city_id: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
@ -1,159 +0,0 @@
|
|||||||
"""
|
|
||||||
User repository with database CRUD operations
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from hub.helpers.auth import Auth
|
|
||||||
from hub.persistence.repository import Repository
|
|
||||||
from hub.persistence.models import User as Model, Application as ApplicationModel, UserRoles
|
|
||||||
|
|
||||||
|
|
||||||
class User(Repository):
|
|
||||||
"""
|
|
||||||
User class
|
|
||||||
"""
|
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __init__(self, db_name: str, dotenv_path: str, app_env: str):
|
|
||||||
super().__init__(db_name, dotenv_path, app_env)
|
|
||||||
|
|
||||||
def __new__(cls, db_name, dotenv_path, app_env):
|
|
||||||
"""
|
|
||||||
Implemented for a singleton pattern
|
|
||||||
"""
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super(User, cls).__new__(cls)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def insert(self, name: str, password: str, role: UserRoles, application_id: int):
|
|
||||||
"""
|
|
||||||
Inserts a new user
|
|
||||||
:param name: username
|
|
||||||
:param password: user password
|
|
||||||
:param role: user rol [Admin or Hub_Reader]
|
|
||||||
:param application_id: user application id
|
|
||||||
:return: Identity id
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
user = self.get_by_name_and_application(name, application_id)
|
|
||||||
if user is not None:
|
|
||||||
raise SQLAlchemyError(f'A user named {user.name} already exists for that application')
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id)
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.add(user)
|
|
||||||
session.flush()
|
|
||||||
session.commit()
|
|
||||||
session.refresh(user)
|
|
||||||
return user.id
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('An error occurred while creating user %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def update(self, user_id: int, name: str, password: str, role: UserRoles):
|
|
||||||
"""
|
|
||||||
Updates a user
|
|
||||||
:param user_id: the id of the user to be updated
|
|
||||||
:param name: the name of the user
|
|
||||||
:param password: the password of the user
|
|
||||||
:param role: the role of the user
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.query(Model).filter(Model.id == user_id).update({
|
|
||||||
'name': name,
|
|
||||||
'password': Auth.hash_password(password),
|
|
||||||
'role': role,
|
|
||||||
'updated': datetime.datetime.utcnow()
|
|
||||||
})
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while updating user: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def delete(self, user_id: int):
|
|
||||||
"""
|
|
||||||
Deletes a user with the id
|
|
||||||
:param user_id: the user id
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
session.query(Model).filter(Model.id == user_id).delete()
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching user: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
|
|
||||||
def get_by_name_and_application(self, name: str, application_id: int) -> Model:
|
|
||||||
"""
|
|
||||||
Fetch user based on the email address
|
|
||||||
:param name: Username
|
|
||||||
:param application_id: User application name
|
|
||||||
:return: User matching the search criteria or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
user = session.execute(
|
|
||||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
|
||||||
).first()
|
|
||||||
session.commit()
|
|
||||||
return user[0]
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching user by name and application: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
except TypeError as err:
|
|
||||||
logging.error('Error while fetching user, empty result %s', err)
|
|
||||||
raise TypeError from err
|
|
||||||
|
|
||||||
def get_by_name_application_id_and_password(self, name: str, password: str, application_id: int) -> Model:
|
|
||||||
"""
|
|
||||||
Fetch user based on the name, password and application id
|
|
||||||
:param name: Username
|
|
||||||
:param password: User password
|
|
||||||
:param application_id: Application id
|
|
||||||
:return: User
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
user = session.execute(
|
|
||||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
|
||||||
).first()
|
|
||||||
if user:
|
|
||||||
if Auth.check_password(password, user[0].password):
|
|
||||||
return user[0]
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching user by name: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
raise ValueError('Unauthorized')
|
|
||||||
|
|
||||||
def get_by_name_application_uuid_and_password(self, name: str, password: str, application_uuid: str) -> Model:
|
|
||||||
"""
|
|
||||||
Fetch user based on the email and password
|
|
||||||
:param name: Username
|
|
||||||
:param password: User password
|
|
||||||
:param application_uuid: Application uuid
|
|
||||||
:return: User
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with Session(self.engine) as session:
|
|
||||||
application = session.execute(
|
|
||||||
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
|
|
||||||
).first()
|
|
||||||
return self.get_by_name_application_id_and_password(name, password, application[0].id)
|
|
||||||
except SQLAlchemyError as err:
|
|
||||||
logging.error('Error while fetching user by name: %s', err)
|
|
||||||
raise SQLAlchemyError from err
|
|
||||||
except ValueError as err:
|
|
||||||
raise ValueError from err
|
|
@ -1,22 +0,0 @@
|
|||||||
"""
|
|
||||||
Base repository class to establish db connection
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2022 Concordia CERC group
|
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
from sqlalchemy import create_engine
|
|
||||||
from hub.persistence.configuration import Configuration
|
|
||||||
|
|
||||||
|
|
||||||
class Repository:
|
|
||||||
"""
|
|
||||||
Base repository class to establish db connection
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, db_name, dotenv_path: str, app_env='TEST'):
|
|
||||||
try:
|
|
||||||
self.configuration = Configuration(db_name, dotenv_path, app_env)
|
|
||||||
self.engine = create_engine(self.configuration.connection_string)
|
|
||||||
except ValueError as err:
|
|
||||||
logging.error('Missing value for credentials: %s', err)
|
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pathlib
|
||||||
|
python-dotenv
|
||||||
|
SQLAlchemy
|
||||||
|
cerc-hub
|
39
setup.py
Normal file
39
setup.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import glob
|
||||||
|
import pathlib
|
||||||
|
from distutils.util import convert_path
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
with pathlib.Path('requirements.txt').open() as r:
|
||||||
|
install_requires = [
|
||||||
|
str(requirement).replace('\n', '')
|
||||||
|
for requirement
|
||||||
|
in r.readlines()
|
||||||
|
]
|
||||||
|
install_requires.append('setuptools')
|
||||||
|
|
||||||
|
main_ns = {}
|
||||||
|
version = convert_path('cerc_persistence/version.py')
|
||||||
|
with open(version) as f:
|
||||||
|
exec(f.read(), main_ns)
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='cerc-persistence',
|
||||||
|
version=main_ns['__version__'],
|
||||||
|
description="",
|
||||||
|
long_description="",
|
||||||
|
classifiers=[
|
||||||
|
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
],
|
||||||
|
include_package_data=True,
|
||||||
|
packages=[
|
||||||
|
'cerc_persistence',
|
||||||
|
'cerc_persistence.models',
|
||||||
|
'cerc_persistence.repositories'
|
||||||
|
],
|
||||||
|
setup_requires=install_requires,
|
||||||
|
install_requires=install_requires,
|
||||||
|
data_files=[],
|
||||||
|
)
|
300
tests/test_db_factory.py
Normal file
300
tests/test_db_factory.py
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
"""
|
||||||
|
Test db factory
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
import distutils.spawn
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import sqlalchemy.exc
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.exc import ProgrammingError
|
||||||
|
|
||||||
|
import hub.helpers.constants as cte
|
||||||
|
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
|
||||||
|
from hub.exports.exports_factory import ExportsFactory
|
||||||
|
from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction
|
||||||
|
from hub.imports.construction_factory import ConstructionFactory
|
||||||
|
from hub.imports.energy_systems_factory import EnergySystemsFactory
|
||||||
|
from hub.imports.geometry_factory import GeometryFactory
|
||||||
|
from hub.imports.results_factory import ResultFactory
|
||||||
|
from hub.imports.usage_factory import UsageFactory
|
||||||
|
from hub.imports.weather_factory import WeatherFactory
|
||||||
|
from cerc_persistence.db_control import DBControl
|
||||||
|
from cerc_persistence.models import City, Application, CityObject, SimulationResults
|
||||||
|
from cerc_persistence.models import User, UserRoles
|
||||||
|
from cerc_persistence.repository import Repository
|
||||||
|
|
||||||
|
|
||||||
|
class Control:
|
||||||
|
_skip_test = False
|
||||||
|
_skip_reason = 'PostgreSQL not properly installed in host machine'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Test
|
||||||
|
setup
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self._skip_test = False
|
||||||
|
# Create test database
|
||||||
|
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='test_db', 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.close()
|
||||||
|
except ProgrammingError:
|
||||||
|
logging.info('Database does not exist. Nothing to delete')
|
||||||
|
except sqlalchemy.exc.OperationalError as operational_error:
|
||||||
|
self._skip_test = True
|
||||||
|
self._skip_reason = f'{operational_error}'
|
||||||
|
return
|
||||||
|
|
||||||
|
Application.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
User.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
City.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
CityObject.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
SimulationResults.__table__.create(bind=repository.engine, checkfirst=True)
|
||||||
|
|
||||||
|
city_file = Path('tests_data/test.geojson').resolve()
|
||||||
|
output_path = Path('tests_outputs/').resolve()
|
||||||
|
self._city = GeometryFactory('geojson',
|
||||||
|
city_file,
|
||||||
|
height_field='citygml_me',
|
||||||
|
year_of_construction_field='ANNEE_CONS',
|
||||||
|
aliases_field=['ID_UEV', 'CIVIQUE_DE', 'NOM_RUE'],
|
||||||
|
function_field='CODE_UTILI',
|
||||||
|
function_to_hub=MontrealFunctionToHubFunction().dictionary).city
|
||||||
|
|
||||||
|
ConstructionFactory('nrcan', self._city).enrich()
|
||||||
|
UsageFactory('nrcan', self._city).enrich()
|
||||||
|
WeatherFactory('epw', self._city).enrich()
|
||||||
|
ExportsFactory('sra', self._city, output_path).export()
|
||||||
|
sra_file = str((output_path / f'{self._city.name}_sra.xml').resolve())
|
||||||
|
subprocess.run([self.sra, sra_file], stdout=subprocess.DEVNULL)
|
||||||
|
ResultFactory('sra', self._city, output_path).enrich()
|
||||||
|
|
||||||
|
for building in self._city.buildings:
|
||||||
|
building.energy_systems_archetype_name = 'system 1 gas pv'
|
||||||
|
EnergySystemsFactory('montreal_custom', self._city).enrich()
|
||||||
|
EnergyBuildingsExportsFactory('insel_monthly_energy_balance', self._city, output_path).export()
|
||||||
|
_insel_files = glob.glob(f'{output_path}/*.insel')
|
||||||
|
for insel_file in _insel_files:
|
||||||
|
subprocess.run([self.insel, str(insel_file)], stdout=subprocess.DEVNULL)
|
||||||
|
ResultFactory('insel_monthly_energy_balance', self._city, output_path).enrich()
|
||||||
|
|
||||||
|
self._database = DBControl(
|
||||||
|
db_name=repository.configuration.db_name,
|
||||||
|
app_env='TEST',
|
||||||
|
dotenv_path=dotenv_path)
|
||||||
|
|
||||||
|
self._application_uuid = 'b9e0ce80-1218-410c-8a64-9d9b7026aad8'
|
||||||
|
self._application_id = 1
|
||||||
|
self._user_id = 1
|
||||||
|
|
||||||
|
self._application_id = self._database.persist_application(
|
||||||
|
'test',
|
||||||
|
'test',
|
||||||
|
self.application_uuid
|
||||||
|
)
|
||||||
|
self._user_id = self._database.create_user('test', self._application_id, 'test', UserRoles.Admin)
|
||||||
|
|
||||||
|
self._pickle_path = Path('tests_data/pickle_path.bz2').resolve()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def database(self):
|
||||||
|
return self._database
|
||||||
|
|
||||||
|
@property
|
||||||
|
def application_uuid(self):
|
||||||
|
return self._application_uuid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def application_id(self):
|
||||||
|
return self._application_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_id(self):
|
||||||
|
return self._user_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skip_test(self):
|
||||||
|
return self._skip_test
|
||||||
|
|
||||||
|
@property
|
||||||
|
def insel(self):
|
||||||
|
return distutils.spawn.find_executable('insel')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sra(self):
|
||||||
|
return distutils.spawn.find_executable('sra')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skip_insel_test(self):
|
||||||
|
return self.insel is None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skip_reason(self):
|
||||||
|
return self._skip_reason
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
return self._skip_reason
|
||||||
|
|
||||||
|
@property
|
||||||
|
def city(self):
|
||||||
|
return self._city
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pickle_path(self):
|
||||||
|
return self._pickle_path
|
||||||
|
|
||||||
|
|
||||||
|
control = Control()
|
||||||
|
|
||||||
|
|
||||||
|
class TestDBFactory(TestCase):
|
||||||
|
"""
|
||||||
|
TestDBFactory
|
||||||
|
"""
|
||||||
|
|
||||||
|
@unittest.skipIf(control.skip_test, control.skip_reason)
|
||||||
|
def test_save_city(self):
|
||||||
|
control.city.name = "Montreal"
|
||||||
|
city_id = control.database.persist_city(
|
||||||
|
control.city,
|
||||||
|
control.pickle_path,
|
||||||
|
control.city.name,
|
||||||
|
control.application_id,
|
||||||
|
control.user_id)
|
||||||
|
control.database.delete_city(city_id)
|
||||||
|
|
||||||
|
@unittest.skipIf(control.skip_test, control.skip_reason)
|
||||||
|
def test_get_update_city(self):
|
||||||
|
city_id = control.database.persist_city(control.city,
|
||||||
|
control.pickle_path,
|
||||||
|
control.city.name,
|
||||||
|
control.application_id,
|
||||||
|
control.user_id)
|
||||||
|
control.city.name = "Ottawa"
|
||||||
|
control.database.update_city(city_id, control.city)
|
||||||
|
cities = control.database.cities_by_user_and_application(
|
||||||
|
control.user_id,
|
||||||
|
control.application_id)
|
||||||
|
for updated_city in cities:
|
||||||
|
if updated_city.id == city_id:
|
||||||
|
self.assertEqual(updated_city.name, control.city.name)
|
||||||
|
break
|
||||||
|
control.database.delete_city(city_id)
|
||||||
|
|
||||||
|
@unittest.skipIf(control.skip_test, control.skip_reason)
|
||||||
|
@unittest.skipIf(control.skip_insel_test, 'insel is not installed')
|
||||||
|
def test_save_results(self):
|
||||||
|
city_id = control.database.persist_city(control.city,
|
||||||
|
control.pickle_path,
|
||||||
|
'current status',
|
||||||
|
control.application_id,
|
||||||
|
control.user_id)
|
||||||
|
city_objects_id = []
|
||||||
|
for building in control.city.buildings:
|
||||||
|
_building = control.database.building_info(building.name, city_id)
|
||||||
|
if cte.MONTH not in building.cooling_demand:
|
||||||
|
print(f'building {building.name} not calculated')
|
||||||
|
continue
|
||||||
|
monthly_cooling_peak_load = building.cooling_peak_load[cte.MONTH]
|
||||||
|
yearly_cooling_peak_load = building.cooling_peak_load[cte.YEAR]
|
||||||
|
monthly_heating_peak_load = building.heating_peak_load[cte.MONTH]
|
||||||
|
yearly_heating_peak_load = building.heating_peak_load[cte.YEAR]
|
||||||
|
monthly_lighting_peak_load = building.lighting_peak_load[cte.MONTH]
|
||||||
|
yearly_lighting_peak_load = building.lighting_peak_load[cte.YEAR]
|
||||||
|
monthly_appliances_peak_load = building.appliances_peak_load[cte.MONTH]
|
||||||
|
yearly_appliances_peak_load = building.appliances_peak_load[cte.YEAR]
|
||||||
|
monthly_cooling_demand = building.cooling_demand[cte.MONTH]
|
||||||
|
yearly_cooling_demand = building.cooling_demand[cte.YEAR]
|
||||||
|
monthly_heating_demand = building.heating_demand[cte.MONTH]
|
||||||
|
yearly_heating_demand = building.heating_demand[cte.YEAR]
|
||||||
|
monthly_lighting_electrical_demand = building.lighting_electrical_demand[cte.MONTH]
|
||||||
|
yearly_lighting_electrical_demand = building.lighting_electrical_demand[cte.YEAR]
|
||||||
|
monthly_appliances_electrical_demand = building.appliances_electrical_demand[cte.MONTH]
|
||||||
|
yearly_appliances_electrical_demand = building.appliances_electrical_demand[cte.YEAR]
|
||||||
|
monthly_domestic_hot_water_heat_demand = building.domestic_hot_water_heat_demand[cte.MONTH]
|
||||||
|
yearly_domestic_hot_water_heat_demand = building.domestic_hot_water_heat_demand[cte.YEAR]
|
||||||
|
monthly_heating_consumption = building.heating_consumption[cte.MONTH]
|
||||||
|
yearly_heating_consumption = building.heating_consumption[cte.YEAR]
|
||||||
|
monthly_cooling_consumption = building.cooling_consumption[cte.MONTH]
|
||||||
|
yearly_cooling_consumption = building.cooling_consumption[cte.YEAR]
|
||||||
|
monthly_domestic_hot_water_consumption = building.domestic_hot_water_consumption[cte.MONTH]
|
||||||
|
yearly_domestic_hot_water_consumption = building._domestic_hot_water_consumption[cte.YEAR]
|
||||||
|
monthly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[
|
||||||
|
cte.MONTH]
|
||||||
|
yearly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[
|
||||||
|
cte.YEAR]
|
||||||
|
monthly_on_site_electrical_production = [x * cte.WATTS_HOUR_TO_JULES
|
||||||
|
for x in building.onsite_electrical_production[cte.MONTH]]
|
||||||
|
yearly_on_site_electrical_production = [x * cte.WATTS_HOUR_TO_JULES
|
||||||
|
for x in building.onsite_electrical_production[cte.YEAR]]
|
||||||
|
results = json.dumps({cte.INSEL_MEB: [
|
||||||
|
{'monthly_cooling_peak_load': monthly_cooling_peak_load},
|
||||||
|
{'yearly_cooling_peak_load': yearly_cooling_peak_load},
|
||||||
|
{'monthly_heating_peak_load': monthly_heating_peak_load},
|
||||||
|
{'yearly_heating_peak_load': yearly_heating_peak_load},
|
||||||
|
{'monthly_lighting_peak_load': monthly_lighting_peak_load},
|
||||||
|
{'yearly_lighting_peak_load': yearly_lighting_peak_load},
|
||||||
|
{'monthly_appliances_peak_load': monthly_appliances_peak_load},
|
||||||
|
{'yearly_appliances_peak_load': yearly_appliances_peak_load},
|
||||||
|
{'monthly_cooling_demand': monthly_cooling_demand},
|
||||||
|
{'yearly_cooling_demand': yearly_cooling_demand},
|
||||||
|
{'monthly_heating_demand': monthly_heating_demand},
|
||||||
|
{'yearly_heating_demand': yearly_heating_demand},
|
||||||
|
{'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand},
|
||||||
|
{'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand},
|
||||||
|
{'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand},
|
||||||
|
{'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand},
|
||||||
|
{'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand},
|
||||||
|
{'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand},
|
||||||
|
{'monthly_heating_consumption': monthly_heating_consumption},
|
||||||
|
{'yearly_heating_consumption': yearly_heating_consumption},
|
||||||
|
{'monthly_cooling_consumption': monthly_cooling_consumption},
|
||||||
|
{'yearly_cooling_consumption': yearly_cooling_consumption},
|
||||||
|
{'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption},
|
||||||
|
{'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption},
|
||||||
|
{
|
||||||
|
'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption},
|
||||||
|
{
|
||||||
|
'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption},
|
||||||
|
{'monthly_on_site_electrical_production': monthly_on_site_electrical_production},
|
||||||
|
{'yearly_on_site_electrical_production': yearly_on_site_electrical_production}
|
||||||
|
]})
|
||||||
|
|
||||||
|
db_building_id = _building.id
|
||||||
|
city_objects_id.append(db_building_id)
|
||||||
|
control.database.add_simulation_results(
|
||||||
|
cte.INSEL_MEB,
|
||||||
|
results, city_object_id=db_building_id)
|
||||||
|
self.assertEqual(17, len(city_objects_id), 'wrong number of results')
|
||||||
|
self.assertIsNotNone(city_objects_id[0], 'city_object_id is None')
|
||||||
|
for _id in city_objects_id:
|
||||||
|
control.database.delete_results_by_name('insel meb', city_object_id=_id)
|
||||||
|
control.database.delete_city(city_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@unittest.skipIf(control.skip_test, control.skip_reason)
|
||||||
|
def tearDownClass(cls):
|
||||||
|
control.database.delete_application(control.application_uuid)
|
||||||
|
control.database.delete_user(control.user_id)
|
||||||
|
os.unlink(control.pickle_path)
|
135
tests/test_db_retrieve.py
Normal file
135
tests/test_db_retrieve.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
"""
|
||||||
|
Test db factory
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
|
"""
|
||||||
|
import distutils.spawn
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import sqlalchemy.exc
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.exc import ProgrammingError
|
||||||
|
from cerc_persistence.db_control import DBControl
|
||||||
|
from cerc_persistence.repository import Repository
|
||||||
|
|
||||||
|
|
||||||
|
class Control:
|
||||||
|
_skip_test = False
|
||||||
|
_skip_reason = 'PostgreSQL not properly installed in host machine'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Test
|
||||||
|
setup
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self._skip_test = False
|
||||||
|
# Create test database
|
||||||
|
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='montreal_retrofit_test', 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.close()
|
||||||
|
except ProgrammingError:
|
||||||
|
logging.info('Database does not exist. Nothing to delete')
|
||||||
|
except sqlalchemy.exc.OperationalError as operational_error:
|
||||||
|
self._skip_test = True
|
||||||
|
self._skip_reason = f'{operational_error}'
|
||||||
|
return
|
||||||
|
|
||||||
|
self._database = DBControl(
|
||||||
|
db_name=repository.configuration.db_name,
|
||||||
|
app_env='TEST',
|
||||||
|
dotenv_path=dotenv_path)
|
||||||
|
|
||||||
|
self._application_uuid = '60b7fc1b-f389-4254-9ffd-22a4cf32c7a3'
|
||||||
|
self._application_id = 1
|
||||||
|
self._user_id = 1
|
||||||
|
self._pickle_path = 'tests_data/pickle_path.bz2'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def database(self):
|
||||||
|
return self._database
|
||||||
|
|
||||||
|
@property
|
||||||
|
def application_uuid(self):
|
||||||
|
return self._application_uuid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def application_id(self):
|
||||||
|
return self._application_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_id(self):
|
||||||
|
return self._user_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skip_test(self):
|
||||||
|
return self._skip_test
|
||||||
|
|
||||||
|
@property
|
||||||
|
def insel(self):
|
||||||
|
return distutils.spawn.find_executable('insel')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sra(self):
|
||||||
|
return distutils.spawn.find_executable('sra')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skip_insel_test(self):
|
||||||
|
return self.insel is None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skip_reason(self):
|
||||||
|
return self._skip_reason
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
return self._skip_reason
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pickle_path(self):
|
||||||
|
return self._pickle_path
|
||||||
|
|
||||||
|
|
||||||
|
control = Control()
|
||||||
|
|
||||||
|
|
||||||
|
class TestDBFactory(TestCase):
|
||||||
|
"""
|
||||||
|
TestDBFactory
|
||||||
|
"""
|
||||||
|
|
||||||
|
@unittest.skipIf(control.skip_test, control.skip_reason)
|
||||||
|
def test_retrieve_results(self):
|
||||||
|
request_values = {
|
||||||
|
"scenarios": [
|
||||||
|
{
|
||||||
|
"current status": ["01002777", "01002773", "01036804"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"skin retrofit": ["01002777", "01002773", "01036804"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system retrofit and pv": ["01002777", "01002773", "01036804"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"skin and system retrofit with pv": ["01002777", "01002773", "01036804"]
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
results = control.database.results(control.user_id, control.application_id, request_values)
|
||||||
|
print(results)
|
3377
tests/tests_data/C40_Final.gml
Normal file
3377
tests/tests_data/C40_Final.gml
Normal file
File diff suppressed because it is too large
Load Diff
240
tests/tests_data/FZK_Haus_LoD_2.gml
Normal file
240
tests/tests_data/FZK_Haus_LoD_2.gml
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- IFC to CityGML by IFCExplorer KIT --><!-- CityGML to Sketchup by Sketchup CityGML Plugin FH GelsenKirchen --><!--CityGML Dataset produced with CityGML Export Plugin for Sketchup by GeoRES --><!--http://www.geores.de --><!-- Edited Manually in Oxygen 8.2 --><!-- Modified by GMLOffset.xslt at Mon Dec 6 2010 --><!-- Version 2 Building located in the area of KIT Campus North)--><!-- Modified by GMLOffset.xslt at Wed Dec 8 2010 --><!-- Modified by GMLOffset.xslt at Wed Mar 29 2017 --><core:CityModel xsi:schemaLocation="http://www.opengis.net/citygml/2.0 http://schemas.opengis.net/citygml/2.0/cityGMLBase.xsd http://www.opengis.net/citygml/appearance/2.0 http://schemas.opengis.net/citygml/appearance/2.0/appearance.xsd http://www.opengis.net/citygml/building/2.0 http://schemas.opengis.net/citygml/building/2.0/building.xsd http://www.opengis.net/citygml/generics/2.0 http://schemas.opengis.net/citygml/generics/2.0/generics.xsd" xmlns:core="http://www.opengis.net/citygml/2.0" xmlns="http://www.opengis.net/citygml/profiles/base/2.0" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:grp="http://www.opengis.net/citygml/cityobjectgroup/2.0" xmlns:app="http://www.opengis.net/citygml/appearance/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xAL="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<!-- Manually edited by KHH 23.01.2017, CityGML 2.0, Address added, Codespaces added -->
|
||||||
|
<gml:name>AC14-FZK-Haus</gml:name>
|
||||||
|
<gml:boundedBy>
|
||||||
|
<gml:Envelope srsDimension="3" srsName="urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH">
|
||||||
|
<gml:lowerCorner srsDimension="3">457842 5439083 111.8 </gml:lowerCorner>
|
||||||
|
<gml:upperCorner srsDimension="3">457854 5439093 118.317669 </gml:upperCorner>
|
||||||
|
</gml:Envelope>
|
||||||
|
</gml:boundedBy>
|
||||||
|
<core:cityObjectMember>
|
||||||
|
<bldg:Building gml:id="UUID_d281adfc-4901-0f52-540b-4cc1a9325f82">
|
||||||
|
<gml:description>FZK-Haus (Forschungszentrum Karlsruhe, now KIT), created by Karl-Heinz
|
||||||
|
Haefele </gml:description>
|
||||||
|
<gml:name>AC14-FZK-Haus</gml:name>
|
||||||
|
<core:creationDate>2017-01-23</core:creationDate>
|
||||||
|
<core:relativeToTerrain>entirelyAboveTerrain</core:relativeToTerrain>
|
||||||
|
<gen:measureAttribute name="GrossPlannedArea">
|
||||||
|
<gen:value uom="m2">120.00</gen:value>
|
||||||
|
</gen:measureAttribute>
|
||||||
|
<gen:stringAttribute name="ConstructionMethod">
|
||||||
|
<gen:value>New Building</gen:value>
|
||||||
|
</gen:stringAttribute>
|
||||||
|
<gen:stringAttribute name="IsLandmarked">
|
||||||
|
<gen:value>NO</gen:value>
|
||||||
|
</gen:stringAttribute>
|
||||||
|
<bldg:class codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_class.xml">1000</bldg:class>
|
||||||
|
<bldg:function codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_function.xml">1000</bldg:function>
|
||||||
|
<bldg:usage codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_usage.xml">1000</bldg:usage>
|
||||||
|
<bldg:yearOfConstruction>2020</bldg:yearOfConstruction>
|
||||||
|
<bldg:roofType codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_roofType.xml">1030</bldg:roofType>
|
||||||
|
<bldg:measuredHeight uom="m">6.52</bldg:measuredHeight>
|
||||||
|
<bldg:storeysAboveGround>2</bldg:storeysAboveGround>
|
||||||
|
<bldg:storeysBelowGround>0</bldg:storeysBelowGround>
|
||||||
|
<bldg:lod2Solid>
|
||||||
|
<gml:Solid>
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:CompositeSurface>
|
||||||
|
<!--Outer Wall 1 (West) -->
|
||||||
|
<gml:surfaceMember xlink:href="#PolyID7350_878_759628_120742"> </gml:surfaceMember>
|
||||||
|
<!--Outer Wall 1 (West) -->
|
||||||
|
<!--Outer Wall 2 (South) -->
|
||||||
|
<gml:surfaceMember xlink:href="#PolyID7351_1722_416019_316876" />
|
||||||
|
<!--Outer Wall 2 (South) -->
|
||||||
|
<!--Outer Wall 3 (East) -->
|
||||||
|
<gml:surfaceMember xlink:href="#PolyID7352_230_209861_355851" />
|
||||||
|
<!--Outer Wall 3 (East) -->
|
||||||
|
<!--Roof 1 (North) -->
|
||||||
|
<gml:surfaceMember xlink:href="#PolyID7353_166_774155_320806" />
|
||||||
|
<!--Roof 1 (North) -->
|
||||||
|
<!--Outer Wall 4 (North) -->
|
||||||
|
<gml:surfaceMember xlink:href="#PolyID7354_1362_450904_410226" />
|
||||||
|
<!--Outer Wall 2 (North) -->
|
||||||
|
<!--Roof 2 (South) -->
|
||||||
|
<gml:surfaceMember xlink:href="#PolyID7355_537_416207_260034" />
|
||||||
|
<!--Roof 2 (South) -->
|
||||||
|
<!--Base Surface -->
|
||||||
|
<gml:surfaceMember xlink:href="#PolyID7356_612_880782_415367" />
|
||||||
|
<!--Base Surface -->
|
||||||
|
</gml:CompositeSurface>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Solid>
|
||||||
|
</bldg:lod2Solid>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="GML_5856d7ad-5e34-498a-817b-9544bfbb1475">
|
||||||
|
<gml:name>Outer Wall 1 (West)</gml:name>
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface>
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="PolyID7350_878_759628_120742">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing gml:id="PolyID7350_878_759628_120742_0">
|
||||||
|
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||||
|
<gml:pos>457842 5439093 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457842 5439093 111.8 </gml:pos>
|
||||||
|
<gml:pos>457842 5439083 111.8 </gml:pos>
|
||||||
|
<gml:pos>457842 5439083 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="GML_d38cf762-c29d-4491-88c9-bdc89e141978">
|
||||||
|
<gml:name>Outer Wall 2 (South)</gml:name>
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface>
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="PolyID7351_1722_416019_316876">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing gml:id="PolyID7351_1722_416019_316876_0">
|
||||||
|
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457842 5439083 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457842 5439083 111.8 </gml:pos>
|
||||||
|
<gml:pos>457854 5439083 111.8 </gml:pos>
|
||||||
|
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="GML_8e5db638-e46a-4739-a98a-2fc2d39c9069">
|
||||||
|
<gml:name>Outer Wall 3 (East)</gml:name>
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface>
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="PolyID7352_230_209861_355851">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing gml:id="PolyID7352_230_209861_355851_0">
|
||||||
|
<gml:pos>457854 5439088 118.317691453624 </gml:pos>
|
||||||
|
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457854 5439083 111.8 </gml:pos>
|
||||||
|
<gml:pos>457854 5439093 111.8 </gml:pos>
|
||||||
|
<gml:pos>457854 5439093 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457854 5439088 118.317691453624 </gml:pos>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="GML_875d470b-32b4-4985-a4c8-0f02caa342a2">
|
||||||
|
<gml:name>Roof 1 (North)</gml:name>
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface>
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="PolyID7353_166_774155_320806">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing gml:id="PolyID7353_166_774155_320806_0">
|
||||||
|
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||||
|
<gml:pos>457854 5439088 118.317691453624 </gml:pos>
|
||||||
|
<gml:pos>457854 5439093 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457842 5439093 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="GML_0f30f604-e70d-4dfe-ba35-853bc69609cc">
|
||||||
|
<gml:name>Outer Wall 4 (North)</gml:name>
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface>
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="PolyID7354_1362_450904_410226">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing gml:id="PolyID7354_1362_450904_410226_0">
|
||||||
|
<gml:pos>457842 5439093 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457854 5439093 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457854 5439093 111.8 </gml:pos>
|
||||||
|
<gml:pos>457842 5439093 111.8 </gml:pos>
|
||||||
|
<gml:pos>457842 5439093 115.430940107676 </gml:pos>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="GML_eeb6796a-e261-4d3b-a6f2-475940cca80a">
|
||||||
|
<gml:name>Roof 2 (South)</gml:name>
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface>
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="PolyID7355_537_416207_260034">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing gml:id="PolyID7355_537_416207_260034_0">
|
||||||
|
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457854 5439088 118.317691453624 </gml:pos>
|
||||||
|
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||||
|
<gml:pos>457842 5439083 115.430940107676 </gml:pos>
|
||||||
|
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:GroundSurface gml:id="GML_257a8dde-8194-4ca3-b581-abd591dcd6a3">
|
||||||
|
<gml:description>Bodenplatte</gml:description>
|
||||||
|
<gml:name>Base Surface</gml:name>
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface>
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="PolyID7356_612_880782_415367">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing gml:id="PolyID7356_612_880782_415367_0">
|
||||||
|
<gml:pos>457854 5439083 111.8 </gml:pos>
|
||||||
|
<gml:pos>457842 5439083 111.8 </gml:pos>
|
||||||
|
<gml:pos>457842 5439093 111.8 </gml:pos>
|
||||||
|
<gml:pos>457854 5439093 111.8 </gml:pos>
|
||||||
|
<gml:pos>457854 5439083 111.8 </gml:pos>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:GroundSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:address>
|
||||||
|
<core:Address>
|
||||||
|
<core:xalAddress>
|
||||||
|
<xAL:AddressDetails>
|
||||||
|
<xAL:Locality Type="Town">
|
||||||
|
<xAL:LocalityName>Eggenstein-Leopoldshafen</xAL:LocalityName>
|
||||||
|
<xAL:Thoroughfare Type="Street">
|
||||||
|
<xAL:ThoroughfareNumber>4711</xAL:ThoroughfareNumber>
|
||||||
|
<xAL:ThoroughfareName>Spöcker Straße</xAL:ThoroughfareName>
|
||||||
|
</xAL:Thoroughfare>
|
||||||
|
<xAL:PostalCode>
|
||||||
|
<xAL:PostalCodeNumber>76344</xAL:PostalCodeNumber>
|
||||||
|
</xAL:PostalCode>
|
||||||
|
</xAL:Locality>
|
||||||
|
</xAL:AddressDetails>
|
||||||
|
</core:xalAddress>
|
||||||
|
</core:Address>
|
||||||
|
</bldg:address>
|
||||||
|
</bldg:Building>
|
||||||
|
</core:cityObjectMember>
|
||||||
|
</core:CityModel>
|
177
tests/tests_data/eilat.geojson
Normal file
177
tests/tests_data/eilat.geojson
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
{
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": 1,
|
||||||
|
"properties": {
|
||||||
|
"heightmax": 9,
|
||||||
|
"ANNEE_CONS": 1978,
|
||||||
|
"CODE_UTILI": "residential"
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
34.95217088371581,
|
||||||
|
29.56694805860026
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95262396587913,
|
||||||
|
29.566952667742285
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95261999147337,
|
||||||
|
29.567024109421467
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.952169558914704,
|
||||||
|
29.567019500282157
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95217088371581,
|
||||||
|
29.56694805860026
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"type": "Polygon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": 3,
|
||||||
|
"properties": {
|
||||||
|
"heightmax": 16,
|
||||||
|
"ANNEE_CONS": 2012,
|
||||||
|
"CODE_UTILI": "dormitory"
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
34.95176644317411,
|
||||||
|
29.56827388702702
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95176550020565,
|
||||||
|
29.568180388329026
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95179850408434,
|
||||||
|
29.568180388329026
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95179850408434,
|
||||||
|
29.5681303582886
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95176644317411,
|
||||||
|
29.5681303582886
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95176644317411,
|
||||||
|
29.568038499789708
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.951874884488376,
|
||||||
|
29.568038499789708
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.951874884488376,
|
||||||
|
29.568058183760357
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95192391882168,
|
||||||
|
29.568058183760357
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.951922032885705,
|
||||||
|
29.56804178045124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95205216246262,
|
||||||
|
29.568042600617147
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.952051219494166,
|
||||||
|
29.568129538124154
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95201821561636,
|
||||||
|
29.5681303582886
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95201821561636,
|
||||||
|
29.568176287507143
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95204839059062,
|
||||||
|
29.568176287507143
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95205027652662,
|
||||||
|
29.56827552735433
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95195503676348,
|
||||||
|
29.568274707190284
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95195597973188,
|
||||||
|
29.56825830391628
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.951849424353696,
|
||||||
|
29.56825830391628
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.951849424353696,
|
||||||
|
29.568274707190284
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.95176644317411,
|
||||||
|
29.56827388702702
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"type": "Polygon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": 2,
|
||||||
|
"properties": {
|
||||||
|
"heightmax": 24,
|
||||||
|
"ANNEE_CONS": 2002,
|
||||||
|
"CODE_UTILI": "Hotel employ"
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
34.94972280674813,
|
||||||
|
29.566224752287738
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.94974316291999,
|
||||||
|
29.56597561012454
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.94989147217407,
|
||||||
|
29.565980668855033
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.94987402402688,
|
||||||
|
29.566233605043536
|
||||||
|
],
|
||||||
|
[
|
||||||
|
34.94972280674813,
|
||||||
|
29.566224752287738
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"type": "Polygon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
81
tests/tests_data/kelowna.obj
Normal file
81
tests/tests_data/kelowna.obj
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# https://github.com/mikedh/trimesh
|
||||||
|
v 329238.00000000 5528272.00000000 0.00000000
|
||||||
|
v 329238.00000000 5528272.00000000 3.79999995
|
||||||
|
v 329254.12500000 5528263.00000000 0.00000000
|
||||||
|
v 329254.12500000 5528263.00000000 3.79999995
|
||||||
|
v 329245.12500000 5528267.50000000 4.93084002
|
||||||
|
v 329246.15625000 5528272.50000000 0.00000000
|
||||||
|
v 329246.15625000 5528272.50000000 3.79999995
|
||||||
|
v 329229.15625000 5528271.00000000 0.00000000
|
||||||
|
v 329229.15625000 5528271.00000000 3.79999995
|
||||||
|
v 329242.18750000 5528267.00000000 5.29822016
|
||||||
|
v 329238.31250000 5528266.50000000 4.68875980
|
||||||
|
v 329229.31250000 5528269.50000000 0.00000000
|
||||||
|
v 329229.31250000 5528269.50000000 3.79999995
|
||||||
|
v 329244.34375000 5528267.00000000 4.99910021
|
||||||
|
v 329242.34375000 5528267.00000000 5.30000019
|
||||||
|
v 329233.34375000 5528276.00000000 0.00000000
|
||||||
|
v 329233.34375000 5528276.00000000 3.79999995
|
||||||
|
v 329247.34375000 5528262.50000000 0.00000000
|
||||||
|
v 329247.34375000 5528262.50000000 3.79999995
|
||||||
|
v 329242.40625000 5528257.50000000 0.00000000
|
||||||
|
v 329242.40625000 5528257.50000000 3.79999995
|
||||||
|
v 329231.50000000 5528270.50000000 4.31147003
|
||||||
|
v 329253.53125000 5528273.00000000 0.00000000
|
||||||
|
v 329253.53125000 5528273.00000000 3.79999995
|
||||||
|
v 329241.71875000 5528276.50000000 0.00000000
|
||||||
|
v 329241.71875000 5528276.50000000 3.79999995
|
||||||
|
v 329233.81250000 5528270.50000000 4.68364000
|
||||||
|
v 329248.81250000 5528267.50000000 4.92572021
|
||||||
|
f 22 9 13
|
||||||
|
f 28 4 24
|
||||||
|
f 23 6 7
|
||||||
|
f 7 24 23
|
||||||
|
f 6 25 26
|
||||||
|
f 26 7 6
|
||||||
|
f 25 1 2
|
||||||
|
f 2 26 25
|
||||||
|
f 1 16 17
|
||||||
|
f 17 2 1
|
||||||
|
f 16 8 9
|
||||||
|
f 9 17 16
|
||||||
|
f 8 12 13
|
||||||
|
f 13 9 8
|
||||||
|
f 12 20 21
|
||||||
|
f 21 13 12
|
||||||
|
f 20 18 19
|
||||||
|
f 19 21 20
|
||||||
|
f 18 3 4
|
||||||
|
f 4 19 18
|
||||||
|
f 3 23 24
|
||||||
|
f 24 4 3
|
||||||
|
f 6 23 3
|
||||||
|
f 6 3 18
|
||||||
|
f 6 18 20
|
||||||
|
f 6 20 12
|
||||||
|
f 6 12 8
|
||||||
|
f 8 16 1
|
||||||
|
f 6 8 1
|
||||||
|
f 1 25 6
|
||||||
|
f 24 7 14
|
||||||
|
f 24 14 5
|
||||||
|
f 5 28 24
|
||||||
|
f 7 26 15
|
||||||
|
f 15 14 7
|
||||||
|
f 26 2 11
|
||||||
|
f 26 11 10
|
||||||
|
f 10 15 26
|
||||||
|
f 2 17 27
|
||||||
|
f 27 11 2
|
||||||
|
f 17 9 22
|
||||||
|
f 22 27 17
|
||||||
|
f 21 10 11
|
||||||
|
f 13 21 11
|
||||||
|
f 13 11 27
|
||||||
|
f 27 22 13
|
||||||
|
f 21 19 5
|
||||||
|
f 21 5 14
|
||||||
|
f 21 14 15
|
||||||
|
f 15 10 21
|
||||||
|
f 19 4 28
|
||||||
|
f 28 5 19
|
73
tests/tests_data/levis.geojson
Normal file
73
tests/tests_data/levis.geojson
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"id": 1,
|
||||||
|
"properties": {
|
||||||
|
"OBJECTID_12": 1,
|
||||||
|
"gml_id": 1,
|
||||||
|
"citygml_me": 20,
|
||||||
|
"Z_Min": 46.1162,
|
||||||
|
"Z_Max": 66.1162,
|
||||||
|
"ANNEE_CONS": 2023,
|
||||||
|
"CODE_UTILI": 1000
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
-71.16553932594044,
|
||||||
|
46.7895775031096
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16535210635354,
|
||||||
|
46.78972033813616
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.1654671126711,
|
||||||
|
46.78979908036044
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16525314742928,
|
||||||
|
46.78995473325631
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16480114585448,
|
||||||
|
46.7896544143249
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16486533542763,
|
||||||
|
46.789394380725696
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16467544127534,
|
||||||
|
46.78927901330414
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16454171299826,
|
||||||
|
46.78930465053031
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16445612690187,
|
||||||
|
46.789766118513455
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16519698155322,
|
||||||
|
46.79023673853192
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16583887727946,
|
||||||
|
46.78976794972763
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-71.16553932594044,
|
||||||
|
46.7895775031096
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"type": "Polygon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
409
tests/tests_data/one_building_in_kelowna.gml
Normal file
409
tests/tests_data/one_building_in_kelowna.gml
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<core:CityModel xmlns:brid="http://www.opengis.net/citygml/bridge/2.0" xmlns:tran="http://www.opengis.net/citygml/transportation/2.0" xmlns:frn="http://www.opengis.net/citygml/cityfurniture/2.0" xmlns:wtr="http://www.opengis.net/citygml/waterbody/2.0" xmlns:sch="http://www.ascc.net/xml/schematron" xmlns:veg="http://www.opengis.net/citygml/vegetation/2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:tun="http://www.opengis.net/citygml/tunnel/2.0" xmlns:tex="http://www.opengis.net/citygml/texturedsurface/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:dem="http://www.opengis.net/citygml/relief/2.0" xmlns:app="http://www.opengis.net/citygml/appearance/2.0" xmlns:luse="http://www.opengis.net/citygml/landuse/2.0" xmlns:xAL="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:smil20lang="http://www.w3.org/2001/SMIL20/Language" xmlns:pbase="http://www.opengis.net/citygml/profiles/base/2.0" xmlns:smil20="http://www.w3.org/2001/SMIL20/" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:core="http://www.opengis.net/citygml/2.0" xmlns:grp="http://www.opengis.net/citygml/cityobjectgroup/2.0">
|
||||||
|
<gml:boundedBy>
|
||||||
|
<gml:Envelope srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:lowerCorner>326011.03601000085 5526048.416990001 -1.6000000000058208</gml:lowerCorner>
|
||||||
|
<gml:upperCorner>329466.6600299999 5529018.72205 9.80000000000291</gml:upperCorner>
|
||||||
|
</gml:Envelope>
|
||||||
|
</gml:boundedBy>
|
||||||
|
<core:cityObjectMember>
|
||||||
|
<bldg:Building gml:id="BLD109438">
|
||||||
|
<gen:doubleAttribute name="gross_floor_area">
|
||||||
|
<gen:value>291</gen:value>
|
||||||
|
</gen:doubleAttribute>
|
||||||
|
<gen:stringAttribute name="gross_floor_raea_unit">
|
||||||
|
<gen:value>m2</gen:value>
|
||||||
|
</gen:stringAttribute>
|
||||||
|
<bldg:function>residential</bldg:function>
|
||||||
|
<bldg:yearOfConstruction>1996</bldg:yearOfConstruction>
|
||||||
|
<bldg:measuredHeight>5.3</bldg:measuredHeight>
|
||||||
|
<bldg:storeysAboveGround>1</bldg:storeysAboveGround>
|
||||||
|
<bldg:lod2Solid>
|
||||||
|
<gml:Solid srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:CompositeSurface>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_854e7876-bcb7-43f5-9f4b-7c55803cf04f"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_50045e42-87aa-4aa4-b179-99d03a5569df"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_6138b267-e734-4830-98f8-a79fc4d38da4"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_7e4a20ee-4581-4e9a-a661-3e80c79ae226"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_770546ef-e544-4d39-8747-e5c6c88d5725"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_0f22b07c-8bd5-43d1-8904-c96a5a0456ce"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_b6219259-c948-487a-96dc-25f9ce257974"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_d806c8f3-93e1-4155-ab28-743fed870f6b"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_da660fbf-9aea-4895-8d9c-cf5fab95862e"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_6315337c-3919-423e-9e46-35fc5f005b7d"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_6bed5c5e-9ee9-4b3a-bfbc-fac54c0f2090"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_8f4f6388-d576-4ded-925a-fd01d43e3c11"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_ad685374-7888-41cf-8464-48c037230174"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_1b440294-d10f-49e2-9c65-78aa0a57a389"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_4d4017ed-3a71-43c7-a79c-04acd9f86433"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_b7c0600b-0c3b-4b8c-8f5f-11d8f774966e"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_18f19ab4-f128-41a0-ab05-34d91ad061b9"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_e5b962d8-6186-4e78-ae08-fc0c00484e8c"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_eebbc322-bf68-4c56-a826-392b617db97c"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_16a00d48-90a4-4cd1-94e6-0654a5b9b1d2"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_4832dea6-f237-45ec-a711-ce1fc27b7e3b"/>
|
||||||
|
</gml:CompositeSurface>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Solid>
|
||||||
|
</bldg:lod2Solid>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_2e3a196c-b5b1-4ee4-af82-329ced61e624">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_854e7876-bcb7-43f5-9f4b-7c55803cf04f">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329231.5010599997 5528270.404139999 4.311470000000554 329229.15295 5528271.14002 3.8000000000029104 329229.30395000055 5528269.304020001 3.8000000000029104 329231.5010599997 5528270.404139999 4.311470000000554</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_9a4410b3-f53c-468a-aef9-1e9f1ba88748">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_50045e42-87aa-4aa4-b179-99d03a5569df">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329248.8121399991 5528267.658840001 4.925719999999274 329254.11205999926 5528262.99903 3.8000000000029104 329253.52796000056 5528272.956 3.8000000000029104 329248.8121399991 5528267.658840001 4.925719999999274</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_d4f2198a-dd18-4fe2-a1f3-33f47393cb22">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_6138b267-e734-4830-98f8-a79fc4d38da4">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329246.16602000035 5528272.533020001 0 329246.16602000035 5528272.533020001 3.8000000000029104 329253.52796000056 5528272.956 3.8000000000029104 329253.52796000056 5528272.956 0 329246.16602000035 5528272.533020001 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_3d62148d-9d75-455f-86aa-1c0877942853">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_7e4a20ee-4581-4e9a-a661-3e80c79ae226">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329241.7199700009 5528276.307010001 0 329241.7199700009 5528276.307010001 3.8000000000029104 329246.16602000035 5528272.533020001 3.8000000000029104 329246.16602000035 5528272.533020001 0 329241.7199700009 5528276.307010001 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_b59d0530-9980-46ae-8452-e0a07cfdf84d">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_770546ef-e544-4d39-8747-e5c6c88d5725">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329237.9890100006 5528272.159 0 329237.9890100006 5528272.159 3.8000000000029104 329241.7199700009 5528276.307010001 3.8000000000029104 329241.7199700009 5528276.307010001 0 329237.9890100006 5528272.159 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_c0bd57d9-a02c-40d5-b467-3fd57478e93b">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_0f22b07c-8bd5-43d1-8904-c96a5a0456ce">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329233.3360600006 5528276.213989999 0 329233.3360600006 5528276.213989999 3.8000000000029104 329237.9890100006 5528272.159 3.8000000000029104 329237.9890100006 5528272.159 0 329233.3360600006 5528276.213989999 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_2ff7cfd9-a3d1-4c76-b30e-501cc012b663">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_b6219259-c948-487a-96dc-25f9ce257974">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329229.15295 5528271.14002 0 329229.15295 5528271.14002 3.8000000000029104 329233.3360600006 5528276.213989999 3.8000000000029104 329233.3360600006 5528276.213989999 0 329229.15295 5528271.14002 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_4bcf78ac-c688-40f8-86ca-19bd790a6647">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_d806c8f3-93e1-4155-ab28-743fed870f6b">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329229.30395000055 5528269.304020001 0 329229.30395000055 5528269.304020001 3.8000000000029104 329229.15295 5528271.14002 3.8000000000029104 329229.15295 5528271.14002 0 329229.30395000055 5528269.304020001 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_5677b3e5-abef-4bc0-87a3-3366fc38e6f9">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_da660fbf-9aea-4895-8d9c-cf5fab95862e">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329242.40003000014 5528257.71503 0 329242.40003000014 5528257.71503 3.8000000000029104 329229.30395000055 5528269.304020001 3.8000000000029104 329229.30395000055 5528269.304020001 0 329242.40003000014 5528257.71503 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_e32a4a70-ad52-4f92-a7e4-bcaeb38ff7c9">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_6315337c-3919-423e-9e46-35fc5f005b7d">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329247.3289800007 5528262.52503 0 329247.3289800007 5528262.52503 3.8000000000029104 329242.40003000014 5528257.71503 3.8000000000029104 329242.40003000014 5528257.71503 0 329247.3289800007 5528262.52503 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_b1442311-0705-4bec-a28d-a81db9bd2f5d">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_6bed5c5e-9ee9-4b3a-bfbc-fac54c0f2090">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329254.11205999926 5528262.99903 0 329254.11205999926 5528262.99903 3.8000000000029104 329247.3289800007 5528262.52503 3.8000000000029104 329247.3289800007 5528262.52503 0 329254.11205999926 5528262.99903 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_63185eaf-4f7b-481b-b912-193cfcb4316a">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_8f4f6388-d576-4ded-925a-fd01d43e3c11">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329253.52796000056 5528272.956 0 329253.52796000056 5528272.956 3.8000000000029104 329254.11205999926 5528262.99903 3.8000000000029104 329254.11205999926 5528262.99903 0 329253.52796000056 5528272.956 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:GroundSurface gml:id="UUID_e348daa3-75bc-44c5-b203-aca0902b4034">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_ad685374-7888-41cf-8464-48c037230174">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329253.52796000056 5528272.956 0 329254.11205999926 5528262.99903 0 329247.3289800007 5528262.52503 0 329242.40003000014 5528257.71503 0 329229.30395000055 5528269.304020001 0 329229.15295 5528271.14002 0 329233.3360600006 5528276.213989999 0 329237.9890100006 5528272.159 0 329241.7199700009 5528276.307010001 0 329246.16602000035 5528272.533020001 0 329253.52796000056 5528272.956 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:GroundSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_1b3328ee-ecdb-45a9-b6f3-e36247f4929e">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_1b440294-d10f-49e2-9c65-78aa0a57a389">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329246.16602000035 5528272.533020001 3.8000000000029104 329244.33748999983 5528267.074109999 4.999100000000908 329245.1323099993 5528267.42457 4.930840000000899 329248.8121399991 5528267.658840001 4.925719999999274 329253.52796000056 5528272.956 3.8000000000029104 329246.16602000035 5528272.533020001 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_02a78c5a-3d35-4491-9801-64aa42addf7e">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_4d4017ed-3a71-43c7-a79c-04acd9f86433">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329241.7199700009 5528276.307010001 3.8000000000029104 329242.3462899998 5528267.00502 5.30000000000291 329244.33748999983 5528267.074109999 4.999100000000908 329246.16602000035 5528272.533020001 3.8000000000029104 329241.7199700009 5528276.307010001 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_f550a210-6813-4f8a-b826-7f7965b50a4a">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_b7c0600b-0c3b-4b8c-8f5f-11d8f774966e">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329237.9890100006 5528272.159 3.8000000000029104 329238.32637000084 5528266.609999999 4.6887600000045495 329242.1777599994 5528266.829500001 5.298219999996945 329242.3462899998 5528267.00502 5.30000000000291 329241.7199700009 5528276.307010001 3.8000000000029104 329237.9890100006 5528272.159 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_8d65b4c5-fa18-4cee-81c9-45229588115e">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_18f19ab4-f128-41a0-ab05-34d91ad061b9">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329233.3360600006 5528276.213989999 3.8000000000029104 329233.80010999925 5528270.5848900005 4.683640000002924 329238.32637000084 5528266.609999999 4.6887600000045495 329237.9890100006 5528272.159 3.8000000000029104 329233.3360600006 5528276.213989999 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_46e8afe5-fd30-4c7a-88ae-a7ee5b2d2af6">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_e5b962d8-6186-4e78-ae08-fc0c00484e8c">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329229.15295 5528271.14002 3.8000000000029104 329231.5010599997 5528270.404139999 4.311470000000554 329233.80010999925 5528270.5848900005 4.683640000002924 329233.3360600006 5528276.213989999 3.8000000000029104 329229.15295 5528271.14002 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_c535c900-8077-46d6-a267-d3e9f3c34254">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_eebbc322-bf68-4c56-a826-392b617db97c">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329242.40003000014 5528257.71503 3.8000000000029104 329242.1777599994 5528266.829500001 5.298219999996945 329238.32637000084 5528266.609999999 4.6887600000045495 329233.80010999925 5528270.5848900005 4.683640000002924 329231.5010599997 5528270.404139999 4.311470000000554 329229.30395000055 5528269.304020001 3.8000000000029104 329242.40003000014 5528257.71503 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_d6d9c32d-cd29-490e-accc-3ac5decbb289">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_16a00d48-90a4-4cd1-94e6-0654a5b9b1d2">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329247.3289800007 5528262.52503 3.8000000000029104 329245.1323099993 5528267.42457 4.930840000000899 329244.33748999983 5528267.074109999 4.999100000000908 329242.3462899998 5528267.00502 5.30000000000291 329242.1777599994 5528266.829500001 5.298219999996945 329242.40003000014 5528257.71503 3.8000000000029104 329247.3289800007 5528262.52503 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_d97b1be8-8be7-4a5c-9f4d-3159853b054e">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_4832dea6-f237-45ec-a711-ce1fc27b7e3b">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329254.11205999926 5528262.99903 3.8000000000029104 329248.8121399991 5528267.658840001 4.925719999999274 329245.1323099993 5528267.42457 4.930840000000899 329247.3289800007 5528262.52503 3.8000000000029104 329254.11205999926 5528262.99903 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
</bldg:Building>
|
||||||
|
</core:cityObjectMember>
|
||||||
|
</core:CityModel>
|
409
tests/tests_data/one_building_in_kelowna_alkis.gml
Normal file
409
tests/tests_data/one_building_in_kelowna_alkis.gml
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<core:CityModel xmlns:brid="http://www.opengis.net/citygml/bridge/2.0" xmlns:tran="http://www.opengis.net/citygml/transportation/2.0" xmlns:frn="http://www.opengis.net/citygml/cityfurniture/2.0" xmlns:wtr="http://www.opengis.net/citygml/waterbody/2.0" xmlns:sch="http://www.ascc.net/xml/schematron" xmlns:veg="http://www.opengis.net/citygml/vegetation/2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:tun="http://www.opengis.net/citygml/tunnel/2.0" xmlns:tex="http://www.opengis.net/citygml/texturedsurface/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:dem="http://www.opengis.net/citygml/relief/2.0" xmlns:app="http://www.opengis.net/citygml/appearance/2.0" xmlns:luse="http://www.opengis.net/citygml/landuse/2.0" xmlns:xAL="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:smil20lang="http://www.w3.org/2001/SMIL20/Language" xmlns:pbase="http://www.opengis.net/citygml/profiles/base/2.0" xmlns:smil20="http://www.w3.org/2001/SMIL20/" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:core="http://www.opengis.net/citygml/2.0" xmlns:grp="http://www.opengis.net/citygml/cityobjectgroup/2.0">
|
||||||
|
<gml:boundedBy>
|
||||||
|
<gml:Envelope srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:lowerCorner>326011.03601000085 5526048.416990001 -1.6000000000058208</gml:lowerCorner>
|
||||||
|
<gml:upperCorner>329466.6600299999 5529018.72205 9.80000000000291</gml:upperCorner>
|
||||||
|
</gml:Envelope>
|
||||||
|
</gml:boundedBy>
|
||||||
|
<core:cityObjectMember>
|
||||||
|
<bldg:Building gml:id="BLD109438">
|
||||||
|
<gen:doubleAttribute name="gross_floor_area">
|
||||||
|
<gen:value>291</gen:value>
|
||||||
|
</gen:doubleAttribute>
|
||||||
|
<gen:stringAttribute name="gross_floor_raea_unit">
|
||||||
|
<gen:value>m2</gen:value>
|
||||||
|
</gen:stringAttribute>
|
||||||
|
<bldg:yearOfConstruction>1996</bldg:yearOfConstruction>
|
||||||
|
<bldg:function>residential</bldg:function>
|
||||||
|
<bldg:measuredHeight>5.3</bldg:measuredHeight>
|
||||||
|
<bldg:storeysAboveGround>1</bldg:storeysAboveGround>
|
||||||
|
<bldg:lod2Solid>
|
||||||
|
<gml:Solid srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:CompositeSurface>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_854e7876-bcb7-43f5-9f4b-7c55803cf04f"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_50045e42-87aa-4aa4-b179-99d03a5569df"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_6138b267-e734-4830-98f8-a79fc4d38da4"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_7e4a20ee-4581-4e9a-a661-3e80c79ae226"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_770546ef-e544-4d39-8747-e5c6c88d5725"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_0f22b07c-8bd5-43d1-8904-c96a5a0456ce"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_b6219259-c948-487a-96dc-25f9ce257974"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_d806c8f3-93e1-4155-ab28-743fed870f6b"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_da660fbf-9aea-4895-8d9c-cf5fab95862e"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_6315337c-3919-423e-9e46-35fc5f005b7d"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_6bed5c5e-9ee9-4b3a-bfbc-fac54c0f2090"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_8f4f6388-d576-4ded-925a-fd01d43e3c11"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_ad685374-7888-41cf-8464-48c037230174"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_1b440294-d10f-49e2-9c65-78aa0a57a389"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_4d4017ed-3a71-43c7-a79c-04acd9f86433"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_b7c0600b-0c3b-4b8c-8f5f-11d8f774966e"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_18f19ab4-f128-41a0-ab05-34d91ad061b9"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_e5b962d8-6186-4e78-ae08-fc0c00484e8c"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_eebbc322-bf68-4c56-a826-392b617db97c"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_16a00d48-90a4-4cd1-94e6-0654a5b9b1d2"/>
|
||||||
|
<gml:surfaceMember xlink:href="#UUID_4832dea6-f237-45ec-a711-ce1fc27b7e3b"/>
|
||||||
|
</gml:CompositeSurface>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Solid>
|
||||||
|
</bldg:lod2Solid>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_2e3a196c-b5b1-4ee4-af82-329ced61e624">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_854e7876-bcb7-43f5-9f4b-7c55803cf04f">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329231.5010599997 5528270.404139999 4.311470000000554 329229.15295 5528271.14002 3.8000000000029104 329229.30395000055 5528269.304020001 3.8000000000029104 329231.5010599997 5528270.404139999 4.311470000000554</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_9a4410b3-f53c-468a-aef9-1e9f1ba88748">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_50045e42-87aa-4aa4-b179-99d03a5569df">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329248.8121399991 5528267.658840001 4.925719999999274 329254.11205999926 5528262.99903 3.8000000000029104 329253.52796000056 5528272.956 3.8000000000029104 329248.8121399991 5528267.658840001 4.925719999999274</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_d4f2198a-dd18-4fe2-a1f3-33f47393cb22">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_6138b267-e734-4830-98f8-a79fc4d38da4">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329246.16602000035 5528272.533020001 0 329246.16602000035 5528272.533020001 3.8000000000029104 329253.52796000056 5528272.956 3.8000000000029104 329253.52796000056 5528272.956 0 329246.16602000035 5528272.533020001 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_3d62148d-9d75-455f-86aa-1c0877942853">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_7e4a20ee-4581-4e9a-a661-3e80c79ae226">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329241.7199700009 5528276.307010001 0 329241.7199700009 5528276.307010001 3.8000000000029104 329246.16602000035 5528272.533020001 3.8000000000029104 329246.16602000035 5528272.533020001 0 329241.7199700009 5528276.307010001 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_b59d0530-9980-46ae-8452-e0a07cfdf84d">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_770546ef-e544-4d39-8747-e5c6c88d5725">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329237.9890100006 5528272.159 0 329237.9890100006 5528272.159 3.8000000000029104 329241.7199700009 5528276.307010001 3.8000000000029104 329241.7199700009 5528276.307010001 0 329237.9890100006 5528272.159 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_c0bd57d9-a02c-40d5-b467-3fd57478e93b">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_0f22b07c-8bd5-43d1-8904-c96a5a0456ce">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329233.3360600006 5528276.213989999 0 329233.3360600006 5528276.213989999 3.8000000000029104 329237.9890100006 5528272.159 3.8000000000029104 329237.9890100006 5528272.159 0 329233.3360600006 5528276.213989999 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_2ff7cfd9-a3d1-4c76-b30e-501cc012b663">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_b6219259-c948-487a-96dc-25f9ce257974">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329229.15295 5528271.14002 0 329229.15295 5528271.14002 3.8000000000029104 329233.3360600006 5528276.213989999 3.8000000000029104 329233.3360600006 5528276.213989999 0 329229.15295 5528271.14002 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_4bcf78ac-c688-40f8-86ca-19bd790a6647">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_d806c8f3-93e1-4155-ab28-743fed870f6b">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329229.30395000055 5528269.304020001 0 329229.30395000055 5528269.304020001 3.8000000000029104 329229.15295 5528271.14002 3.8000000000029104 329229.15295 5528271.14002 0 329229.30395000055 5528269.304020001 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_5677b3e5-abef-4bc0-87a3-3366fc38e6f9">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_da660fbf-9aea-4895-8d9c-cf5fab95862e">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329242.40003000014 5528257.71503 0 329242.40003000014 5528257.71503 3.8000000000029104 329229.30395000055 5528269.304020001 3.8000000000029104 329229.30395000055 5528269.304020001 0 329242.40003000014 5528257.71503 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_e32a4a70-ad52-4f92-a7e4-bcaeb38ff7c9">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_6315337c-3919-423e-9e46-35fc5f005b7d">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329247.3289800007 5528262.52503 0 329247.3289800007 5528262.52503 3.8000000000029104 329242.40003000014 5528257.71503 3.8000000000029104 329242.40003000014 5528257.71503 0 329247.3289800007 5528262.52503 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_b1442311-0705-4bec-a28d-a81db9bd2f5d">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_6bed5c5e-9ee9-4b3a-bfbc-fac54c0f2090">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329254.11205999926 5528262.99903 0 329254.11205999926 5528262.99903 3.8000000000029104 329247.3289800007 5528262.52503 3.8000000000029104 329247.3289800007 5528262.52503 0 329254.11205999926 5528262.99903 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:WallSurface gml:id="UUID_63185eaf-4f7b-481b-b912-193cfcb4316a">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_8f4f6388-d576-4ded-925a-fd01d43e3c11">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329253.52796000056 5528272.956 0 329253.52796000056 5528272.956 3.8000000000029104 329254.11205999926 5528262.99903 3.8000000000029104 329254.11205999926 5528262.99903 0 329253.52796000056 5528272.956 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:WallSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:GroundSurface gml:id="UUID_e348daa3-75bc-44c5-b203-aca0902b4034">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_ad685374-7888-41cf-8464-48c037230174">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329253.52796000056 5528272.956 0 329254.11205999926 5528262.99903 0 329247.3289800007 5528262.52503 0 329242.40003000014 5528257.71503 0 329229.30395000055 5528269.304020001 0 329229.15295 5528271.14002 0 329233.3360600006 5528276.213989999 0 329237.9890100006 5528272.159 0 329241.7199700009 5528276.307010001 0 329246.16602000035 5528272.533020001 0 329253.52796000056 5528272.956 0</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:GroundSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_1b3328ee-ecdb-45a9-b6f3-e36247f4929e">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_1b440294-d10f-49e2-9c65-78aa0a57a389">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329246.16602000035 5528272.533020001 3.8000000000029104 329244.33748999983 5528267.074109999 4.999100000000908 329245.1323099993 5528267.42457 4.930840000000899 329248.8121399991 5528267.658840001 4.925719999999274 329253.52796000056 5528272.956 3.8000000000029104 329246.16602000035 5528272.533020001 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_02a78c5a-3d35-4491-9801-64aa42addf7e">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_4d4017ed-3a71-43c7-a79c-04acd9f86433">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329241.7199700009 5528276.307010001 3.8000000000029104 329242.3462899998 5528267.00502 5.30000000000291 329244.33748999983 5528267.074109999 4.999100000000908 329246.16602000035 5528272.533020001 3.8000000000029104 329241.7199700009 5528276.307010001 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_f550a210-6813-4f8a-b826-7f7965b50a4a">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_b7c0600b-0c3b-4b8c-8f5f-11d8f774966e">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329237.9890100006 5528272.159 3.8000000000029104 329238.32637000084 5528266.609999999 4.6887600000045495 329242.1777599994 5528266.829500001 5.298219999996945 329242.3462899998 5528267.00502 5.30000000000291 329241.7199700009 5528276.307010001 3.8000000000029104 329237.9890100006 5528272.159 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_8d65b4c5-fa18-4cee-81c9-45229588115e">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_18f19ab4-f128-41a0-ab05-34d91ad061b9">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329233.3360600006 5528276.213989999 3.8000000000029104 329233.80010999925 5528270.5848900005 4.683640000002924 329238.32637000084 5528266.609999999 4.6887600000045495 329237.9890100006 5528272.159 3.8000000000029104 329233.3360600006 5528276.213989999 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_46e8afe5-fd30-4c7a-88ae-a7ee5b2d2af6">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_e5b962d8-6186-4e78-ae08-fc0c00484e8c">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329229.15295 5528271.14002 3.8000000000029104 329231.5010599997 5528270.404139999 4.311470000000554 329233.80010999925 5528270.5848900005 4.683640000002924 329233.3360600006 5528276.213989999 3.8000000000029104 329229.15295 5528271.14002 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_c535c900-8077-46d6-a267-d3e9f3c34254">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_eebbc322-bf68-4c56-a826-392b617db97c">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329242.40003000014 5528257.71503 3.8000000000029104 329242.1777599994 5528266.829500001 5.298219999996945 329238.32637000084 5528266.609999999 4.6887600000045495 329233.80010999925 5528270.5848900005 4.683640000002924 329231.5010599997 5528270.404139999 4.311470000000554 329229.30395000055 5528269.304020001 3.8000000000029104 329242.40003000014 5528257.71503 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_d6d9c32d-cd29-490e-accc-3ac5decbb289">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_16a00d48-90a4-4cd1-94e6-0654a5b9b1d2">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329247.3289800007 5528262.52503 3.8000000000029104 329245.1323099993 5528267.42457 4.930840000000899 329244.33748999983 5528267.074109999 4.999100000000908 329242.3462899998 5528267.00502 5.30000000000291 329242.1777599994 5528266.829500001 5.298219999996945 329242.40003000014 5528257.71503 3.8000000000029104 329247.3289800007 5528262.52503 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
<bldg:boundedBy>
|
||||||
|
<bldg:RoofSurface gml:id="UUID_d97b1be8-8be7-4a5c-9f4d-3159853b054e">
|
||||||
|
<bldg:lod2MultiSurface>
|
||||||
|
<gml:MultiSurface srsName="EPSG:26911" srsDimension="3">
|
||||||
|
<gml:surfaceMember>
|
||||||
|
<gml:Polygon gml:id="UUID_4832dea6-f237-45ec-a711-ce1fc27b7e3b">
|
||||||
|
<gml:exterior>
|
||||||
|
<gml:LinearRing>
|
||||||
|
<gml:posList>329254.11205999926 5528262.99903 3.8000000000029104 329248.8121399991 5528267.658840001 4.925719999999274 329245.1323099993 5528267.42457 4.930840000000899 329247.3289800007 5528262.52503 3.8000000000029104 329254.11205999926 5528262.99903 3.8000000000029104</gml:posList>
|
||||||
|
</gml:LinearRing>
|
||||||
|
</gml:exterior>
|
||||||
|
</gml:Polygon>
|
||||||
|
</gml:surfaceMember>
|
||||||
|
</gml:MultiSurface>
|
||||||
|
</bldg:lod2MultiSurface>
|
||||||
|
</bldg:RoofSurface>
|
||||||
|
</bldg:boundedBy>
|
||||||
|
</bldg:Building>
|
||||||
|
</core:cityObjectMember>
|
||||||
|
</core:CityModel>
|
8761
tests/tests_data/one_building_in_kelowna_sra_SW.out
Normal file
8761
tests/tests_data/one_building_in_kelowna_sra_SW.out
Normal file
File diff suppressed because it is too large
Load Diff
420
tests/tests_data/pluto_building.gml
Normal file
420
tests/tests_data/pluto_building.gml
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<CityModel>
|
||||||
|
<name>Gowanus 2050 Best Practice Scenario</name>
|
||||||
|
<boundedBy>
|
||||||
|
<Envelope srsName="EPSG:32118" srsDimension="3" xmlns:brid="http://www.opengis.net/citygml/bridge/2.0" xmlns:tran="http://www.opengis.net/citygml/transportation/2.0" xmlns:frn="http://www.opengis.net/citygml/cityfurniture/2.0" xmlns:wtr="http://www.opengis.net/citygml/waterbody/2.0" xmlns:sch="http://www.ascc.net/xml/schematron" xmlns:veg="http://www.opengis.net/citygml/vegetation/2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:tun="http://www.opengis.net/citygml/tunnel/2.0" xmlns:tex="http://www.opengis.net/citygml/texturedsurface/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:dem="http://www.opengis.net/citygml/relief/2.0" xmlns:app="http://www.opengis.net/citygml/appearance/2.0" xmlns:luse="http://www.opengis.net/citygml/landuse/2.0" xmlns:xAL="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:smil20lang="http://www.w3.org/2001/SMIL20/Language" xmlns:pbase="http://www.opengis.net/citygml/profiles/base/2.0" xmlns:smil20="http://www.w3.org/2001/SMIL20/" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:core="http://www.opengis.net/citygml/2.0" xmlns:grp="http://www.opengis.net/citygml/cityobjectgroup/2.0">
|
||||||
|
<lowerCorner>299606.4441129853 55348.37638737355 0</lowerCorner>
|
||||||
|
<upperCorner>301879.9050504853 57594.05119206105 62.04879541695123</upperCorner>
|
||||||
|
</Envelope>
|
||||||
|
</boundedBy>
|
||||||
|
<cityObjectMember>
|
||||||
|
<Building id="GBP__169">
|
||||||
|
<lod1Solid>
|
||||||
|
<Solid srsName="EPSG:32118" srsDimension="3">
|
||||||
|
<exterior>
|
||||||
|
<CompositeSurface>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301010.4314176728 57301.3749225298 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727 301010.4314176728 57301.3749225298 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301014.183859079 57308.78849674855 10.786276534199727 301010.4314176728 57301.3749225298 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301004.1125700165 57288.87345768605 10.786276534199727 300992.0398161103 57285.56779362355 10.786276534199727 301000.3254606415 57281.3758990923 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301024.4275114228 57311.0624225298 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727 301004.5266325165 57271.70548893605 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301000.3254606415 57281.3758990923 10.786276534199727 300997.2820036103 57275.3758990923 10.786276534199727 301004.5266325165 57271.70548893605 10.786276534199727 301000.3254606415 57281.3758990923 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301004.1125700165 57288.87345768605 10.786276534199727 301000.3254606415 57281.3758990923 10.786276534199727 301004.5266325165 57271.70548893605 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301017.183859079 57314.7147662798 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727 301017.183859079 57314.7147662798 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301005.9055387665 57312.9716022173 10.786276534199727 301002.1530973603 57305.55900456105 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727 301005.9055387665 57312.9716022173 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>300995.8337614228 57293.0555865923 10.786276534199727 300992.0398161103 57285.56779362355 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727 300995.8337614228 57293.0555865923 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301014.183859079 57308.78849674855 10.786276534199727 301002.1530973603 57305.55900456105 10.786276534199727 301010.4314176728 57301.3749225298 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301005.9055387665 57312.9716022173 10.786276534199727 301005.9055387665 57312.9716022173 0.0 301002.1530973603 57305.55900456105 10.786276534199727 301005.9055387665 57312.9716022173 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301002.1530973603 57305.55900456105 10.786276534199727 301005.9055387665 57312.9716022173 0.0 301002.1530973603 57305.55900456105 0.0 301002.1530973603 57305.55900456105 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301017.183859079 57314.7147662798 0.0 301024.4275114228 57311.0624225298 0.0 301014.183859079 57308.78849674855 0.0 301017.183859079 57314.7147662798 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301005.9055387665 57312.9716022173 0.0 301014.183859079 57308.78849674855 0.0 301002.1530973603 57305.55900456105 0.0 301005.9055387665 57312.9716022173 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>300995.8337614228 57293.0555865923 0.0 301004.1125700165 57288.87345768605 0.0 300992.0398161103 57285.56779362355 0.0 300995.8337614228 57293.0555865923 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301014.183859079 57308.78849674855 0.0 301010.4314176728 57301.3749225298 0.0 301002.1530973603 57305.55900456105 0.0 301014.183859079 57308.78849674855 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301010.4314176728 57301.3749225298 0.0 301024.4275114228 57311.0624225298 0.0 301004.1125700165 57288.87345768605 0.0 301010.4314176728 57301.3749225298 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301014.183859079 57308.78849674855 0.0 301024.4275114228 57311.0624225298 0.0 301010.4314176728 57301.3749225298 0.0 301014.183859079 57308.78849674855 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301024.4275114228 57311.0624225298 0.0 301004.5266325165 57271.70548893605 0.0 301004.1125700165 57288.87345768605 0.0 301024.4275114228 57311.0624225298 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301004.1125700165 57288.87345768605 0.0 301000.3254606415 57281.3758990923 0.0 300992.0398161103 57285.56779362355 0.0 301004.1125700165 57288.87345768605 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301000.3254606415 57281.3758990923 0.0 301004.5266325165 57271.70548893605 0.0 300997.2820036103 57275.3758990923 0.0 301000.3254606415 57281.3758990923 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301004.1125700165 57288.87345768605 0.0 301004.5266325165 57271.70548893605 0.0 301000.3254606415 57281.3758990923 0.0 301004.1125700165 57288.87345768605 0.0</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301014.183859079 57308.78849674855 10.786276534199727 301014.183859079 57308.78849674855 0.0 301005.9055387665 57312.9716022173 10.786276534199727 301014.183859079 57308.78849674855 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301005.9055387665 57312.9716022173 10.786276534199727 301014.183859079 57308.78849674855 0.0 301005.9055387665 57312.9716022173 0.0 301005.9055387665 57312.9716022173 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301017.183859079 57314.7147662798 10.786276534199727 301017.183859079 57314.7147662798 0.0 301014.183859079 57308.78849674855 10.786276534199727 301017.183859079 57314.7147662798 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301014.183859079 57308.78849674855 10.786276534199727 301017.183859079 57314.7147662798 0.0 301014.183859079 57308.78849674855 0.0 301014.183859079 57308.78849674855 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301002.1530973603 57305.55900456105 10.786276534199727 301002.1530973603 57305.55900456105 0.0 301010.4314176728 57301.3749225298 10.786276534199727 301002.1530973603 57305.55900456105 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301010.4314176728 57301.3749225298 10.786276534199727 301002.1530973603 57305.55900456105 0.0 301010.4314176728 57301.3749225298 0.0 301010.4314176728 57301.3749225298 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301024.4275114228 57311.0624225298 10.786276534199727 301024.4275114228 57311.0624225298 0.0 301017.183859079 57314.7147662798 10.786276534199727 301024.4275114228 57311.0624225298 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301017.183859079 57314.7147662798 10.786276534199727 301024.4275114228 57311.0624225298 0.0 301017.183859079 57314.7147662798 0.0 301017.183859079 57314.7147662798 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301004.5266325165 57271.70548893605 10.786276534199727 301004.5266325165 57271.70548893605 0.0 301024.4275114228 57311.0624225298 10.786276534199727 301004.5266325165 57271.70548893605 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301024.4275114228 57311.0624225298 10.786276534199727 301004.5266325165 57271.70548893605 0.0 301024.4275114228 57311.0624225298 0.0 301024.4275114228 57311.0624225298 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>300997.2820036103 57275.3758990923 10.786276534199727 300997.2820036103 57275.3758990923 0.0 301004.5266325165 57271.70548893605 10.786276534199727 300997.2820036103 57275.3758990923 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301004.5266325165 57271.70548893605 10.786276534199727 300997.2820036103 57275.3758990923 0.0 301004.5266325165 57271.70548893605 0.0 301004.5266325165 57271.70548893605 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301010.4314176728 57301.3749225298 10.786276534199727 301010.4314176728 57301.3749225298 0.0 301004.1125700165 57288.87345768605 10.786276534199727 301010.4314176728 57301.3749225298 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301004.1125700165 57288.87345768605 10.786276534199727 301010.4314176728 57301.3749225298 0.0 301004.1125700165 57288.87345768605 0.0 301004.1125700165 57288.87345768605 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301004.1125700165 57288.87345768605 10.786276534199727 301004.1125700165 57288.87345768605 0.0 300995.8337614228 57293.0555865923 10.786276534199727 301004.1125700165 57288.87345768605 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>300995.8337614228 57293.0555865923 10.786276534199727 301004.1125700165 57288.87345768605 0.0 300995.8337614228 57293.0555865923 0.0 300995.8337614228 57293.0555865923 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301000.3254606415 57281.3758990923 10.786276534199727 301000.3254606415 57281.3758990923 0.0 300997.2820036103 57275.3758990923 10.786276534199727 301000.3254606415 57281.3758990923 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>300997.2820036103 57275.3758990923 10.786276534199727 301000.3254606415 57281.3758990923 0.0 300997.2820036103 57275.3758990923 0.0 300997.2820036103 57275.3758990923 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>300995.8337614228 57293.0555865923 10.786276534199727 300995.8337614228 57293.0555865923 0.0 300992.0398161103 57285.56779362355 10.786276534199727 300995.8337614228 57293.0555865923 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>300992.0398161103 57285.56779362355 10.786276534199727 300995.8337614228 57293.0555865923 0.0 300992.0398161103 57285.56779362355 0.0 300992.0398161103 57285.56779362355 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>300992.0398161103 57285.56779362355 10.786276534199727 300992.0398161103 57285.56779362355 0.0 301000.3254606415 57281.3758990923 10.786276534199727 300992.0398161103 57285.56779362355 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
<surfaceMember>
|
||||||
|
<Polygon>
|
||||||
|
<exterior>
|
||||||
|
<LinearRing>
|
||||||
|
<posList>301000.3254606415 57281.3758990923 10.786276534199727 300992.0398161103 57285.56779362355 0.0 301000.3254606415 57281.3758990923 0.0 301000.3254606415 57281.3758990923 10.786276534199727</posList>
|
||||||
|
</LinearRing>
|
||||||
|
</exterior>
|
||||||
|
</Polygon>
|
||||||
|
</surfaceMember>
|
||||||
|
</CompositeSurface>
|
||||||
|
</exterior>
|
||||||
|
</Solid>
|
||||||
|
</lod1Solid>
|
||||||
|
<yearOfConstruction>1965</yearOfConstruction>
|
||||||
|
<function>W4</function>
|
||||||
|
</Building>
|
||||||
|
</cityObjectMember>
|
||||||
|
</CityModel>
|
1
tests/tests_data/test.geojson
Normal file
1
tests/tests_data/test.geojson
Normal file
File diff suppressed because one or more lines are too long
14
tests/tests_data/w2w_user_output.csv
Normal file
14
tests/tests_data/w2w_user_output.csv
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
,Monthly HP Electricity Demand (kWh),Monthly Fuel Consumption of Auxiliary Heater (m3)
|
||||||
|
Jan,1031544.62,24276356.0
|
||||||
|
Feb,874352.562,19785768.0
|
||||||
|
Mar,691775.25,117312.656
|
||||||
|
Apr,280416.469,-0.0
|
||||||
|
May,0.0,40314676.0
|
||||||
|
Jun,0.0,5447721.0
|
||||||
|
Jul,0.0,1187115.88
|
||||||
|
Aug,0.0,1961530.88
|
||||||
|
Sept,0.0,20623850.0
|
||||||
|
Oct,191220.531,-0.0
|
||||||
|
Nov,423974.062,-0.0
|
||||||
|
Dec,848334.875,6793204.5
|
||||||
|
Total,4341618.369,120507534.91600001
|
|
3
tests/tests_outputs/.gitignore
vendored
Normal file
3
tests/tests_outputs/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Except this file
|
||||||
|
*
|
||||||
|
!.gitignore
|
Loading…
Reference in New Issue
Block a user