Rename of imports and modified requirements to match this specific library

This commit is contained in:
Ruben Sanchez 2023-10-26 10:07:46 -04:00
parent a296769361
commit 10fe42bbda
56 changed files with 15866 additions and 1348 deletions

8
.idea/.gitignore vendored Normal file
View 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

View 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>

View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -1,32 +1,47 @@
## TODO
- Figure out what version to put
## Database Persistence ##
The persistence package includes classes to store different class objects in a Postgres database.
### models ###
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
### 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
from the production environment, which is why this is necessary. An example is shown below
```python
from hub.persistence import CityRepo
from cerc_persistence import CityRepo
# instantiate city repo for hub production database
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
### config_db ##
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.
### 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 ###
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
PROD_DB_USER=postgres-database-user
@ -42,6 +57,7 @@ TEST_DB_PORT=database-port
```
### Database Related Unit Test
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*).
NB: You can provide any credentials for the test to connect to postgres, just make sure

View 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

View 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)

View 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')

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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)

View File

@ -0,0 +1,4 @@
"""
CERC Persistence version number
"""
__version__ = '0.1.0'

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -0,0 +1,4 @@
pathlib
python-dotenv
SQLAlchemy
cerc-hub

39
setup.py Normal file
View 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
View 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
View 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)

File diff suppressed because it is too large Load Diff

View 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>

View 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"
}
}
]
}

View 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

View 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"
}
}
]
}

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

File diff suppressed because one or more lines are too long

View 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
1 Monthly HP Electricity Demand (kWh) Monthly Fuel Consumption of Auxiliary Heater (m3)
2 Jan 1031544.62 24276356.0
3 Feb 874352.562 19785768.0
4 Mar 691775.25 117312.656
5 Apr 280416.469 -0.0
6 May 0.0 40314676.0
7 Jun 0.0 5447721.0
8 Jul 0.0 1187115.88
9 Aug 0.0 1961530.88
10 Sept 0.0 20623850.0
11 Oct 191220.531 -0.0
12 Nov 423974.062 -0.0
13 Dec 848334.875 6793204.5
14 Total 4341618.369 120507534.91600001

3
tests/tests_outputs/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Except this file
*
!.gitignore