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:
commit
0f982df32e
|
@ -34,7 +34,7 @@ class Archetype:
|
|||
Get name
|
||||
:return: string
|
||||
"""
|
||||
return f'{self._name}_lod{self._lod}'
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def systems(self) -> List[System]:
|
||||
|
|
|
@ -55,7 +55,7 @@ class System:
|
|||
Get name
|
||||
:return: string
|
||||
"""
|
||||
return f'{self._name}_lod{self._lod}'
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def demand_types(self):
|
||||
|
|
|
@ -388,6 +388,42 @@ class Building(CityObject):
|
|||
"""
|
||||
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
|
||||
def heating_peak_load(self) -> Union[None, dict]:
|
||||
"""
|
||||
|
|
28
hub/helpers/data/montreal_custom_fuel_to_hub_fuel.py
Normal file
28
hub/helpers/data/montreal_custom_fuel_to_hub_fuel.py
Normal 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
|
|
@ -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.montreal_custom_fuel_to_hub_fuel import MontrealCustomFuelToHubFuel
|
||||
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.alkis_function_to_hub_function import AlkisFunctionToHubFunction
|
||||
|
@ -141,3 +142,10 @@ class Dictionaries:
|
|||
Get Eilat's function to hub function, transformation 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
|
||||
|
|
|
@ -13,10 +13,7 @@ class ConstructionHelper:
|
|||
Construction helper
|
||||
"""
|
||||
# NREL
|
||||
_nrel_standards = {
|
||||
'ASHRAE Std189': 1,
|
||||
'ASHRAE 90.1_2004': 2
|
||||
}
|
||||
|
||||
_reference_city_to_nrel_climate_zone = {
|
||||
'Miami': 'ASHRAE_2004:1A',
|
||||
'Houston': 'ASHRAE_2004:2A',
|
||||
|
@ -35,17 +32,6 @@ class ConstructionHelper:
|
|||
'Duluth': 'ASHRAE_2004:7A',
|
||||
'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 = {
|
||||
'Montreal': '6',
|
||||
|
|
|
@ -46,7 +46,7 @@ class MontrealCustomEnergySystemParameters:
|
|||
else:
|
||||
_generic_energy_systems = city.generic_energy_systems
|
||||
for building in city.buildings:
|
||||
archetype_name = f'{building.energy_systems_archetype_name}_lod1.0'
|
||||
archetype_name = building.energy_systems_archetype_name
|
||||
try:
|
||||
archetype = self._search_archetypes(montreal_custom_catalog, archetype_name)
|
||||
except KeyError:
|
||||
|
@ -54,9 +54,12 @@ class MontrealCustomEnergySystemParameters:
|
|||
archetype_name)
|
||||
continue
|
||||
|
||||
_energy_systems_connection_table, _generic_energy_systems \
|
||||
= self._create_generic_systems(archetype, building,
|
||||
_energy_systems_connection_table, _generic_energy_systems)
|
||||
_energy_systems_connection_table, _generic_energy_systems = self._create_generic_systems(
|
||||
archetype,
|
||||
building,
|
||||
_energy_systems_connection_table,
|
||||
_generic_energy_systems
|
||||
)
|
||||
city.energy_systems_connection_table = _energy_systems_connection_table
|
||||
city.generic_energy_systems = _generic_energy_systems
|
||||
|
||||
|
@ -85,10 +88,10 @@ class MontrealCustomEnergySystemParameters:
|
|||
energy_system.demand_types = _hub_demand_types
|
||||
_generation_system = GenericGenerationSystem()
|
||||
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[
|
||||
_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.source_types = archetype_generation_equipment.source_types
|
||||
_generation_system.heat_efficiency = archetype_generation_equipment.heat_efficiency
|
||||
|
|
|
@ -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
|
|
@ -12,7 +12,6 @@ from hub.imports.geometry.citygml import CityGml
|
|||
from hub.imports.geometry.geojson import Geojson
|
||||
from hub.imports.geometry.gpandas import GPandas
|
||||
from hub.imports.geometry.obj import Obj
|
||||
from hub.imports.geometry.rhino import Rhino
|
||||
|
||||
|
||||
class GeometryFactory:
|
||||
|
@ -80,14 +79,6 @@ class GeometryFactory:
|
|||
self._function_field,
|
||||
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
|
||||
def city(self) -> City:
|
||||
"""
|
||||
|
|
|
@ -7,9 +7,9 @@ Project CoderPeter Yefi peteryefi@gmail.com
|
|||
import json
|
||||
from typing import Dict
|
||||
|
||||
from hub.city_model_structure.city import City
|
||||
|
||||
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.simulation_results import SimulationResults
|
||||
from hub.persistence.repositories.user import User
|
||||
|
@ -22,7 +22,7 @@ class DBControl:
|
|||
"""
|
||||
|
||||
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._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)
|
||||
|
@ -63,7 +63,23 @@ class DBControl:
|
|||
:param application_id: Application id
|
||||
: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:
|
||||
"""
|
||||
|
@ -72,51 +88,71 @@ class DBControl:
|
|||
:param city_id: City ID
|
||||
: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
|
||||
:param user_id: the user 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
|
||||
"""
|
||||
if result_names is None:
|
||||
result_names = []
|
||||
results = {}
|
||||
for city in cities['cities']:
|
||||
city_name = next(iter(city))
|
||||
result_set = self._city_repository.get_by_user_id_application_id_and_name(user_id, application_id, city_name)
|
||||
if result_set is None:
|
||||
for scenario in request_values['scenarios']:
|
||||
scenario_name = next(iter(scenario))
|
||||
result_sets = self._city.get_by_user_id_application_id_and_scenario(
|
||||
user_id,
|
||||
application_id,
|
||||
scenario_name
|
||||
)
|
||||
if result_sets is None:
|
||||
continue
|
||||
city_id = result_set.id
|
||||
results[city_name] = []
|
||||
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 result_set in result_sets:
|
||||
city_id = result_set[0].id
|
||||
|
||||
for value in _:
|
||||
values = json.loads(value.values)
|
||||
values["building"] = building_name
|
||||
results[city_name].append(values)
|
||||
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, 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
|
||||
: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_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):
|
||||
"""
|
||||
|
@ -124,7 +160,7 @@ class DBControl:
|
|||
:param city_id: the id of the city to update
|
||||
: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):
|
||||
"""
|
||||
|
@ -194,7 +230,7 @@ class DBControl:
|
|||
Deletes a single city from the database
|
||||
: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):
|
||||
"""
|
||||
|
|
|
@ -20,19 +20,17 @@ class City(Models):
|
|||
id = Column(Integer, Sequence('city_id_seq'), primary_key=True)
|
||||
pickle_path = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
level_of_detail = Column(Integer, nullable=False)
|
||||
climate_file = Column(String, nullable=False)
|
||||
scenario = Column(String, nullable=False)
|
||||
application_id = Column(Integer, ForeignKey('application.id'), nullable=False)
|
||||
user_id = Column(Integer, ForeignKey('user.id'), 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, 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.name = name
|
||||
self.level_of_detail = level_of_detail
|
||||
self.climate_file = climate_file
|
||||
self.scenario = scenario
|
||||
self.application_id = application_id
|
||||
self.user_id = user_id
|
||||
self.hub_release = hub_release
|
||||
|
|
|
@ -33,6 +33,7 @@ class CityObject(Models):
|
|||
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)
|
||||
|
@ -48,6 +49,7 @@ class CityObject(Models):
|
|||
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
|
||||
if storeys is None:
|
||||
storeys = building.max_height / building.average_storey_height
|
||||
|
|
|
@ -34,11 +34,12 @@ class City(Repository):
|
|||
cls._instance = super(City, cls).__new__(cls)
|
||||
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
|
||||
: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
|
||||
|
@ -48,8 +49,7 @@ class City(Repository):
|
|||
db_city = Model(
|
||||
pickle_path,
|
||||
city.name,
|
||||
city.level_of_detail.geometry,
|
||||
'None' if city.climate_file is None else str(city.climate_file),
|
||||
scenario,
|
||||
application_id,
|
||||
user_id,
|
||||
__version__)
|
||||
|
@ -98,21 +98,19 @@ class City(Repository):
|
|||
logging.error('Error while fetching city %s', 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
|
||||
:param user_id: the user id
|
||||
:param application_id: the application id
|
||||
:param city_name: the city name
|
||||
:return: ModelCity
|
||||
:param scenario: simulation scenario name
|
||||
:return: [ModelCity]
|
||||
"""
|
||||
try:
|
||||
result_set = self.session.execute(select(Model).where(Model.user_id == user_id,
|
||||
Model.application_id == application_id,
|
||||
Model.name == city_name
|
||||
)).first()
|
||||
if result_set is not None:
|
||||
result_set = result_set[0]
|
||||
Model.scenario == scenario
|
||||
)).all()
|
||||
return result_set
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching city by name %s', err)
|
||||
|
@ -133,3 +131,4 @@ class City(Repository):
|
|||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching city by name %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
|||
import datetime
|
||||
import logging
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import select, or_
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
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
|
||||
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:
|
||||
raise SQLAlchemyError(f'A city_object named {building.name} already exists in that city')
|
||||
try:
|
||||
|
@ -96,19 +96,30 @@ class CityObject(Repository):
|
|||
logging.error('Error while deleting application %s', 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
|
||||
:param name: city object name
|
||||
: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:
|
||||
_city_object = self.session.execute(select(Model).where(
|
||||
Model.name == name, Model.city_id == city_id
|
||||
)).first()
|
||||
return _city_object[0]
|
||||
# search by name first
|
||||
city_object = self.session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
|
||||
if city_object is not None:
|
||||
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:
|
||||
logging.error('Error while fetching city object by name and city: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
|
|
|
@ -12,7 +12,6 @@ openpyxl
|
|||
networkx
|
||||
parseidf==1.0.0
|
||||
ply
|
||||
rhino3dm==7.7.0
|
||||
scipy
|
||||
PyYAML
|
||||
pyecore==0.12.2
|
||||
|
|
|
@ -4,34 +4,34 @@ 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 unittest import TestCase
|
||||
from pathlib import Path
|
||||
import sqlalchemy.exc
|
||||
from unittest import TestCase
|
||||
|
||||
from hub.imports.geometry_factory import GeometryFactory
|
||||
from hub.imports.construction_factory import ConstructionFactory
|
||||
from hub.imports.usage_factory import UsageFactory
|
||||
from hub.imports.results_factory import ResultFactory
|
||||
from hub.imports.weather_factory import WeatherFactory
|
||||
from hub.imports.energy_systems_factory import EnergySystemsFactory
|
||||
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 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 User, UserRoles
|
||||
from hub.helpers.dictionaries import Dictionaries
|
||||
from sqlalchemy.exc import ProgrammingError
|
||||
import uuid
|
||||
import hub.helpers.constants as cte
|
||||
import distutils.spawn
|
||||
from hub.persistence.repository import Repository
|
||||
|
||||
|
||||
class Control:
|
||||
|
@ -52,7 +52,7 @@ class Control:
|
|||
self._skip_reason = f'.env file missing at {dotenv_path}'
|
||||
return
|
||||
dotenv_path = str(dotenv_path)
|
||||
repository = Repository(db_name='hub_unittests', app_env='TEST', dotenv_path=dotenv_path)
|
||||
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
|
||||
|
@ -71,11 +71,16 @@ class Control:
|
|||
CityObject.__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()
|
||||
self._city = GeometryFactory('citygml',
|
||||
self._city = GeometryFactory('geojson',
|
||||
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()
|
||||
UsageFactory('nrcan', self._city).enrich()
|
||||
WeatherFactory('epw', self._city).enrich()
|
||||
|
@ -84,7 +89,6 @@ class Control:
|
|||
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()
|
||||
|
@ -99,9 +103,17 @@ class Control:
|
|||
app_env='TEST',
|
||||
dotenv_path=dotenv_path)
|
||||
|
||||
self._application_uuid = str(uuid.uuid4())
|
||||
self._application_id = self._database.persist_application('test', 'test application', self.application_uuid)
|
||||
self._user_id = self._database.create_user('Admin', self._application_id, 'Admin@123', UserRoles.Admin)
|
||||
self._application_uuid = '60b7fc1b-f389-4254-9ffd-22a4cf32c7a3'
|
||||
self._application_id = 1
|
||||
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'
|
||||
|
||||
@property
|
||||
|
@ -167,6 +179,7 @@ TestDBFactory
|
|||
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)
|
||||
|
@ -176,6 +189,7 @@ TestDBFactory
|
|||
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"
|
||||
|
@ -194,6 +208,7 @@ TestDBFactory
|
|||
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 = []
|
||||
|
@ -206,6 +221,10 @@ TestDBFactory
|
|||
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][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]
|
||||
|
@ -222,50 +241,59 @@ TestDBFactory
|
|||
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_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 = building.onsite_electrical_production[cte.MONTH]
|
||||
yearly_on_site_electrical_production = 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_cooling_demand': monthly_cooling_demand.tolist()},
|
||||
{'yearly_cooling_demand': yearly_cooling_demand.tolist()},
|
||||
{'monthly_heating_demand': monthly_heating_demand.tolist()},
|
||||
{'yearly_heating_demand': yearly_heating_demand.tolist()},
|
||||
{'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand.tolist()},
|
||||
{'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand.tolist()},
|
||||
{'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand.tolist()},
|
||||
{'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand.tolist()},
|
||||
{'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand.tolist()},
|
||||
{'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand.tolist()},
|
||||
{'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}
|
||||
]})
|
||||
{'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.tolist()},
|
||||
{'yearly_cooling_demand': yearly_cooling_demand.tolist()},
|
||||
{'monthly_heating_demand': monthly_heating_demand.tolist()},
|
||||
{'yearly_heating_demand': yearly_heating_demand.tolist()},
|
||||
{'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand.tolist()},
|
||||
{'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand.tolist()},
|
||||
{'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand.tolist()},
|
||||
{'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand.tolist()},
|
||||
{'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand.tolist()},
|
||||
{'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand.tolist()},
|
||||
{'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(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')
|
||||
"""
|
||||
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)
|
||||
"""
|
|
@ -110,15 +110,6 @@ class TestGeometryFactory(TestCase):
|
|||
self._check_surfaces(building)
|
||||
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):
|
||||
"""
|
||||
Test obj import
|
||||
|
|
|
@ -69,6 +69,10 @@ class TestResultsImport(TestCase):
|
|||
self.assertIsNotNone(building.cooling_demand[cte.MONTH][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.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):
|
||||
# todo: this is not technically a import
|
||||
|
|
Binary file not shown.
BIN
tests/tests_data/pickle_path.bz2
Normal file
BIN
tests/tests_data/pickle_path.bz2
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user