Merge branch 'main' into reviewing_hub

# Conflicts:
#	hub/imports/construction/eilat_physics_parameters.py
#	hub/imports/construction/nrcan_physics_parameters.py
This commit is contained in:
Pilar Monsalvete 2023-08-02 14:35:31 -04:00
commit 6b89008698
32 changed files with 336 additions and 308 deletions

3
cerc_hub.egg-info/.gitignore vendored Normal file
View File

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

View File

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

View File

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

View File

@ -385,6 +385,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]:
"""

View File

@ -7,10 +7,13 @@ Project Coder Pilar Monsalvete Alvarez de uribarri pilar.monsalvete@concordia.ca
from pathlib import Path
import requests
from hub.exports.building_energy.energy_ade import EnergyAde
from hub.exports.building_energy.idf import Idf
from hub.exports.building_energy.insel.insel_monthly_energy_balance import InselMonthlyEnergyBalance
from hub.helpers.utils import validate_import_export_type
from hub.imports.weather.helpers.weather import Weather as wh
class EnergyBuildingsExportsFactory:
@ -49,8 +52,11 @@ class EnergyBuildingsExportsFactory:
:return: None
"""
idf_data_path = (Path(__file__).parent / './building_energy/idf_files/').resolve()
# todo: create a get epw file function based on the city
weather_path = (Path(__file__).parent / '../data/weather/epw/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve()
url = wh().epw_file(self._city.region_code)
weather_path = (Path(__file__).parent.parent / f'data/weather/epw/{url.rsplit("/", 1)[1]}').resolve()
if not weather_path.exists():
with open(weather_path, 'wb') as epw_file:
epw_file.write(requests.get(url, allow_redirects=True).content)
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
target_buildings=self._target_buildings)

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

View File

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

View File

@ -0,0 +1,29 @@
"""
Energy systems helper
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from hub.helpers import constants as cte
class EnergySystemsHelper:
"""
EnergySystems helper
"""
_montreal_custom_fuel_to_hub_fuel = {
'gas': cte.GAS,
'electricity': cte.ELECTRICITY,
'renewable': cte.RENEWABLE
}
@staticmethod
def montreal_custom_fuel_to_hub_fuel(fuel):
"""
Get hub fuel from montreal_custom catalog fuel
:param fuel: str
:return: str
"""
return EnergySystemsHelper._montreal_custom_fuel_to_hub_fuel[fuel]

View File

@ -19,6 +19,7 @@ from hub.city_model_structure.energy_systems.generation_system import Generation
from hub.city_model_structure.energy_systems.distribution_system import DistributionSystem
from hub.city_model_structure.energy_systems.emission_system import EmissionSystem
from hub.helpers.dictionaries import Dictionaries
from hub.imports.energy_systems.helpers.energy_systems_helper import EnergySystemsHelper
class MontrealCustomEnergySystemParameters:
@ -45,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:
@ -53,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
@ -84,10 +88,11 @@ 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]
_generation_system.fuel_type = 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
_generation_system.cooling_efficiency = archetype_generation_equipment.cooling_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.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:
"""

View File

@ -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,30 +88,49 @@ 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:
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 = self._city_object.get_by_name_and_city(building_name, city_id).id
city_object_id = _building.id
_ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names(
city_id,
city_object_id,
@ -104,19 +139,20 @@ class DBControl:
for value in _:
values = json.loads(value.values)
values["building"] = building_name
results[city_name].append(values)
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):
"""

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
"""
Hub version number
"""
__version__ = '0.1.7.23'
__version__ = '0.1.7.29'

View File

@ -1,6 +1,6 @@
xmltodict
numpy
trimesh[all]==3.12.0
trimesh[all]
pyproj
pandas
requests
@ -12,7 +12,6 @@ openpyxl
networkx
parseidf==1.0.0
ply
rhino3dm==7.7.0
scipy
PyYAML
pyecore==0.12.2

View File

@ -72,6 +72,7 @@ setup(
'hub.imports.construction',
'hub.imports.construction.helpers',
'hub.imports.energy_systems',
'hub.imports.energy_systems.helpers',
'hub.imports.geometry',
'hub.imports.geometry.citygml_classes',
'hub.imports.geometry.helpers',

View File

@ -33,7 +33,7 @@ class TestCityMerge(TestCase):
self._executable = 'sra'
def test_merge(self):
file_path = Path('./tests_data/test.geojson').resolve()
file_path = Path(self._example_path / 'test.geojson').resolve()
full_city = GeometryFactory('geojson', file_path, height_field='citygml_me').city
self.assertEqual(17, len(full_city.buildings), 'Wrong number of buildings')
odd_city = City(full_city.lower_corner, full_city.upper_corner, full_city.srs_name)
@ -54,17 +54,17 @@ class TestCityMerge(TestCase):
def test_merge_with_radiation(self):
sra = distutils.spawn.find_executable('sra')
file_path = Path('./tests_data/test.geojson').resolve()
output_path = Path('./tests_outputs/')
file_path = Path(self._example_path / 'test.geojson').resolve()
full_city = GeometryFactory('geojson', file_path, height_field='citygml_me').city
even_city = City(full_city.lower_corner, full_city.upper_corner, full_city.srs_name)
for building in full_city.buildings:
if int(building.name) % 2 == 0:
even_city.add_city_object(copy.deepcopy(building))
ExportsFactory('sra', full_city, output_path).export()
sra_file = str((output_path / f'{full_city.name}_sra.xml').resolve())
ExportsFactory('sra', full_city, self._output_path).export()
sra_file = str((self._output_path / f'{full_city.name}_sra.xml').resolve())
subprocess.run([sra, sra_file], stdout=subprocess.DEVNULL)
ResultFactory('sra', full_city, output_path).enrich()
ResultFactory('sra', full_city, self._output_path).enrich()
self.assertEqual(17, len(full_city.buildings), 'Wrong number of buildings')
merged_city = full_city.merge(even_city)

View File

@ -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,8 +241,10 @@ 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: [
@ -231,6 +252,10 @@ TestDBFactory
{'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()},
@ -258,14 +283,17 @@ TestDBFactory
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)
"""

View File

@ -4,6 +4,8 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Peter Yefi peteryefi@gmail.com
"""
from pathlib import Path
import pandas as pd
from unittest import TestCase
from hub.imports.geometry_factory import GeometryFactory
@ -38,8 +40,10 @@ class TestEnergySystemsFactory(TestCase):
Test setup
:return: None
"""
city_file = "tests_data/C40_Final.gml"
self._output_path = "tests_data/as_user_output.csv"
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
self._output_path = (Path(__file__).parent / 'tests_outputs').resolve()
city_file = (self._example_path/"C40_Final.gml").resolve()
self._output_path = (self._output_path/"as_user_output.csv").resolve()
self._city = GeometryFactory('citygml', path=city_file).city
EnergySystemsFactory('air_source_hp', self._city).enrich()

View File

@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Peter Yefi peteryefi@gmail.com
"""
from pathlib import Path
from unittest import TestCase
import pandas as pd
@ -24,8 +25,10 @@ class TestEnergySystemsFactory(TestCase):
Test setup
:return: None
"""
city_file = "tests_data/C40_Final.gml"
self._output_path = "tests_data/w2w_user_output.csv"
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
self._output_path = (Path(__file__).parent / 'tests_outputs').resolve()
city_file = (self._example_path / "C40_Final.gml").resolve()
self._output_path = (self._example_path / "w2w_user_output.csv").resolve()
self._city = GeometryFactory('citygml', path=city_file).city
EnergySystemsFactory('water_to_water_hp', self._city).enrich()

View File

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

View File

@ -23,9 +23,10 @@ class GreeneryInIdf(TestCase):
GreeneryInIdf TestCase 1
"""
def test_greenery_in_idf(self):
city_file = "tests_data/one_building_in_kelowna.gml"
output_path = Path('tests_outputs/').resolve()
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
output_path = (Path(__file__).parent / 'tests_outputs').resolve()
city_file = (self._example_path / "one_building_in_kelowna.gml").resolve()
city = GeometryFactory('citygml', path=city_file).city
for building in city.buildings:
building.year_of_construction = 2006

View File

@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Peter Yefi peteryefi@gmail.com
"""
from pathlib import Path
from unittest import TestCase
from hub.imports.geometry_factory import GeometryFactory
from hub.imports.energy_systems_factory import EnergySystemsFactory
@ -38,8 +39,9 @@ class TestHeatPumpResults(TestCase):
Test setup
:return: None
"""
city_file = "tests_data/C40_Final.gml"
self._output_path = "tests_data/as_user_output.csv"
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
self._output_path = (Path(__file__).parent / 'tests_outputs/as_user_output.csv').resolve()
city_file = (self._example_path / "C40_Final.gml").resolve()
self._city = GeometryFactory('citygml', path=city_file).city
EnergySystemsFactory('air_source_hp', self._city).enrich()

View File

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

Binary file not shown.

View File

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