Merge pull request 'retrofit_db_changes' (#38) from retrofit_db_changes into main

Reviewed-on: https://nextgenerations-cities.encs.concordia.ca/gitea/CERC/hub/pulls/38
This commit is contained in:
Guille Gutierrez 2023-08-02 14:26:14 -04:00
commit 0f982df32e
20 changed files with 266 additions and 290 deletions

View File

@ -34,7 +34,7 @@ class Archetype:
Get name Get name
:return: string :return: string
""" """
return f'{self._name}_lod{self._lod}' return self._name
@property @property
def systems(self) -> List[System]: def systems(self) -> List[System]:

View File

@ -55,7 +55,7 @@ class System:
Get name Get name
:return: string :return: string
""" """
return f'{self._name}_lod{self._lod}' return self._name
@property @property
def demand_types(self): def demand_types(self):

View File

@ -388,6 +388,42 @@ class Building(CityObject):
""" """
self._domestic_hot_water_heat_demand = value self._domestic_hot_water_heat_demand = value
@property
def lighting_peak_load(self) -> Union[None, dict]:
"""
Get lighting peak load in W
:return: dict{[float]}
"""
results = {}
peak_lighting = 0
for thermal_zone in self.thermal_zones:
lighting = thermal_zone.lighting
for schedule in lighting.schedules:
peak = max(schedule.values) * lighting.density * thermal_zone.total_floor_area
if peak > peak_lighting:
peak_lighting = peak
results[cte.MONTH] = [peak for _ in range(0, 12)]
results[cte.YEAR] = [peak]
return results
@property
def appliances_peak_load(self) -> Union[None, dict]:
"""
Get appliances peak load in W
:return: dict{[float]}
"""
results = {}
peak_appliances = 0
for thermal_zone in self.thermal_zones:
appliances = thermal_zone.appliances
for schedule in appliances.schedules:
peak = max(schedule.values) * appliances.density * thermal_zone.total_floor_area
if peak > peak_appliances:
peak_appliances = peak
results[cte.MONTH] = [peak for _ in range(0, 12)]
results[cte.YEAR] = [peak]
return results
@property @property
def heating_peak_load(self) -> Union[None, dict]: def heating_peak_load(self) -> Union[None, dict]:
""" """

View File

@ -0,0 +1,28 @@
"""
Dictionaries module for montreal custom fuel to hub fuel
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
"""
import hub.helpers.constants as cte
class MontrealCustomFuelToHubFuel:
"""
Montreal custom fuel to hub fuel class
"""
def __init__(self):
self._dictionary = {
'gas': cte.GAS,
'electricity': cte.ELECTRICITY,
'renewable': cte.RENEWABLE
}
@property
def dictionary(self) -> dict:
"""
Get the dictionary
:return: {}
"""
return self._dictionary

View File

@ -6,6 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
from hub.helpers.data.hft_function_to_hub_function import HftFunctionToHubFunction from hub.helpers.data.hft_function_to_hub_function import HftFunctionToHubFunction
from hub.helpers.data.montreal_custom_fuel_to_hub_fuel import MontrealCustomFuelToHubFuel
from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction
from hub.helpers.data.eilat_function_to_hub_function import EilatFunctionToHubFunction from hub.helpers.data.eilat_function_to_hub_function import EilatFunctionToHubFunction
from hub.helpers.data.alkis_function_to_hub_function import AlkisFunctionToHubFunction from hub.helpers.data.alkis_function_to_hub_function import AlkisFunctionToHubFunction
@ -141,3 +142,10 @@ class Dictionaries:
Get Eilat's function to hub function, transformation dictionary Get Eilat's function to hub function, transformation dictionary
""" """
return EilatFunctionToHubFunction().dictionary return EilatFunctionToHubFunction().dictionary
@property
def montreal_custom_fuel_to_hub_fuel(self) -> dict:
"""
Get hub fuel from montreal_custom catalog fuel
"""
return MontrealCustomFuelToHubFuel().dictionary

View File

@ -13,10 +13,7 @@ class ConstructionHelper:
Construction helper Construction helper
""" """
# NREL # NREL
_nrel_standards = {
'ASHRAE Std189': 1,
'ASHRAE 90.1_2004': 2
}
_reference_city_to_nrel_climate_zone = { _reference_city_to_nrel_climate_zone = {
'Miami': 'ASHRAE_2004:1A', 'Miami': 'ASHRAE_2004:1A',
'Houston': 'ASHRAE_2004:2A', 'Houston': 'ASHRAE_2004:2A',
@ -35,17 +32,6 @@ class ConstructionHelper:
'Duluth': 'ASHRAE_2004:7A', 'Duluth': 'ASHRAE_2004:7A',
'Fairbanks': 'ASHRAE_2004:8A' 'Fairbanks': 'ASHRAE_2004:8A'
} }
nrel_window_types = [cte.WINDOW, cte.DOOR, cte.SKYLIGHT]
nrel_construction_types = {
cte.WALL: 'exterior wall',
cte.INTERIOR_WALL: 'interior wall',
cte.GROUND_WALL: 'ground wall',
cte.GROUND: 'exterior slab',
cte.ATTIC_FLOOR: 'attic floor',
cte.INTERIOR_SLAB: 'interior slab',
cte.ROOF: 'roof'
}
_reference_city_to_nrcan_climate_zone = { _reference_city_to_nrcan_climate_zone = {
'Montreal': '6', 'Montreal': '6',

View File

@ -46,7 +46,7 @@ class MontrealCustomEnergySystemParameters:
else: else:
_generic_energy_systems = city.generic_energy_systems _generic_energy_systems = city.generic_energy_systems
for building in city.buildings: for building in city.buildings:
archetype_name = f'{building.energy_systems_archetype_name}_lod1.0' archetype_name = building.energy_systems_archetype_name
try: try:
archetype = self._search_archetypes(montreal_custom_catalog, archetype_name) archetype = self._search_archetypes(montreal_custom_catalog, archetype_name)
except KeyError: except KeyError:
@ -54,9 +54,12 @@ class MontrealCustomEnergySystemParameters:
archetype_name) archetype_name)
continue continue
_energy_systems_connection_table, _generic_energy_systems \ _energy_systems_connection_table, _generic_energy_systems = self._create_generic_systems(
= self._create_generic_systems(archetype, building, archetype,
_energy_systems_connection_table, _generic_energy_systems) building,
_energy_systems_connection_table,
_generic_energy_systems
)
city.energy_systems_connection_table = _energy_systems_connection_table city.energy_systems_connection_table = _energy_systems_connection_table
city.generic_energy_systems = _generic_energy_systems city.generic_energy_systems = _generic_energy_systems
@ -85,10 +88,10 @@ class MontrealCustomEnergySystemParameters:
energy_system.demand_types = _hub_demand_types energy_system.demand_types = _hub_demand_types
_generation_system = GenericGenerationSystem() _generation_system = GenericGenerationSystem()
archetype_generation_equipment = system.generation_system archetype_generation_equipment = system.generation_system
_type = str(system.name).split('_', maxsplit=1)[0] _type = system.name
_generation_system.type = Dictionaries().montreal_system_to_hub_energy_generation_system[ _generation_system.type = Dictionaries().montreal_system_to_hub_energy_generation_system[
_type] _type]
_fuel_type = EnergySystemsHelper().montreal_custom_fuel_to_hub_fuel(archetype_generation_equipment.fuel_type) _fuel_type = Dictionaries().montreal_custom_fuel_to_hub_fuel[archetype_generation_equipment.fuel_type]
_generation_system.fuel_type = _fuel_type _generation_system.fuel_type = _fuel_type
_generation_system.source_types = archetype_generation_equipment.source_types _generation_system.source_types = archetype_generation_equipment.source_types
_generation_system.heat_efficiency = archetype_generation_equipment.heat_efficiency _generation_system.heat_efficiency = archetype_generation_equipment.heat_efficiency

View File

@ -1,144 +0,0 @@
"""
Rhino module parses rhino files and import the geometry into the city model structure
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.capip
"""
import numpy as np
from rhino3dm import *
from rhino3dm._rhino3dm import Extrusion, MeshType, File3dm
from hub.city_model_structure.attributes.point import Point
from hub.city_model_structure.attributes.polygon import Polygon
from hub.city_model_structure.building import Building
from hub.city_model_structure.building_demand.surface import Surface as HubSurface
from hub.city_model_structure.city import City
from hub.helpers.configuration_helper import ConfigurationHelper
from hub.imports.geometry.helpers.geometry_helper import GeometryHelper
class Rhino:
"""
Rhino class
"""
def __init__(self, path):
self._model = File3dm.Read(str(path))
max_float = float(ConfigurationHelper().max_coordinate)
min_float = float(ConfigurationHelper().min_coordinate)
self._min_x = self._min_y = self._min_z = max_float
self._max_x = self._max_y = self._max_z = min_float
@staticmethod
def _in_perimeter(wall, corner):
res = wall.contains_point(Point(corner))
return res
@staticmethod
def _add_hole(solid_polygon, hole):
first = solid_polygon.points[0]
points = first + hole.points + solid_polygon.points
return Polygon(points)
@staticmethod
def _solid_points(coordinates) -> np.ndarray:
solid_points = np.fromstring(coordinates, dtype=float, sep=' ')
solid_points = GeometryHelper.to_points_matrix(solid_points)
result = []
found = False
for row in solid_points:
for row2 in result:
if row[0] == row2[0] and row[1] == row2[1] and row[2] == row2[2]:
found = True
if not found:
result.append(row)
return solid_points
def _corners(self, point):
if point.X < self._min_x:
self._min_x = point.X
if point.Y < self._min_y:
self._min_y = point.Y
if point.Z < self._min_z:
self._min_z = point.Z
if point.X > self._max_x:
self._max_x = point.X
if point.Y > self._max_y:
self._max_y = point.Y
if point.Z > self._max_z:
self._max_z = point.Z
def _add_face(self, face):
hub_surfaces = []
_mesh = face.GetMesh(MeshType.Default)
for i in range(0, len(_mesh.Faces)):
mesh_faces = _mesh.Faces[i]
_points = ''
faces = []
for index in mesh_faces:
if index in faces:
continue
faces.append(index)
self._corners(_mesh.Vertices[index])
_points = _points + f'{_mesh.Vertices[index].X} {_mesh.Vertices[index].Y} {_mesh.Vertices[index].Z} '
polygon_points = Rhino._solid_points(_points.strip())
hub_surfaces.append(HubSurface(Polygon(polygon_points), Polygon(polygon_points)))
return hub_surfaces
@property
def city(self) -> City:
"""
Return a city based in the rhino file
:return: City
"""
buildings = []
city_objects = [] # building and "windows"
windows = []
_prev_name = ''
for obj in self._model.Objects:
name = obj.Attributes.Id
hub_surfaces = []
if isinstance(obj.Geometry, Extrusion):
surface = obj.Geometry
hub_surfaces = hub_surfaces + self._add_face(surface)
else:
for face in obj.Geometry.Faces:
if face is None:
break
hub_surfaces = hub_surfaces + self._add_face(face)
building = Building(name, hub_surfaces, 'unknown', 'unknown', [])
city_objects.append(building)
lower_corner = (self._min_x, self._min_y, self._min_z)
upper_corner = (self._max_x, self._max_y, self._max_z)
city = City(lower_corner, upper_corner, 'EPSG:26918')
for building in city_objects:
if len(building.surfaces) <= 2:
# is not a building but a window!
for surface in building.surfaces:
# add to windows the "hole" with the normal inverted
windows.append(Polygon(surface.perimeter_polygon.inverse))
else:
buildings.append(building)
# todo: this method will be pretty inefficient
for hole in windows:
corner = hole.coordinates[0]
for building in buildings:
for surface in building.surfaces:
plane = surface.perimeter_polygon.plane
# todo: this is a hack for dompark project it should not be done this way windows should be correctly modeled
# if the distance between the wall plane and the window is less than 2m
# and the window Z coordinate it's between the wall Z, it's a window of that wall
if plane.distance_to_point(corner) <= 2:
# check if the window is in the right high.
if surface.upper_corner[2] >= corner[2] >= surface.lower_corner[2]:
if surface.holes_polygons is None:
surface.holes_polygons = []
surface.holes_polygons.append(hole)
for building in buildings:
city.add_city_object(building)
building.level_of_detail.geometry = 3
city.level_of_detail.geometry = 3
return city

View File

@ -12,7 +12,6 @@ from hub.imports.geometry.citygml import CityGml
from hub.imports.geometry.geojson import Geojson from hub.imports.geometry.geojson import Geojson
from hub.imports.geometry.gpandas import GPandas from hub.imports.geometry.gpandas import GPandas
from hub.imports.geometry.obj import Obj from hub.imports.geometry.obj import Obj
from hub.imports.geometry.rhino import Rhino
class GeometryFactory: class GeometryFactory:
@ -80,14 +79,6 @@ class GeometryFactory:
self._function_field, self._function_field,
self._function_to_hub).city self._function_to_hub).city
@property
def _rhino(self) -> City:
"""
Enrich the city by using Rhino information as data source
:return: City
"""
return Rhino(self._path).city
@property @property
def city(self) -> City: def city(self) -> City:
""" """

View File

@ -7,9 +7,9 @@ Project CoderPeter Yefi peteryefi@gmail.com
import json import json
from typing import Dict from typing import Dict
from hub.city_model_structure.city import City
from hub.persistence.repositories.application import Application from hub.persistence.repositories.application import Application
from hub.persistence.repositories.city import City as CityRepository from hub.persistence.repositories.city import City
from hub.persistence.repositories.city_object import CityObject from hub.persistence.repositories.city_object import CityObject
from hub.persistence.repositories.simulation_results import SimulationResults from hub.persistence.repositories.simulation_results import SimulationResults
from hub.persistence.repositories.user import User from hub.persistence.repositories.user import User
@ -22,7 +22,7 @@ class DBControl:
""" """
def __init__(self, db_name, app_env, dotenv_path): def __init__(self, db_name, app_env, dotenv_path):
self._city_repository = CityRepository(db_name=db_name, dotenv_path=dotenv_path, app_env=app_env) 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._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._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._city_object = CityObject(db_name=db_name, app_env=app_env, dotenv_path=dotenv_path)
@ -63,7 +63,23 @@ class DBControl:
:param application_id: Application id :param application_id: Application id
:return: [City] :return: [City]
""" """
return self._city_repository.get_by_user_id_and_application_id(user_id, application_id) 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: def building_info(self, name, city_id) -> CityObject:
""" """
@ -72,51 +88,71 @@ class DBControl:
:param city_id: City ID :param city_id: City ID
:return: CityObject :return: CityObject
""" """
return self._city_object.get_by_name_and_city(name, city_id) return self._city_object.get_by_name_or_alias_and_city(name, city_id)
def results(self, user_id, application_id, cities, result_names=None) -> Dict: 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 Retrieve the simulation results for the given cities from the database
:param user_id: the user id owning the results :param user_id: the user id owning the results
:param application_id: the application id owning the results :param application_id: the application id owning the results
:param cities: dictionary containing the city and building names for 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 :param result_names: if given, filter the results to the selected names
""" """
if result_names is None: if result_names is None:
result_names = [] result_names = []
results = {} results = {}
for city in cities['cities']: for scenario in request_values['scenarios']:
city_name = next(iter(city)) scenario_name = next(iter(scenario))
result_set = self._city_repository.get_by_user_id_application_id_and_name(user_id, application_id, city_name) result_sets = self._city.get_by_user_id_application_id_and_scenario(
if result_set is None: user_id,
application_id,
scenario_name
)
if result_sets is None:
continue continue
city_id = result_set.id for result_set in result_sets:
results[city_name] = [] city_id = result_set[0].id
for building_name in city[city_name]:
if self._city_object.get_by_name_and_city(building_name, city_id) is None:
continue
city_object_id = self._city_object.get_by_name_and_city(building_name, city_id).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 _: results[scenario_name] = []
values = json.loads(value.values) for building_name in scenario[scenario_name]:
values["building"] = building_name _building = self._city_object.get_by_name_or_alias_and_city(building_name, city_id)
results[city_name].append(values) 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 return results
def persist_city(self, city: City, pickle_path, application_id: int, user_id: int): def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int):
""" """
Creates a city into the database Creates a city into the database
:param city: City to be stored :param city: City to be stored
:param pickle_path: Path to save the pickle file :param pickle_path: Path to save the pickle file
:param scenario: Simulation scenario name
:param application_id: Application id owning this city :param application_id: Application id owning this city
:param user_id: User who create the city :param user_id: User who create the city
return identity_id return identity_id
""" """
return self._city_repository.insert(city, pickle_path, application_id, user_id) return self._city.insert(city, pickle_path, scenario, application_id, user_id)
def update_city(self, city_id, city): def update_city(self, city_id, city):
""" """
@ -124,7 +160,7 @@ class DBControl:
:param city_id: the id of the city to update :param city_id: the id of the city to update
:param city: the updated city object :param city: the updated city object
""" """
return self._city_repository.update(city_id, city) return self._city.update(city_id, city)
def persist_application(self, name: str, description: str, application_uuid: str): def persist_application(self, name: str, description: str, application_uuid: str):
""" """
@ -194,7 +230,7 @@ class DBControl:
Deletes a single city from the database Deletes a single city from the database
:param city_id: the id of the city to get :param city_id: the id of the city to get
""" """
self._city_repository.delete(city_id) self._city.delete(city_id)
def delete_results_by_name(self, name, city_id=None, city_object_id=None): def delete_results_by_name(self, name, city_id=None, city_object_id=None):
""" """

View File

@ -20,19 +20,17 @@ class City(Models):
id = Column(Integer, Sequence('city_id_seq'), primary_key=True) id = Column(Integer, Sequence('city_id_seq'), primary_key=True)
pickle_path = Column(String, nullable=False) pickle_path = Column(String, nullable=False)
name = Column(String, nullable=False) name = Column(String, nullable=False)
level_of_detail = Column(Integer, nullable=False) scenario = Column(String, nullable=False)
climate_file = Column(String, nullable=False)
application_id = Column(Integer, ForeignKey('application.id'), nullable=False) application_id = Column(Integer, ForeignKey('application.id'), nullable=False)
user_id = Column(Integer, ForeignKey('user.id'), nullable=True) user_id = Column(Integer, ForeignKey('user.id'), nullable=True)
hub_release = Column(String, nullable=False) hub_release = Column(String, nullable=False)
created = Column(DateTime, default=datetime.datetime.utcnow) created = Column(DateTime, default=datetime.datetime.utcnow)
updated = Column(DateTime, default=datetime.datetime.utcnow) updated = Column(DateTime, default=datetime.datetime.utcnow)
def __init__(self, pickle_path, name, level_of_detail, climate_file, application_id, user_id, hub_release): def __init__(self, pickle_path, name, scenario, application_id, user_id, hub_release):
self.pickle_path = str(pickle_path) self.pickle_path = str(pickle_path)
self.name = name self.name = name
self.level_of_detail = level_of_detail self.scenario = scenario
self.climate_file = climate_file
self.application_id = application_id self.application_id = application_id
self.user_id = user_id self.user_id = user_id
self.hub_release = hub_release self.hub_release = hub_release

View File

@ -33,6 +33,7 @@ class CityObject(Models):
wall_area = Column(Float, nullable=False) wall_area = Column(Float, nullable=False)
windows_area = Column(Float, nullable=False) windows_area = Column(Float, nullable=False)
roof_area = Column(Float, nullable=False) roof_area = Column(Float, nullable=False)
total_pv_area = Column(Float, nullable=False)
system_name = Column(String, nullable=False) system_name = Column(String, nullable=False)
created = Column(DateTime, default=datetime.datetime.utcnow) created = Column(DateTime, default=datetime.datetime.utcnow)
updated = Column(DateTime, default=datetime.datetime.utcnow) updated = Column(DateTime, default=datetime.datetime.utcnow)
@ -48,6 +49,7 @@ class CityObject(Models):
self.volume = building.volume self.volume = building.volume
self.area = building.floor_area self.area = building.floor_area
self.roof_area = sum(roof.solid_polygon.area for roof in building.roofs) 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 storeys = building.storeys_above_ground
if storeys is None: if storeys is None:
storeys = building.max_height / building.average_storey_height storeys = building.max_height / building.average_storey_height

View File

@ -34,11 +34,12 @@ class City(Repository):
cls._instance = super(City, cls).__new__(cls) cls._instance = super(City, cls).__new__(cls)
return cls._instance return cls._instance
def insert(self, city: CityHub, pickle_path, application_id, user_id: int): def insert(self, city: CityHub, pickle_path, scenario, application_id, user_id: int):
""" """
Inserts a city Inserts a city
:param city: The complete city instance :param city: The complete city instance
:param pickle_path: Path to the pickle :param pickle_path: Path to the pickle
:param scenario: Simulation scenario name
:param application_id: Application id owning the instance :param application_id: Application id owning the instance
:param user_id: User id owning the instance :param user_id: User id owning the instance
:return: Identity id :return: Identity id
@ -48,8 +49,7 @@ class City(Repository):
db_city = Model( db_city = Model(
pickle_path, pickle_path,
city.name, city.name,
city.level_of_detail.geometry, scenario,
'None' if city.climate_file is None else str(city.climate_file),
application_id, application_id,
user_id, user_id,
__version__) __version__)
@ -98,21 +98,19 @@ class City(Repository):
logging.error('Error while fetching city %s', err) logging.error('Error while fetching city %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
def get_by_user_id_application_id_and_name(self, user_id, application_id, city_name) -> Model: 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 Fetch city based on the user who created it
:param user_id: the user id :param user_id: the user id
:param application_id: the application id :param application_id: the application id
:param city_name: the city name :param scenario: simulation scenario name
:return: ModelCity :return: [ModelCity]
""" """
try: try:
result_set = self.session.execute(select(Model).where(Model.user_id == user_id, result_set = self.session.execute(select(Model).where(Model.user_id == user_id,
Model.application_id == application_id, Model.application_id == application_id,
Model.name == city_name Model.scenario == scenario
)).first() )).all()
if result_set is not None:
result_set = result_set[0]
return result_set return result_set
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching city by name %s', err) logging.error('Error while fetching city by name %s', err)
@ -133,3 +131,4 @@ class City(Repository):
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching city by name %s', err) logging.error('Error while fetching city by name %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err

View File

@ -7,7 +7,7 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
import datetime import datetime
import logging import logging
from sqlalchemy import select from sqlalchemy import select, or_
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from hub.city_model_structure.building import Building from hub.city_model_structure.building import Building
@ -39,7 +39,7 @@ class CityObject(Repository):
:param building: the city object (only building for now) to be inserted :param building: the city object (only building for now) to be inserted
return Identity id return Identity id
""" """
city_object = self.get_by_name_and_city(building.name, city_id) city_object = self.get_by_name_or_alias_and_city(building.name, city_id)
if city_object is not None: if city_object is not None:
raise SQLAlchemyError(f'A city_object named {building.name} already exists in that city') raise SQLAlchemyError(f'A city_object named {building.name} already exists in that city')
try: try:
@ -96,19 +96,30 @@ class CityObject(Repository):
logging.error('Error while deleting application %s', err) logging.error('Error while deleting application %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
def get_by_name_and_city(self, name, city_id) -> Model: def get_by_name_or_alias_and_city(self, name, city_id) -> Model:
""" """
Fetch a city object based on name and city id Fetch a city object based on name and city id
:param name: city object name :param name: city object name
:param city_id: a city identifier :param city_id: a city identifier
:return: [CityObject] with the provided name belonging to the city with id city_id :return: [CityObject] with the provided name or alias belonging to the city with id city_id
""" """
_city_object = None
try: try:
_city_object = self.session.execute(select(Model).where( # search by name first
Model.name == name, Model.city_id == city_id city_object = self.session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
)).first() if city_object is not None:
return _city_object[0] return city_object[0]
city_objects = self.session.execute(
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
).all()
# name not found, so search by alias instead
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: except SQLAlchemyError as err:
logging.error('Error while fetching city object by name and city: %s', err) logging.error('Error while fetching city object by name and city: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err

View File

@ -12,7 +12,6 @@ openpyxl
networkx networkx
parseidf==1.0.0 parseidf==1.0.0
ply ply
rhino3dm==7.7.0
scipy scipy
PyYAML PyYAML
pyecore==0.12.2 pyecore==0.12.2

View File

@ -4,34 +4,34 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Peter Yefi peteryefi@gmail.com Project Coder Peter Yefi peteryefi@gmail.com
""" """
import distutils.spawn
import glob import glob
import json import json
import logging import logging
import os import os
import subprocess import subprocess
import unittest import unittest
from unittest import TestCase
from pathlib import Path from pathlib import Path
import sqlalchemy.exc from unittest import TestCase
from hub.imports.geometry_factory import GeometryFactory import sqlalchemy.exc
from hub.imports.construction_factory import ConstructionFactory from sqlalchemy import create_engine
from hub.imports.usage_factory import UsageFactory from sqlalchemy.exc import ProgrammingError
from hub.imports.results_factory import ResultFactory
from hub.imports.weather_factory import WeatherFactory import hub.helpers.constants as cte
from hub.imports.energy_systems_factory import EnergySystemsFactory
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
from hub.exports.exports_factory import ExportsFactory 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 hub.persistence.db_control import DBControl from hub.persistence.db_control import DBControl
from hub.persistence.repository import Repository
from sqlalchemy import create_engine
from hub.persistence.models import City, Application, CityObject, SimulationResults from hub.persistence.models import City, Application, CityObject, SimulationResults
from hub.persistence.models import User, UserRoles from hub.persistence.models import User, UserRoles
from hub.helpers.dictionaries import Dictionaries from hub.persistence.repository import Repository
from sqlalchemy.exc import ProgrammingError
import uuid
import hub.helpers.constants as cte
import distutils.spawn
class Control: class Control:
@ -52,7 +52,7 @@ class Control:
self._skip_reason = f'.env file missing at {dotenv_path}' self._skip_reason = f'.env file missing at {dotenv_path}'
return return
dotenv_path = str(dotenv_path) dotenv_path = str(dotenv_path)
repository = Repository(db_name='hub_unittests', app_env='TEST', dotenv_path=dotenv_path) repository = Repository(db_name='montreal_retrofit_test', app_env='TEST', dotenv_path=dotenv_path)
engine = create_engine(repository.configuration.connection_string) engine = create_engine(repository.configuration.connection_string)
try: try:
# delete test database if it exists # delete test database if it exists
@ -71,11 +71,16 @@ class Control:
CityObject.__table__.create(bind=repository.engine, checkfirst=True) CityObject.__table__.create(bind=repository.engine, checkfirst=True)
SimulationResults.__table__.create(bind=repository.engine, checkfirst=True) SimulationResults.__table__.create(bind=repository.engine, checkfirst=True)
city_file = "tests_data/FZK_Haus_LoD_2.gml" city_file = Path('tests_data/test.geojson').resolve()
output_path = Path('tests_outputs/').resolve() output_path = Path('tests_outputs/').resolve()
self._city = GeometryFactory('citygml', self._city = GeometryFactory('geojson',
city_file, city_file,
function_to_hub=Dictionaries().alkis_function_to_hub_function).city 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() ConstructionFactory('nrcan', self._city).enrich()
UsageFactory('nrcan', self._city).enrich() UsageFactory('nrcan', self._city).enrich()
WeatherFactory('epw', self._city).enrich() WeatherFactory('epw', self._city).enrich()
@ -84,7 +89,6 @@ class Control:
subprocess.run([self.sra, sra_file], stdout=subprocess.DEVNULL) subprocess.run([self.sra, sra_file], stdout=subprocess.DEVNULL)
ResultFactory('sra', self._city, output_path).enrich() ResultFactory('sra', self._city, output_path).enrich()
for building in self._city.buildings: for building in self._city.buildings:
building.energy_systems_archetype_name = 'system 1 gas pv' building.energy_systems_archetype_name = 'system 1 gas pv'
EnergySystemsFactory('montreal_custom', self._city).enrich() EnergySystemsFactory('montreal_custom', self._city).enrich()
@ -99,9 +103,17 @@ class Control:
app_env='TEST', app_env='TEST',
dotenv_path=dotenv_path) dotenv_path=dotenv_path)
self._application_uuid = str(uuid.uuid4()) self._application_uuid = '60b7fc1b-f389-4254-9ffd-22a4cf32c7a3'
self._application_id = self._database.persist_application('test', 'test application', self.application_uuid) self._application_id = 1
self._user_id = self._database.create_user('Admin', self._application_id, 'Admin@123', UserRoles.Admin) self._user_id = 1
self._application_id = self._database.persist_application(
'City_layers',
'City layers test user',
self.application_uuid
)
self._user_id = self._database.create_user('city_layers', self._application_id, 'city_layers', UserRoles.Admin)
self._pickle_path = 'tests_data/pickle_path.bz2' self._pickle_path = 'tests_data/pickle_path.bz2'
@property @property
@ -167,6 +179,7 @@ TestDBFactory
city_id = control.database.persist_city( city_id = control.database.persist_city(
control.city, control.city,
control.pickle_path, control.pickle_path,
control.city.name,
control.application_id, control.application_id,
control.user_id) control.user_id)
control.database.delete_city(city_id) control.database.delete_city(city_id)
@ -176,6 +189,7 @@ TestDBFactory
def test_get_update_city(self): def test_get_update_city(self):
city_id = control.database.persist_city(control.city, city_id = control.database.persist_city(control.city,
control.pickle_path, control.pickle_path,
control.city.name,
control.application_id, control.application_id,
control.user_id) control.user_id)
control.city.name = "Ottawa" control.city.name = "Ottawa"
@ -194,6 +208,7 @@ TestDBFactory
def test_save_results(self): def test_save_results(self):
city_id = control.database.persist_city(control.city, city_id = control.database.persist_city(control.city,
control.pickle_path, control.pickle_path,
'current status',
control.application_id, control.application_id,
control.user_id) control.user_id)
city_objects_id = [] city_objects_id = []
@ -206,6 +221,10 @@ TestDBFactory
yearly_cooling_peak_load = building.cooling_peak_load[cte.YEAR] yearly_cooling_peak_load = building.cooling_peak_load[cte.YEAR]
monthly_heating_peak_load = building.heating_peak_load[cte.MONTH] monthly_heating_peak_load = building.heating_peak_load[cte.MONTH]
yearly_heating_peak_load = building.heating_peak_load[cte.YEAR] 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][cte.INSEL_MEB] monthly_cooling_demand = building.cooling_demand[cte.MONTH][cte.INSEL_MEB]
yearly_cooling_demand = building.cooling_demand[cte.YEAR][cte.INSEL_MEB] yearly_cooling_demand = building.cooling_demand[cte.YEAR][cte.INSEL_MEB]
monthly_heating_demand = building.heating_demand[cte.MONTH][cte.INSEL_MEB] monthly_heating_demand = building.heating_demand[cte.MONTH][cte.INSEL_MEB]
@ -222,50 +241,59 @@ TestDBFactory
yearly_cooling_consumption = building.cooling_consumption[cte.YEAR] yearly_cooling_consumption = building.cooling_consumption[cte.YEAR]
monthly_domestic_hot_water_consumption = building.domestic_hot_water_consumption[cte.MONTH] monthly_domestic_hot_water_consumption = building.domestic_hot_water_consumption[cte.MONTH]
yearly_domestic_hot_water_consumption = building._domestic_hot_water_consumption[cte.YEAR] yearly_domestic_hot_water_consumption = building._domestic_hot_water_consumption[cte.YEAR]
monthly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[cte.MONTH] monthly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[
yearly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[cte.YEAR] cte.MONTH]
yearly_distribution_systems_electrical_consumption = building.distribution_systems_electrical_consumption[
cte.YEAR]
monthly_on_site_electrical_production = building.onsite_electrical_production[cte.MONTH] monthly_on_site_electrical_production = building.onsite_electrical_production[cte.MONTH]
yearly_on_site_electrical_production = building.onsite_electrical_production[cte.YEAR] yearly_on_site_electrical_production = building.onsite_electrical_production[cte.YEAR]
results = json.dumps({cte.INSEL_MEB: [ results = json.dumps({cte.INSEL_MEB: [
{'monthly_cooling_peak_load': monthly_cooling_peak_load}, {'monthly_cooling_peak_load': monthly_cooling_peak_load},
{'yearly_cooling_peak_load': yearly_cooling_peak_load}, {'yearly_cooling_peak_load': yearly_cooling_peak_load},
{'monthly_heating_peak_load': monthly_heating_peak_load}, {'monthly_heating_peak_load': monthly_heating_peak_load},
{'yearly_heating_peak_load': yearly_heating_peak_load}, {'yearly_heating_peak_load': yearly_heating_peak_load},
{'monthly_cooling_demand': monthly_cooling_demand.tolist()}, {'monthly_lighting_peak_load': monthly_lighting_peak_load},
{'yearly_cooling_demand': yearly_cooling_demand.tolist()}, {'yearly_lighting_peak_load': yearly_lighting_peak_load},
{'monthly_heating_demand': monthly_heating_demand.tolist()}, {'monthly_appliances_peak_load': monthly_appliances_peak_load},
{'yearly_heating_demand': yearly_heating_demand.tolist()}, {'yearly_appliances_peak_load': yearly_appliances_peak_load},
{'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand.tolist()}, {'monthly_cooling_demand': monthly_cooling_demand.tolist()},
{'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand.tolist()}, {'yearly_cooling_demand': yearly_cooling_demand.tolist()},
{'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand.tolist()}, {'monthly_heating_demand': monthly_heating_demand.tolist()},
{'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand.tolist()}, {'yearly_heating_demand': yearly_heating_demand.tolist()},
{'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand.tolist()}, {'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand.tolist()},
{'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand.tolist()}, {'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand.tolist()},
{'monthly_heating_consumption': monthly_heating_consumption}, {'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand.tolist()},
{'yearly_heating_consumption': yearly_heating_consumption}, {'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand.tolist()},
{'monthly_cooling_consumption': monthly_cooling_consumption}, {'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand.tolist()},
{'yearly_cooling_consumption': yearly_cooling_consumption}, {'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand.tolist()},
{'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption}, {'monthly_heating_consumption': monthly_heating_consumption},
{'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption}, {'yearly_heating_consumption': yearly_heating_consumption},
{'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption}, {'monthly_cooling_consumption': monthly_cooling_consumption},
{'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption}, {'yearly_cooling_consumption': yearly_cooling_consumption},
{'monthly_on_site_electrical_production': monthly_on_site_electrical_production}, {'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption},
{'yearly_on_site_electrical_production': yearly_on_site_electrical_production} {'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 db_building_id = _building.id
city_objects_id.append(db_building_id) city_objects_id.append(db_building_id)
control.database.add_simulation_results( control.database.add_simulation_results(
cte.INSEL_MEB, cte.INSEL_MEB,
results, city_object_id=db_building_id) results, city_object_id=db_building_id)
self.assertEqual(1, len(city_objects_id), 'wrong number of results') self.assertEqual(17, len(city_objects_id), 'wrong number of results')
self.assertIsNotNone(city_objects_id[0], 'city_object_id is None') self.assertIsNotNone(city_objects_id[0], 'city_object_id is None')
"""
for _id in city_objects_id: for _id in city_objects_id:
control.database.delete_results_by_name('insel meb', city_object_id=_id) control.database.delete_results_by_name('insel meb', city_object_id=_id)
control.database.delete_city(city_id) control.database.delete_city(city_id)
@classmethod @classmethod
@unittest.skipIf(control.skip_test, control.skip_reason) @unittest.skipIf(control.skip_test, control.skip_reason)
def tearDownClass(cls): def tearDownClass(cls):
control.database.delete_application(control.application_uuid) control.database.delete_application(control.application_uuid)
control.database.delete_user(control.user_id) control.database.delete_user(control.user_id)
"""

View File

@ -110,15 +110,6 @@ class TestGeometryFactory(TestCase):
self._check_surfaces(building) self._check_surfaces(building)
city = ConstructionFactory('nrel', city).enrich() city = ConstructionFactory('nrel', city).enrich()
def test_import_rhino(self):
"""
Test rhino import
"""
file = 'dompark.3dm'
city = self._get_city(file, 'rhino')
self.assertIsNotNone(city, 'city is none')
self.assertTrue(len(city.buildings) == 36)
def test_import_obj(self): def test_import_obj(self):
""" """
Test obj import Test obj import

View File

@ -69,6 +69,10 @@ class TestResultsImport(TestCase):
self.assertIsNotNone(building.cooling_demand[cte.MONTH][cte.INSEL_MEB]) self.assertIsNotNone(building.cooling_demand[cte.MONTH][cte.INSEL_MEB])
self.assertIsNotNone(building.heating_demand[cte.YEAR][cte.INSEL_MEB]) self.assertIsNotNone(building.heating_demand[cte.YEAR][cte.INSEL_MEB])
self.assertIsNotNone(building.cooling_demand[cte.YEAR][cte.INSEL_MEB]) self.assertIsNotNone(building.cooling_demand[cte.YEAR][cte.INSEL_MEB])
self.assertIsNotNone(building.lighting_peak_load[cte.MONTH])
self.assertIsNotNone(building.lighting_peak_load[cte.YEAR])
self.assertIsNotNone(building.appliances_peak_load[cte.MONTH])
self.assertIsNotNone(building.appliances_peak_load[cte.YEAR])
def test_peak_loads(self): def test_peak_loads(self):
# todo: this is not technically a import # todo: this is not technically a import

Binary file not shown.

Binary file not shown.