Merge remote-tracking branch 'origin/main' into Stochastic_occupancy_model

# Conflicts:
#	.gitignore
This commit is contained in:
Guille Gutierrez 2023-11-30 13:26:05 +01:00
commit ae784765d3
70 changed files with 1372 additions and 552 deletions

14
.gitignore vendored
View File

@ -1,2 +1,12 @@
# Default ignored files !.gitignore
.idea **/venv/
.idea/
/development_tests/
/data/energy_systems/heat_pumps/*.csv
/data/energy_systems/heat_pumps/*.insel
.DS_Store
**/.env
**/hub/logs/
**/__pycache__/
**/.idea/
cerc_hub.egg-info

View File

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

12
hub/.gitignore vendored
View File

@ -1,12 +0,0 @@
!.gitignore
**/venv/
.idea/
/development_tests/
/data/energy_systems/heat_pumps/*.csv
/data/energy_systems/heat_pumps/*.insel
.DS_Store
**/.env
**/hub/logs/
**/__pycache__/
**/.idea/

50
hub/LINUX_INSTALL.md Normal file
View File

@ -0,0 +1,50 @@
# LINUX_INSTALL
## Prepare your environment
### Install Miniconda
1. Get the link for the latest version of Miniconda from https://docs.conda.io/en/latest/miniconda.html
2. Download the installer using wget
````
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
````
3. Make the installer executable
````
chmod +x ./Miniconda3-latest-Linux-x86_64.sh
````
4. Run the installer
````
./Miniconda3-latest-Linux-x86_64.sh
````
5. Holder enter until you are prompted to accept the license terms. Enter yes.
6. Initialize the conda environment
````
conda init bash
````
7. Source .bashrc
````
source ~/.bashrc
````
8. Create a conda environment for the hub
````
conda create --name hub python=3.9.16
````
### Setup SRA
1. Get the sra binary and libshortwave.so library from Guille or Koa
2. Place the binary and the library into your directory of choice
3. Make a symlink for the binary and place it into /usr/local/bin/sra
````
sudo ln -s ~/sra /usr/local/bin/sra
````
4. Make a symlink for the library and place it into /usr/local/lib/libshortwave.so
````
sudo ln -s ~/libshortwave.so /usr/local/lib/libshortwave.so
````
### Setup INSEL
1. TBD
### Get a Python editor
You are welcome to use the Python editor of your preference. The CERC team generally uses PyCharm to develop the hub.
The latest version of PyCharm can be downloaded from [JetBrains website](https://www.jetbrains.com/pycharm/promo/?source=google&medium=cpc&campaign=14127625109&term=pycharm&content=536947779504&gad=1&gclid=CjwKCAjw0ZiiBhBKEiwA4PT9z2AxPfy39x_RcBqlYxJ6sm_s55T9qvA_sZ8ZfkhIVX6FOD-ySbmzARoCcpQQAvD_BwE).
For setup and installation instructions, please view the "Get a Python Editor"
from the [WINDOWS_INSTALL](https://nextgenerations-cities.encs.concordia.ca/gitea/CERC/hub/src/branch/main/hub/WINDOWS_INSTALL.md)
documentation.

View File

@ -2,16 +2,16 @@
This is an installation guide for Windows, covering all the steps needed to begin developing code for the Urban This is an installation guide for Windows, covering all the steps needed to begin developing code for the Urban
Simulation Platform 'Hub'. At the end of this process you will have installed and configured all the necessary applications, Simulation Platform 'Hub'. At the end of this process you will have installed and configured all the necessary applications,
set up your own project on CERC's Gitlab and created your first python file. set up your own project on CERC's Gitea and created your first python file.
## Prepare your environment ## Prepare your environment
g
To develop any new code for the Urban Simulation Platform you must have the right software applications installed and configured. To develop any new code for the Urban Simulation Platform you must have the right software applications installed and configured.
The Platform is written in python and so the applications you need are: The Platform is written in python and so the applications you need are:
* Miniconda * Miniconda
* SRA Files
* Python Editor * Python Editor
You also need to register a user account with the CERC's code repository on Gitlab and have the necessary permissions for You also need to register a user account with the CERC's code repository on Gitea and have the necessary permissions for
creating new code. For that purpose, please, contact Guillermo (guillermo.gutierrezmorote@concordia.ca) or creating new code. For that purpose, please, contact Guillermo (guillermo.gutierrezmorote@concordia.ca) or
Koa (kekoa.wells@concordia.ca) as soon as possible. Koa (kekoa.wells@concordia.ca) as soon as possible.
@ -47,6 +47,29 @@ _The term '...' is not recognized as the name of a cmdlet, function,..._
To solve it, type 'Set-ExecutionPolicy Unrestricted' as shown in the image. To solve it, type 'Set-ExecutionPolicy Unrestricted' as shown in the image.
### Setup SRA
1. Get the SRA executable and dll files from Guille or Koa
2. Create a folder in "C:\Program Files\" called "sra"
![create_sra](docs/img_windows_install/img_34.png)
3. Copy shortwave_integer.exe and pthreadGC2.dll into the sra folder.
![create_sra](docs/img_windows_install/img_35.png)
4. Add the newly created sra folder to the Path, similar to step 2 from the Miniconda setup above.
![create_sra](docs/img_windows_install/img_36.png)
### Install and setup INSEL
1. Get the INSEL installer from Guille or Koa
2. Run the installer to completion using the default installation path
3. Add the INSEL installation folder to the Path
![create_sra](docs/img_windows_install/img_41.png)
### Get a Python editor ### Get a Python editor
1. You will need a python editor in order to import the existing Hub source code and to write your own python code. 1. You will need a python editor in order to import the existing Hub source code and to write your own python code.
@ -55,7 +78,7 @@ an excellent open-source python editor.
2. Run the installer, and follow the installation instructions for PyCharm, you may change a few options, 2. Run the installer, and follow the installation instructions for PyCharm, you may change a few options,
but the default ones should be fine. but the default ones should be fine.
**NOTE:** If Pycharm asks you to create a Virtual Environment, click **Cancel**. You will do it later using Conda instead. **NOTE:** If PyCharm asks you to create a Virtual Environment, click **Cancel**. You will do it later using Conda instead.
![creating_virtual_environment](docs/img_windows_install/img_31.png) ![creating_virtual_environment](docs/img_windows_install/img_31.png)
@ -70,14 +93,12 @@ You can find it also at **Git->Clone...**
![pycharm get from version control](docs/img_windows_install/img_6.png) ![pycharm get from version control](docs/img_windows_install/img_6.png)
3. Select **Git** as the **Version control**. For the URL use the link to the Hub repository, as seen below. 3. Select **Git** as the **Version control**. Open the [hub repository](https://nextgenerations-cities.encs.concordia.ca/gitea/CERC/hub)
on Gitea and copy the URL from your browser to use as the URL inside PyCharm.
![pycharm get from version control screen](docs/img_windows_install/img_1.png) ![pycharm get from version control screen](docs/img_windows_install/img_1.png)
(You can also copy this URL by going to the Hub repository in [Gitlab](https://rs-loy-gitlab.concordia.ca/Guille/hub.git) ![gitea get https](docs/img_windows_install/img_39.png)
and clicking on the **Copy URL** button, next to **Clone with HTTPS**)
![gitlab get https](docs/img_windows_install/img_17.png)
The Directory to store the Hub source code locally is automatically created for you. Edit this if you prefer it to be stored somewhere else. The Directory to store the Hub source code locally is automatically created for you. Edit this if you prefer it to be stored somewhere else.
@ -152,7 +173,7 @@ _lca_classes_,... And, click on the **Create** button.
3. Click on the **Git** button in the bottom-left corner to pop-up the window showing the Git information. 3. Click on the **Git** button in the bottom-left corner to pop-up the window showing the Git information.
See your new branch has been created under _Local_. See your new branch has been created under _Local_.
4. Now we need to let the CERC Gitlab repository know about this new branch. You do this by right-clicking on 4. Now we need to let the CERC Gitea repository know about this new branch. You do this by right-clicking on
your branch and selecting **Push...** from the drop-down menu. your branch and selecting **Push...** from the drop-down menu.
5. Then click on the **Push** button at the bottom-right of the **Push Commits** window. 5. Then click on the **Push** button at the bottom-right of the **Push Commits** window.
@ -180,33 +201,35 @@ See the picture below.
![pycharm configuration screen](docs/img_windows_install/img_5.png) ![pycharm configuration screen](docs/img_windows_install/img_5.png)
## Set up a new project on Gitlab ## Set up a new project on Gitea
You will need an account before you can access the Gitea. Please contact Guillermo (guillermo.gutierrezmorote@concordia.ca) or
Koa (kekoa.wells@concordia.ca) to request an account.
1. Open a browser and to the [CERC Git](https://rs-loy-gitlab.concordia.ca/). Click on the blue **New project** button. 1. Open a browser and go to the [CERC Gitea](https://nextgenerations-cities.encs.concordia.ca/). Click on the **+** in the top right
and select "New Repository" or press the **+** below the Organization tab.
![git new project screen](docs/img_windows_install/img_14.png) ![git new project screen](docs/img_windows_install/img_37.png)
2. Choose the **Create blank project** option from the three options seen below. 2. Choose the **Create blank project** option from the three options seen below.
3. Type in a name that describes your project: _hp_workflow_, _bus_system_optimization_... 3. Type in a name that describes your project: _hp_workflow_, _bus_system_optimization_...
(remember to follow the CERC naming conventions described in the [Coding Style](PYGUIDE.md)). (remember to follow the CERC naming conventions described in the [Coding Style](PYGUIDE.md)).
Check the option **Initialize repository with a README**, and ideally, check the **Visibility Level** to be **Public**. Ideally, uncheck the option **Make Repository Private**, and check the **Initialize Repository**
Then click on the **Create project** button. Then click on the **Create project** button.
![git give a name](docs/img_windows_install/img_15.png) ![git give a name](docs/img_windows_install/img_38.png)
You should then see a confirmation screen with all the information about your new project. You should then see a confirmation screen with all the information about your new project.
## Get your project into Pycharm ## Get your project into Pycharm
1. Now you can make a clone of this project, within PyCharm. First, copy the URL by clicking on the blue **Clone** button 1. Now you can make a clone of this project, within PyCharm. First, go to the page of your repository on the Gitea and copy the URL.
and then click on the **Copy URL** button, next to the **Clone with HTTPS** link.
2. Switch back to PyCharm and close the Hub project by choosing **File->Close Project**. You will then see the 2. Switch back to PyCharm and close the Hub project by choosing **File->Close Project**. You will then see the
**Welcome To PyCharm** window again. **Welcome To PyCharm** window again.
3. Clone a copy of your Project into PyCharm, following the steps 2-6 of the _GET THE CERC HUB SOURCE CODE_ 3. Clone a copy of your Project into PyCharm, following the steps 2-6 of the _GET THE CERC HUB SOURCE CODE_
section above, but using the URL link that you just copied for your gitlab project. section above, but using the URL link that you just copied for your Gitea project.
4. Select **File->Settings** to open the **Settings** window. From the panel on the left click on 4. Select **File->Settings** to open the **Settings** window. From the panel on the left click on
**Project:<project name> -> Project Structure**. **Project:<project name> -> Project Structure**.
@ -242,5 +265,5 @@ city = GeometryFactory('citygml', path='myfile.gml').city
9. Always remember to push your own project changes as the last thing you do before ending your working day! 9. Always remember to push your own project changes as the last thing you do before ending your working day!
First, commit your changes by clicking on the green check in the top-right corner of Pycharm. Add a comment that explains briefly your changes. First, commit your changes by clicking on the green check in the top-right corner of Pycharm. Add a comment that explains briefly your changes.
Then, pull by clicking on the blue arrow to be sure that there are no conflicts between your version (local) and the remote one (gitlab). Then, pull by clicking on the blue arrow to be sure that there are no conflicts between your version (local) and the remote one (Gitea).
Once the conflicts are solved and the merge in local is done, push the changes by clicking on the green arrow. Once the conflicts are solved and the merge in local is done, push the changes by clicking on the green arrow.

View File

@ -188,7 +188,7 @@ class EilatCatalog(Catalog):
schedules_key = {} schedules_key = {}
for j in range(0, number_usage_types): for j in range(0, number_usage_types):
usage_parameters = _extracted_data.iloc[j] usage_parameters = _extracted_data.iloc[j]
usage_type = usage_parameters[0] usage_type = usage_parameters.iloc[0]
lighting_data[usage_type] = usage_parameters[1:6].values.tolist() lighting_data[usage_type] = usage_parameters[1:6].values.tolist()
plug_loads_data[usage_type] = usage_parameters[8:13].values.tolist() plug_loads_data[usage_type] = usage_parameters[8:13].values.tolist()
occupancy_data[usage_type] = usage_parameters[17:20].values.tolist() occupancy_data[usage_type] = usage_parameters[17:20].values.tolist()

View File

@ -70,6 +70,9 @@ class Building(CityObject):
self._min_x = min(self._min_x, surface.lower_corner[0]) self._min_x = min(self._min_x, surface.lower_corner[0])
self._min_y = min(self._min_y, surface.lower_corner[1]) self._min_y = min(self._min_y, surface.lower_corner[1])
self._min_z = min(self._min_z, surface.lower_corner[2]) self._min_z = min(self._min_z, surface.lower_corner[2])
self._max_x = max(self._max_x, surface.upper_corner[0])
self._max_y = max(self._max_y, surface.upper_corner[1])
self._max_z = max(self._max_z, surface.upper_corner[2])
surface.id = surface_id surface.id = surface_id
if surface.type == cte.GROUND: if surface.type == cte.GROUND:
self._grounds.append(surface) self._grounds.append(surface)
@ -440,8 +443,7 @@ class Building(CityObject):
""" """
results = {} results = {}
if cte.HOUR in self.heating_demand: if cte.HOUR in self.heating_demand:
monthly_values = PeakLoads().\ monthly_values = PeakLoads().peak_loads_from_hourly(self.heating_demand[cte.HOUR])
peak_loads_from_hourly(self.heating_demand[cte.HOUR])
else: else:
monthly_values = PeakLoads(self).heating_peak_loads_from_methodology monthly_values = PeakLoads(self).heating_peak_loads_from_methodology
if monthly_values is None: if monthly_values is None:
@ -682,11 +684,17 @@ class Building(CityObject):
for i, value in enumerate(item): for i, value in enumerate(item):
_working_hours[key][i] = max(_working_hours[key][i], saved_values[i]) _working_hours[key][i] = max(_working_hours[key][i], saved_values[i])
_total_hours = 0 working_hours = {}
for key in _working_hours: values_months = []
hours = sum(_working_hours[key]) for month in cte.WEEK_DAYS_A_MONTH.keys():
_total_hours += hours * cte.WEEK_DAYS_A_YEAR[key] _total_hours_month = 0
return _total_hours for key in _working_hours:
hours = sum(_working_hours[key])
_total_hours_month += hours * cte.WEEK_DAYS_A_MONTH[month][key]
values_months.append(_total_hours_month)
working_hours[cte.MONTH] = values_months
working_hours[cte.YEAR] = sum(working_hours[cte.MONTH])
return working_hours
@property @property
def distribution_systems_electrical_consumption(self): def distribution_systems_electrical_consumption(self):
@ -735,8 +743,13 @@ class Building(CityObject):
for key, item in self._distribution_systems_electrical_consumption.items(): for key, item in self._distribution_systems_electrical_consumption.items():
for i in range(0, len(item)): for i in range(0, len(item)):
self._distribution_systems_electrical_consumption[key][i] += _peak_load * _consumption_fix_flow \ _working_hours_value = _working_hours[key]
* _working_hours if len(item) == 12:
_working_hours_value = _working_hours[key][i]
self._distribution_systems_electrical_consumption[key][i] += (
_peak_load * _consumption_fix_flow * _working_hours_value * cte.WATTS_HOUR_TO_JULES
)
return self._distribution_systems_electrical_consumption return self._distribution_systems_electrical_consumption
def _calculate_consumption(self, consumption_type, demand): def _calculate_consumption(self, consumption_type, demand):
@ -797,3 +810,17 @@ class Building(CityObject):
orientation_losses_factor[_key]['south'])] orientation_losses_factor[_key]['south'])]
self._onsite_electrical_production[_key] = _results self._onsite_electrical_production[_key] = _results
return self._onsite_electrical_production return self._onsite_electrical_production
@property
def lower_corner(self):
"""
Get building lower corner.
"""
return [self._min_x, self._min_y, self._min_z]
@property
def upper_corner(self):
"""
Get building upper corner.
"""
return [self._max_x, self._max_y, self._max_z]

View File

@ -14,6 +14,7 @@ class Construction:
""" """
def __init__(self): def __init__(self):
self._type = None self._type = None
self._name = None
self._layers = None self._layers = None
self._window_ratio = None self._window_ratio = None
self._window_frame_ratio = None self._window_frame_ratio = None
@ -37,6 +38,22 @@ class Construction:
""" """
self._type = value self._type = value
@property
def name(self):
"""
Get construction name
:return: str
"""
return self._name
@name.setter
def name(self, value):
"""
Set construction name
:param value: str
"""
self._name = value
@property @property
def layers(self) -> [Layer]: def layers(self) -> [Layer]:
""" """

View File

@ -130,6 +130,7 @@ class InternalZone:
for hole in surface.holes_polygons: for hole in surface.holes_polygons:
windows_areas.append(hole.area) windows_areas.append(hole.area)
_thermal_boundary = ThermalBoundary(surface, surface.solid_polygon.area, windows_areas) _thermal_boundary = ThermalBoundary(surface, surface.solid_polygon.area, windows_areas)
surface.associated_thermal_boundaries = [_thermal_boundary]
_thermal_boundaries.append(_thermal_boundary) _thermal_boundaries.append(_thermal_boundary)
_number_of_storeys = int(self.volume / self.area / self.thermal_archetype.average_storey_height) _number_of_storeys = int(self.volume / self.area / self.thermal_archetype.average_storey_height)
_thermal_zone = ThermalZone(_thermal_boundaries, self, self.volume, self.area, _number_of_storeys) _thermal_zone = ThermalZone(_thermal_boundaries, self, self.volume, self.area, _number_of_storeys)

View File

@ -16,7 +16,7 @@ class Layer:
def __init__(self): def __init__(self):
self._thickness = None self._thickness = None
self._id = None self._id = None
self._name = None self._material_name = None
self._conductivity = None self._conductivity = None
self._specific_heat = None self._specific_heat = None
self._density = None self._density = None
@ -54,20 +54,20 @@ class Layer:
self._thickness = float(value) self._thickness = float(value)
@property @property
def name(self): def material_name(self):
""" """
Get material name Get material name
:return: str :return: str
""" """
return self._name return self._material_name
@name.setter @material_name.setter
def name(self, value): def material_name(self, value):
""" """
Set material name Set material name
:param value: string :param value: string
""" """
self._name = str(value) self._material_name = str(value)
@property @property
def conductivity(self) -> Union[None, float]: def conductivity(self) -> Union[None, float]:

View File

@ -18,6 +18,7 @@ from hub.city_model_structure.attributes.point import Point
from hub.city_model_structure.greenery.vegetation import Vegetation from hub.city_model_structure.greenery.vegetation import Vegetation
from hub.city_model_structure.building_demand.thermal_boundary import ThermalBoundary from hub.city_model_structure.building_demand.thermal_boundary import ThermalBoundary
import hub.helpers.constants as cte import hub.helpers.constants as cte
from hub.helpers.configuration_helper import ConfigurationHelper
class Surface: class Surface:
@ -154,7 +155,6 @@ class Surface:
if self._inclination is None: if self._inclination is None:
self._inclination = np.arccos(self.perimeter_polygon.normal[2]) self._inclination = np.arccos(self.perimeter_polygon.normal[2])
return self._inclination return self._inclination
@property @property
def type(self): def type(self):
""" """

View File

@ -256,6 +256,19 @@ class ThermalBoundary:
raise TypeError('Constructions layers are not initialized') from TypeError raise TypeError('Constructions layers are not initialized') from TypeError
return self._u_value return self._u_value
@property
def construction_name(self):
"""
Get construction name
:return: str
"""
if self._construction_archetype is not None:
self._construction_name = self._construction_archetype.name
else:
logging.error('Construction name not defined\n')
raise ValueError('Construction name not defined')
return self._construction_name
@u_value.setter @u_value.setter
def u_value(self, value): def u_value(self, value):
""" """

View File

@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
from math import inf
from typing import Union, List from typing import Union, List
from hub.city_model_structure.attributes.schedule import Schedule from hub.city_model_structure.attributes.schedule import Schedule
@ -22,20 +23,16 @@ class ThermalControl:
@staticmethod @staticmethod
def _maximum_value(schedules): def _maximum_value(schedules):
maximum = -1000 maximum = -inf
for schedule in schedules: for schedule in schedules:
for value in schedule.values: maximum = max(maximum, max(schedule.values))
if value > maximum:
maximum = value
return maximum return maximum
@staticmethod @staticmethod
def _minimum_value(schedules): def _minimum_value(schedules):
minimum = 1000 minimum = inf
for schedule in schedules: for schedule in schedules:
for value in schedule.values: minimum = min(minimum, min(schedule.values))
if value < minimum:
minimum = value
return minimum return minimum
@property @property

View File

@ -14,6 +14,7 @@ import math
import pickle import pickle
import sys import sys
import pathlib import pathlib
import os
from pathlib import Path from pathlib import Path
from typing import List, Union from typing import List, Union
@ -101,7 +102,7 @@ class City:
Get city location Get city location
:return: Location :return: Location
""" """
return self._get_location().city return self._get_location()
@property @property
def name(self): def name(self):
@ -113,6 +114,15 @@ class City:
return self._get_location().city return self._get_location().city
return self._name return self._name
@name.setter
def name(self, value):
"""
Set city name
:param value:str
"""
if value is not None:
self._name = str(value)
@property @property
def climate_reference_city(self) -> Union[None, str]: def climate_reference_city(self) -> Union[None, str]:
""" """
@ -275,15 +285,6 @@ class City:
""" """
return self._srs_name return self._srs_name
@name.setter
def name(self, value):
"""
Set city name
:param value:str
"""
if value is not None:
self._name = str(value)
@staticmethod @staticmethod
def load(city_filename) -> City: def load(city_filename) -> City:
""" """
@ -299,6 +300,20 @@ class City:
with open(city_filename, 'rb') as file: with open(city_filename, 'rb') as file:
return pickle.load(file) return pickle.load(file)
@staticmethod
def load_compressed(compressed_city_filename, destination_filename) -> City:
"""
Load a city from compressed_city_filename
:param compressed_city_filename: Compressed pickle as source
:param destination_filename: Pickle file as destination
:return: City
"""
with open(str(compressed_city_filename), 'rb') as source, open(str(destination_filename), 'wb') as destination:
destination.write(bz2.decompress(source.read()))
loaded_city = City.load(destination_filename)
os.unlink(destination_filename)
return loaded_city
def save(self, city_filename): def save(self, city_filename):
""" """
Save a city into the given filename Save a city into the given filename

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -12,6 +12,7 @@ from geomeppy import IDF
import hub.helpers.constants as cte import hub.helpers.constants as cte
from hub.city_model_structure.attributes.schedule import Schedule from hub.city_model_structure.attributes.schedule import Schedule
from hub.city_model_structure.building_demand.thermal_zone import ThermalZone from hub.city_model_structure.building_demand.thermal_zone import ThermalZone
from hub.helpers.configuration_helper import ConfigurationHelper
class Idf: class Idf:
@ -55,7 +56,6 @@ class Idf:
_SIMPLE = 'Simple' _SIMPLE = 'Simple'
idf_surfaces = { idf_surfaces = {
# todo: make an enum for all the surface types
cte.WALL: 'wall', cte.WALL: 'wall',
cte.GROUND: 'floor', cte.GROUND: 'floor',
cte.ROOF: 'roof' cte.ROOF: 'roof'
@ -148,28 +148,28 @@ class Idf:
def _add_material(self, layer): def _add_material(self, layer):
for material in self._idf.idfobjects[self._MATERIAL]: for material in self._idf.idfobjects[self._MATERIAL]:
if material.Name == layer.material.name: if material.Name == layer.material_name:
return return
for material in self._idf.idfobjects[self._MATERIAL_NOMASS]: for material in self._idf.idfobjects[self._MATERIAL_NOMASS]:
if material.Name == layer.material.name: if material.Name == layer.material_name:
return return
if layer.material.no_mass: if layer.no_mass:
self._idf.newidfobject(self._MATERIAL_NOMASS, self._idf.newidfobject(self._MATERIAL_NOMASS,
Name=layer.material.name, Name=layer.material_name,
Roughness=self._ROUGHNESS, Roughness=self._ROUGHNESS,
Thermal_Resistance=layer.material.thermal_resistance Thermal_Resistance=layer.thermal_resistance
) )
else: else:
self._idf.newidfobject(self._MATERIAL, self._idf.newidfobject(self._MATERIAL,
Name=layer.material.name, Name=layer.material_name,
Roughness=self._ROUGHNESS, Roughness=self._ROUGHNESS,
Thickness=layer.thickness, Thickness=layer.thickness,
Conductivity=layer.material.conductivity, Conductivity=layer.conductivity,
Density=layer.material.density, Density=layer.density,
Specific_Heat=layer.material.specific_heat, Specific_Heat=layer.specific_heat,
Thermal_Absorptance=layer.material.thermal_absorptance, Thermal_Absorptance=layer.thermal_absorptance,
Solar_Absorptance=layer.material.solar_absorptance, Solar_Absorptance=layer.solar_absorptance,
Visible_Absorptance=layer.material.visible_absorptance Visible_Absorptance=layer.visible_absorptance
) )
@staticmethod @staticmethod
@ -338,11 +338,11 @@ class Idf:
_kwargs = {'Name': vegetation_name, _kwargs = {'Name': vegetation_name,
'Outside_Layer': thermal_boundary.parent_surface.vegetation.name} 'Outside_Layer': thermal_boundary.parent_surface.vegetation.name}
for i in range(0, len(layers) - 1): for i in range(0, len(layers) - 1):
_kwargs[f'Layer_{i + 2}'] = layers[i].material.name _kwargs[f'Layer_{i + 2}'] = layers[i].material_name
else: else:
_kwargs = {'Name': thermal_boundary.construction_name, 'Outside_Layer': layers[0].material.name} _kwargs = {'Name': thermal_boundary.construction_name, 'Outside_Layer': layers[0].material_name}
for i in range(1, len(layers) - 1): for i in range(1, len(layers) - 1):
_kwargs[f'Layer_{i + 1}'] = layers[i].material.name _kwargs[f'Layer_{i + 1}'] = layers[i].material_name
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs) self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
def _add_window_construction_and_material(self, thermal_opening): def _add_window_construction_and_material(self, thermal_opening):
@ -512,12 +512,12 @@ class Idf:
self._rename_building(self._city.name) self._rename_building(self._city.name)
self._lod = self._city.level_of_detail.geometry self._lod = self._city.level_of_detail.geometry
for building in self._city.buildings: for building in self._city.buildings:
print('building name', building.name)
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
if internal_zone.thermal_zones_from_internal_zones is None: if internal_zone.thermal_zones_from_internal_zones is None:
continue continue
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
for thermal_boundary in thermal_zone.thermal_boundaries: for thermal_boundary in thermal_zone.thermal_boundaries:
self._add_construction(thermal_boundary) self._add_construction(thermal_boundary)
if thermal_boundary.parent_surface.vegetation is not None: if thermal_boundary.parent_surface.vegetation is not None:
self._add_vegetation_material(thermal_boundary.parent_surface.vegetation) self._add_vegetation_material(thermal_boundary.parent_surface.vegetation)
@ -560,7 +560,7 @@ class Idf:
self._add_dhw(thermal_zone, building.name) self._add_dhw(thermal_zone, building.name)
if self._export_type == "Surfaces": if self._export_type == "Surfaces":
if building.name in self._target_buildings or building.name in self._adjacent_buildings: if building.name in self._target_buildings or building.name in self._adjacent_buildings:
if building.internal_zones[0].thermal_zones_from_internal_zones is not None: if building.thermal_zones_from_internal_zones is not None:
self._add_surfaces(building, building.name) self._add_surfaces(building, building.name)
else: else:
self._add_pure_geometry(building, building.name) self._add_pure_geometry(building, building.name)
@ -633,6 +633,8 @@ class Idf:
self._city.lower_corner) self._city.lower_corner)
shading.setcoords(coordinates) shading.setcoords(coordinates)
solar_reflectance = surface.short_wave_reflectance solar_reflectance = surface.short_wave_reflectance
if solar_reflectance is None:
solar_reflectance = ConfigurationHelper().short_wave_reflectance
self._idf.newidfobject(self._SHADING_PROPERTY, self._idf.newidfobject(self._SHADING_PROPERTY,
Shading_Surface_Name=f'{surface.name}', Shading_Surface_Name=f'{surface.name}',
Diffuse_Solar_Reflectance_of_Unglazed_Part_of_Shading_Surface=solar_reflectance, Diffuse_Solar_Reflectance_of_Unglazed_Part_of_Shading_Surface=solar_reflectance,
@ -677,41 +679,40 @@ class Idf:
self._idf.set_wwr(wwr) self._idf.set_wwr(wwr)
def _add_surfaces(self, building, zone_name): def _add_surfaces(self, building, zone_name):
for internal_zone in building.internal_zones: for thermal_zone in building.thermal_zones_from_internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: for boundary in thermal_zone.thermal_boundaries:
for boundary in thermal_zone.thermal_boundaries: idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type] outside_boundary_condition = 'Outdoors'
outside_boundary_condition = 'Outdoors' sun_exposure = 'SunExposed'
sun_exposure = 'SunExposed' wind_exposure = 'WindExposed'
wind_exposure = 'WindExposed' _kwargs = {'Name': f'{boundary.parent_surface.name}',
_kwargs = {'Name': f'{boundary.parent_surface.name}', 'Surface_Type': idf_surface_type,
'Surface_Type': idf_surface_type, 'Zone_Name': zone_name}
'Zone_Name': zone_name} if boundary.parent_surface.type == cte.GROUND:
if boundary.parent_surface.type == cte.GROUND: outside_boundary_condition = 'Ground'
outside_boundary_condition = 'Ground' sun_exposure = 'NoSun'
sun_exposure = 'NoSun' wind_exposure = 'NoWind'
wind_exposure = 'NoWind' if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5:
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5: outside_boundary_condition = 'Surface'
outside_boundary_condition = 'Surface' outside_boundary_condition_object = boundary.parent_surface.name
outside_boundary_condition_object = boundary.parent_surface.name sun_exposure = 'NoSun'
sun_exposure = 'NoSun' wind_exposure = 'NoWind'
wind_exposure = 'NoWind' _kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object _kwargs['Outside_Boundary_Condition'] = outside_boundary_condition
_kwargs['Outside_Boundary_Condition'] = outside_boundary_condition _kwargs['Sun_Exposure'] = sun_exposure
_kwargs['Sun_Exposure'] = sun_exposure _kwargs['Wind_Exposure'] = wind_exposure
_kwargs['Wind_Exposure'] = wind_exposure
if boundary.parent_surface.vegetation is not None: if boundary.parent_surface.vegetation is not None:
construction_name = f'{boundary.construction_name}_{boundary.parent_surface.vegetation.name}' construction_name = f'{boundary.construction_name}_{boundary.parent_surface.vegetation.name}'
else: else:
construction_name = boundary.construction_name construction_name = boundary.construction_name
_kwargs['Construction_Name'] = construction_name _kwargs['Construction_Name'] = construction_name
surface = self._idf.newidfobject(self._SURFACE, **_kwargs) surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates, coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
self._city.lower_corner) self._city.lower_corner)
surface.setcoords(coordinates) surface.setcoords(coordinates)
if self._lod >= 3: if self._lod >= 3:
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:

View File

@ -42,6 +42,14 @@ class InselMonthlyEnergyBalance:
self._insel_files_paths.append(building.name + '.insel') self._insel_files_paths.append(building.name + '.insel')
file_name_out = building.name + '.out' file_name_out = building.name + '.out'
output_path = Path(self._path / file_name_out).resolve() output_path = Path(self._path / file_name_out).resolve()
skip_building = False
for internal_zone in building.internal_zones:
if internal_zone.thermal_archetype is None:
logging.warning('Building %s has missing values. Monthly Energy Balance cannot be processed', building.name)
skip_building = True
break
if skip_building:
continue
if building.thermal_zones_from_internal_zones is None: if building.thermal_zones_from_internal_zones is None:
logging.warning('Building %s has missing values. Monthly Energy Balance cannot be processed', building.name) logging.warning('Building %s has missing values. Monthly Energy Balance cannot be processed', building.name)

View File

@ -7,9 +7,12 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
from pathlib import Path from pathlib import Path
from hub.exports.formats.glb import Glb
from hub.exports.formats.obj import Obj from hub.exports.formats.obj import Obj
from hub.exports.formats.geojson import Geojson
from hub.exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm from hub.exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
from hub.exports.formats.stl import Stl from hub.exports.formats.stl import Stl
from hub.exports.formats.cesiumjs_tileset import CesiumjsTileset
from hub.helpers.utils import validate_import_export_type from hub.helpers.utils import validate_import_export_type
@ -17,7 +20,7 @@ class ExportsFactory:
""" """
Exports factory class Exports factory class
""" """
def __init__(self, handler, city, path, target_buildings=None, adjacent_buildings=None): def __init__(self, handler, city, path, target_buildings=None, adjacent_buildings=None, base_uri=None):
self._city = city self._city = city
self._handler = '_' + handler.lower() self._handler = '_' + handler.lower()
validate_import_export_type(ExportsFactory, handler) validate_import_export_type(ExportsFactory, handler)
@ -26,18 +29,7 @@ class ExportsFactory:
self._path = path self._path = path
self._target_buildings = target_buildings self._target_buildings = target_buildings
self._adjacent_buildings = adjacent_buildings self._adjacent_buildings = adjacent_buildings
self._base_uri = base_uri
@property
def _citygml(self):
"""
Export to citygml
:return: None
"""
raise NotImplementedError
@property
def _collada(self):
raise NotImplementedError
@property @property
def _stl(self): def _stl(self):
@ -61,9 +53,30 @@ class ExportsFactory:
Export the city to Simplified Radiosity Algorithm xml format Export the city to Simplified Radiosity Algorithm xml format
:return: None :return: None
""" """
return SimplifiedRadiosityAlgorithm(self._city, return SimplifiedRadiosityAlgorithm(
(self._path / f'{self._city.name}_sra.xml'), self._city, (self._path / f'{self._city.name}_sra.xml'), target_buildings=self._target_buildings
target_buildings=self._target_buildings) )
@property
def _cesiumjs_tileset(self):
"""
Export the city to a cesiumJs tileset format
:return: None
"""
return CesiumjsTileset(
self._city,
(self._path / f'{self._city.name}.json'),
target_buildings=self._target_buildings,
base_uri=self._base_uri
)
@property
def _glb(self):
return Glb(self._city, self._path, target_buildings=self._target_buildings)
@property
def _geojson(self):
return Geojson(self._city, self._path, target_buildings=self._target_buildings)
def export(self): def export(self):
""" """

View File

@ -0,0 +1,159 @@
"""
export a city into Cesium tileset format
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import json
import math
import pyproj
from pyproj import Transformer
from hub.helpers.geometry_helper import GeometryHelper
class CesiumjsTileset:
def __init__(self, city, file_name, target_buildings=None, base_uri=None):
self._city = city
self._file_name = file_name
self._target_buildings = target_buildings
if base_uri is None:
base_uri = '.'
self._base_uri = base_uri
try:
srs_name = self._city.srs_name
if self._city.srs_name in GeometryHelper.srs_transformations:
srs_name = GeometryHelper.srs_transformations[self._city.srs_name]
input_reference = pyproj.CRS(srs_name) # Projected coordinate system from input data
except pyproj.exceptions.CRSError as err:
raise pyproj.exceptions.CRSError from err
self._to_gps = Transformer.from_crs(input_reference, pyproj.CRS('EPSG:4326'))
city_upper_corner = [
self._city.upper_corner[0] - self._city.lower_corner[0],
self._city.upper_corner[1] - self._city.lower_corner[1],
self._city.upper_corner[2] - self._city.lower_corner[2]
]
city_lower_corner = [0, 0, 0]
self._tile_set = {
'asset': {
'version': '1.1',
"tilesetVersion": "1.2.3"
},
'position': self._to_gps.transform(self._city.lower_corner[0], self._city.lower_corner[1]),
'schema': {
'id': "building",
'classes': {
'building': {
"properties": {
'name': {
'type': 'STRING'
},
'position': {
'type': 'SCALAR',
'array': True,
'componentType': 'FLOAT32'
},
'aliases': {
'type': 'STRING',
'array': True,
},
'volume': {
'type': 'SCALAR',
'componentType': 'FLOAT32'
},
'floor_area': {
'type': 'SCALAR',
'componentType': 'FLOAT32'
},
'max_height': {
'type': 'SCALAR',
'componentType': 'INT32'
},
'year_of_construction': {
'type': 'SCALAR',
'componentType': 'INT32'
},
'function': {
'type': 'STRING'
},
'usages_percentage': {
'type': 'STRING'
}
}
}
}
},
'geometricError': 500,
'root': {
'boundingVolume': {
'box': CesiumjsTileset._box_values(city_upper_corner, city_lower_corner)
},
'geometricError': 70,
'refine': 'ADD',
'children': []
}
}
self._export()
@staticmethod
def _box_values(upper_corner, lower_corner):
x = (upper_corner[0] - lower_corner[0]) / 2
x_center = ((upper_corner[0] - lower_corner[0]) / 2) + lower_corner[0]
y = (upper_corner[1] - lower_corner[1]) / 2
y_center = ((upper_corner[1] - lower_corner[1]) / 2) + lower_corner[1]
z = (upper_corner[2] - lower_corner[2]) / 2
return [x_center, y_center, z, x, 0, 0, 0, y, 0, 0, 0, z]
def _ground_coordinates(self, coordinates):
ground_coordinates = []
for coordinate in coordinates:
ground_coordinates.append(
(coordinate[0] - self._city.lower_corner[0], coordinate[1] - self._city.lower_corner[1])
)
return ground_coordinates
def _export(self):
for building in self._city.buildings:
upper_corner = [-math.inf, -math.inf, 0]
lower_corner = [math.inf, math.inf, 0]
lower_corner_coordinates = lower_corner
for surface in building.grounds: # todo: maybe we should add the terrain?
coordinates = self._ground_coordinates(surface.solid_polygon.coordinates)
lower_corner = [min([c[0] for c in coordinates]), min([c[1] for c in coordinates]), 0]
lower_corner_coordinates = [
min([c[0] for c in surface.solid_polygon.coordinates]),
min([c[1] for c in surface.solid_polygon.coordinates]),
0
]
upper_corner = [max([c[0] for c in coordinates]), max([c[1] for c in coordinates]), building.max_height]
tile = {
'boundingVolume': {
'box': CesiumjsTileset._box_values(upper_corner, lower_corner)
},
'geometricError': 250,
'metadata': {
'class': 'building',
'properties': {
'name': building.name,
'position': self._to_gps.transform(lower_corner_coordinates[0], lower_corner_coordinates[1]),
'aliases': building.aliases,
'volume': building.volume,
'floor_area': building.floor_area,
'max_height': building.max_height,
'year_of_construction': building.year_of_construction,
'function': building.function,
'usages_percentage': building.usages_percentage
}
},
'content': {
'uri': f'{self._base_uri}/{building.name}.glb'
}
}
self._tile_set['root']['children'].append(tile)
with open(self._file_name, 'w') as f:
json.dump(self._tile_set, f, indent=2)

View File

@ -0,0 +1,112 @@
"""
export a city into Geojson format
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import json
from pathlib import Path
import numpy as np
import pyproj
from pyproj import Transformer
from hub.helpers.geometry_helper import GeometryHelper
class Geojson:
"""
Export to geojson format
"""
def __init__(self, city, path, target_buildings):
self._city = city
self._file_path = Path(path / f'{self._city.name}.geojson').resolve()
try:
srs_name = self._city.srs_name
if self._city.srs_name in GeometryHelper.srs_transformations:
srs_name = GeometryHelper.srs_transformations[self._city.srs_name]
input_reference = pyproj.CRS(srs_name) # Projected coordinate system from input data
except pyproj.exceptions.CRSError as err:
raise pyproj.exceptions.CRSError from err
self._to_gps = Transformer.from_crs(input_reference, pyproj.CRS('EPSG:4326'))
if target_buildings is None:
target_buildings = [b.name for b in self._city.buildings]
self._geojson_skeleton = {
'type': 'FeatureCollection',
'features': []
}
self._feature_skeleton = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': []
},
'properties': {}
}
self._export()
def _export(self):
for building in self._city.buildings:
if len(building.grounds) == 1:
ground = building.grounds[0]
feature = self._polygon(ground)
else:
feature = self._multipolygon(building.grounds)
feature['id'] = building.name
feature['properties']['height'] = f'{building.max_height - building.lower_corner[2]}'
feature['properties']['function'] = f'{building.function}'
feature['properties']['year_of_construction'] = f'{building.year_of_construction}'
feature['properties']['aliases'] = building.aliases
feature['properties']['elevation'] = f'{building.lower_corner[2]}'
self._geojson_skeleton['features'].append(feature)
with open(self._file_path, 'w', encoding='utf-8') as f:
json.dump(self._geojson_skeleton, f, indent=2)
def _polygon(self, ground):
feature = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': []
},
'properties': {}
}
ground_coordinates = []
for coordinate in ground.solid_polygon.coordinates:
gps_coordinate = self._to_gps.transform(coordinate[0], coordinate[1])
ground_coordinates.insert(0, [gps_coordinate[1], gps_coordinate[0]])
first_gps_coordinate = self._to_gps.transform(
ground.solid_polygon.coordinates[0][0],
ground.solid_polygon.coordinates[0][1]
)
ground_coordinates.insert(0, [first_gps_coordinate[1], first_gps_coordinate[0]])
feature['geometry']['coordinates'].append(ground_coordinates)
return feature
def _multipolygon(self, grounds):
feature = {
'type': 'Feature',
'geometry': {
'type': 'MultiPolygon',
'coordinates': []
},
'properties': {}
}
polygons = []
for ground in grounds:
ground_coordinates = []
for coordinate in ground.solid_polygon.coordinates:
gps_coordinate = self._to_gps.transform(coordinate[0], coordinate[1])
ground_coordinates.insert(0, [gps_coordinate[1], gps_coordinate[0]])
first_gps_coordinate = self._to_gps.transform(
ground.solid_polygon.coordinates[0][0],
ground.solid_polygon.coordinates[0][1]
)
ground_coordinates.insert(0, [first_gps_coordinate[1], first_gps_coordinate[0]])
polygons.append(ground_coordinates)
feature['geometry']['coordinates'].append(polygons)
return feature

View File

@ -0,0 +1,54 @@
"""
export a city into Glb format
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import os
import shutil
import subprocess
from hub.city_model_structure.city import City
from hub.exports.formats.obj import Obj
class GltExceptionError(Exception):
"""
Glt execution error
"""
class Glb:
"""
Glb class
"""
def __init__(self, city, path, target_buildings=None):
self._city = city
self._path = path
if target_buildings is None:
target_buildings = [b.name for b in self._city.buildings]
self._target_buildings = target_buildings
self._export()
@property
def _obj2gltf(self):
return shutil.which('obj2gltf')
def _export(self):
try:
for building in self._city.buildings:
city = City(self._city.lower_corner, self._city.upper_corner, self._city.srs_name)
city.add_city_object(building)
city.name = building.name
Obj(city, self._path)
glb = f'{self._path}/{building.name}.glb'
subprocess.run([
self._obj2gltf,
'-i', f'{self._path}/{building.name}.obj',
'-b',
'-o', f'{glb}'
])
os.unlink(f'{self._path}/{building.name}.obj')
os.unlink(f'{self._path}/{building.name}.mtl')
except (subprocess.SubprocessError, subprocess.TimeoutExpired, subprocess.CalledProcessError) as err:
raise GltExceptionError from err

View File

@ -4,9 +4,10 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from pathlib import Path from pathlib import Path
import numpy as np
class Obj: class Obj:
""" """
@ -17,29 +18,64 @@ class Obj:
self._path = path self._path = path
self._export() self._export()
def _to_vertex(self, coordinate): def _ground(self, coordinate):
x = coordinate[0] - self._city.lower_corner[0] x = coordinate[0] - self._city.lower_corner[0]
y = coordinate[1] - self._city.lower_corner[1] y = coordinate[1] - self._city.lower_corner[1]
z = coordinate[2] - self._city.lower_corner[2] z = coordinate[2] - self._city.lower_corner[2]
return f'v {x} {y} {z}\n' return x, y, z
def _to_vertex(self, coordinate):
x, y, z = self._ground(coordinate)
return f'v {x} {z} -{y}\n' # to match opengl expectations
def _to_texture_vertex(self, coordinate):
u, v, _ = self._ground(coordinate)
return f'vt {u} {v}\n'
def _to_normal_vertex(self, coordinates):
ground_vertex = []
for coordinate in coordinates:
x, y, z = self._ground(coordinate)
ground_vertex.append(np.array([x, y, z]))
# recalculate the normal to get grounded values
edge_1 = ground_vertex[1] - ground_vertex[0]
edge_2 = ground_vertex[2] - ground_vertex[0]
normal = np.cross(edge_1, edge_2)
normal = normal / np.linalg.norm(normal)
return f'vn {normal[0]} {normal[1]} {normal[2]}\n'
def _export(self): def _export(self):
if self._city.name is None: if self._city.name is None:
self._city.name = 'unknown_city' self._city.name = 'unknown_city'
file_name = self._city.name + '.obj' obj_name = f'{self._city.name}.obj'
file_path = (Path(self._path).resolve() / file_name).resolve() mtl_name = f'{self._city.name}.mtl'
obj_file_path = (Path(self._path).resolve() / obj_name).resolve()
mtl_file_path = (Path(self._path).resolve() / mtl_name).resolve()
with open(mtl_file_path, 'w', encoding='utf-8') as mtl:
mtl.write("newmtl cerc_base_material\n")
mtl.write("Ka 1.0 1.0 1.0 # Ambient color (white)\n")
mtl.write("Kd 0.1 0.3 0.1 # Diffuse color (greenish)\n")
mtl.write("Ks 1.0 1.0 1.0 # Specular color (white)\n")
mtl.write("Ns 400.0 # Specular exponent (defines shininess)\n")
vertices = {} vertices = {}
with open(file_path, 'w', encoding='utf-8') as obj: faces = []
vertex_index = 0
normal_index = 0
with open(obj_file_path, 'w', encoding='utf-8') as obj:
obj.write("# cerc-hub export\n") obj.write("# cerc-hub export\n")
vertex_index = 0 obj.write(f'mtllib {mtl_name}\n')
faces = []
for building in self._city.buildings: for building in self._city.buildings:
obj.write(f'# building {building.name}\n') obj.write(f'# building {building.name}\n')
obj.write(f'g {building.name}\n') obj.write(f'g {building.name}\n')
obj.write('s off\n') obj.write('s off\n')
for surface in building.surfaces: for surface in building.surfaces:
obj.write(f'# surface {surface.name}\n') obj.write(f'# surface {surface.name}\n')
face = 'f ' face = []
normal = self._to_normal_vertex(surface.perimeter_polygon.coordinates)
normal_index += 1
textures = []
for coordinate in surface.perimeter_polygon.coordinates: for coordinate in surface.perimeter_polygon.coordinates:
vertex = self._to_vertex(coordinate) vertex = self._to_vertex(coordinate)
if vertex not in vertices: if vertex not in vertices:
@ -47,11 +83,13 @@ class Obj:
vertices[vertex] = vertex_index vertices[vertex] = vertex_index
current = vertex_index current = vertex_index
obj.write(vertex) obj.write(vertex)
textures.append(self._to_texture_vertex(coordinate)) # only append if non-existing
else: else:
current = vertices[vertex] current = vertices[vertex]
face.append(f'{current}/{current}/{normal_index}') # insert clockwise
obj.writelines(normal) # add the normal
obj.writelines(textures) # add the texture vertex
face = f'{face} {current}' faces.append(f"f {' '.join(face)}\n")
faces.append(f'{face} {face.split(" ")[1]}\n')
obj.writelines(faces) obj.writelines(faces)
faces = [] faces = []

View File

@ -49,36 +49,140 @@ WEEK_DAYS = 'Weekdays'
WEEK_ENDS = 'Weekends' WEEK_ENDS = 'Weekends'
ALL_DAYS = 'Alldays' ALL_DAYS = 'Alldays'
WEEK_DAYS_A_MONTH = {'monday': [5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 5], JANUARY = 'January'
'tuesday': [5, 4, 4, 4, 5, 4, 5, 4, 4, 5, 4, 4], FEBRUARY = 'February'
'wednesday': [5, 4, 4, 4, 5, 4, 4, 5, 4, 5, 4, 4], MARCH = 'March'
'thursday': [4, 4, 5, 4, 5, 4, 4, 5, 4, 4, 5, 4], APRIL = 'April'
'friday': [4, 4, 5, 4, 4, 5, 4, 5, 4, 4, 5, 4], MAY = 'May'
'saturday': [4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5], JUNE = 'June'
'sunday': [4, 4, 4, 5, 4, 4, 5, 4, 5, 4, 4, 5], JULY = 'July'
'holiday': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} AUGUST = 'August'
SEPTEMBER = 'September'
OCTOBER = 'October'
NOVEMBER = 'November'
DECEMBER = 'December'
WEEK_DAYS_A_YEAR = {'monday': 51, MONTHS = [JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER]
'tuesday': 50,
'wednesday': 50,
'thursday': 50,
'friday': 50,
'saturday': 52,
'sunday': 52,
'holiday': 10}
DAYS_A_MONTH = {'January': 31, WEEK_DAYS_A_MONTH = {JANUARY: {MONDAY: 5,
'February': 28, TUESDAY: 5,
'March': 31, WEDNESDAY: 5,
'April': 30, THURSDAY: 4,
'May': 31, FRIDAY: 4,
'June': 30, SATURDAY: 4,
'July': 31, SUNDAY: 4,
'August': 31, HOLIDAY: 0},
'September': 30, FEBRUARY: {MONDAY: 4,
'October': 31, TUESDAY: 4,
'November': 30, WEDNESDAY: 4,
'December': 31} THURSDAY: 4,
FRIDAY: 4,
SATURDAY: 4,
SUNDAY: 4,
HOLIDAY: 0},
MARCH: {MONDAY: 4,
TUESDAY: 4,
WEDNESDAY: 4,
THURSDAY: 5,
FRIDAY: 5,
SATURDAY: 5,
SUNDAY: 4,
HOLIDAY: 0},
APRIL: {MONDAY: 5,
TUESDAY: 4,
WEDNESDAY: 4,
THURSDAY: 4,
FRIDAY: 4,
SATURDAY: 4,
SUNDAY: 5,
HOLIDAY: 0},
MAY: {MONDAY: 4,
TUESDAY: 5,
WEDNESDAY: 5,
THURSDAY: 5,
FRIDAY: 4,
SATURDAY: 4,
SUNDAY: 4,
HOLIDAY: 0},
JUNE: {MONDAY: 4,
TUESDAY: 4,
WEDNESDAY: 4,
THURSDAY: 4,
FRIDAY: 5,
SATURDAY: 5,
SUNDAY: 4,
HOLIDAY: 0},
JULY: {MONDAY: 5,
TUESDAY: 5,
WEDNESDAY: 4,
THURSDAY: 4,
FRIDAY: 4,
SATURDAY: 4,
SUNDAY: 5,
HOLIDAY: 0},
AUGUST: {MONDAY: 4,
TUESDAY: 4,
WEDNESDAY: 5,
THURSDAY: 5,
FRIDAY: 5,
SATURDAY: 4,
SUNDAY: 4,
HOLIDAY: 0},
SEPTEMBER: {MONDAY: 4,
TUESDAY: 4,
WEDNESDAY: 4,
THURSDAY: 4,
FRIDAY: 4,
SATURDAY: 5,
SUNDAY: 5,
HOLIDAY: 0},
OCTOBER: {MONDAY: 5,
TUESDAY: 5,
WEDNESDAY: 5,
THURSDAY: 4,
FRIDAY: 4,
SATURDAY: 4,
SUNDAY: 4,
HOLIDAY: 0},
NOVEMBER: {MONDAY: 4,
TUESDAY: 4,
WEDNESDAY: 4,
THURSDAY: 5,
FRIDAY: 5,
SATURDAY: 4,
SUNDAY: 4,
HOLIDAY: 0},
DECEMBER: {MONDAY: 5,
TUESDAY: 4,
WEDNESDAY: 4,
THURSDAY: 4,
FRIDAY: 4,
SATURDAY: 5,
SUNDAY: 5,
HOLIDAY: 0},
}
WEEK_DAYS_A_YEAR = {MONDAY: 51,
TUESDAY: 50,
WEDNESDAY: 50,
THURSDAY: 50,
FRIDAY: 50,
SATURDAY: 52,
SUNDAY: 52,
HOLIDAY: 10}
DAYS_A_MONTH = {JANUARY: 31,
FEBRUARY: 28,
MARCH: 31,
APRIL: 30,
MAY: 31,
JUNE: 30,
JULY: 31,
AUGUST: 31,
SEPTEMBER: 30,
OCTOBER: 31,
NOVEMBER: 30,
DECEMBER: 31}
# data types # data types
ANY_NUMBER = 'any_number' ANY_NUMBER = 'any_number'

View File

@ -16,7 +16,9 @@ class HubFunctionToEilatConstructionFunction:
self._dictionary = { self._dictionary = {
cte.RESIDENTIAL: 'Residential_building', cte.RESIDENTIAL: 'Residential_building',
cte.HOTEL: 'Residential_building', cte.HOTEL: 'Residential_building',
cte.DORMITORY: 'Residential_building' cte.DORMITORY: 'Residential_building',
cte.DATACENTER: 'n/a',
cte.FARM: 'n/a'
} }
@property @property

View File

@ -73,7 +73,9 @@ class HubFunctionToMontrealCustomCostsFunction:
cte.AUTOMOTIVE_FACILITY: 'non-residential', cte.AUTOMOTIVE_FACILITY: 'non-residential',
cte.PARKING_GARAGE: 'non-residential', cte.PARKING_GARAGE: 'non-residential',
cte.RELIGIOUS: 'non-residential', cte.RELIGIOUS: 'non-residential',
cte.NON_HEATED: 'non-residential' cte.NON_HEATED: 'non-residential',
cte.DATACENTER: 'n/a',
cte.FARM: 'n/a'
} }
@property @property

View File

@ -72,7 +72,9 @@ class HubFunctionToNrcanConstructionFunction:
cte.AUTOMOTIVE_FACILITY: 'n/a', cte.AUTOMOTIVE_FACILITY: 'n/a',
cte.PARKING_GARAGE: 'n/a', cte.PARKING_GARAGE: 'n/a',
cte.RELIGIOUS: 'n/a', cte.RELIGIOUS: 'n/a',
cte.NON_HEATED: 'n/a' cte.NON_HEATED: 'n/a',
cte.DATACENTER: 'n/a',
cte.FARM: 'n/a'
} }
@property @property

View File

@ -73,7 +73,9 @@ class HubFunctionToNrelConstructionFunction:
cte.AUTOMOTIVE_FACILITY: 'n/a', cte.AUTOMOTIVE_FACILITY: 'n/a',
cte.PARKING_GARAGE: 'n/a', cte.PARKING_GARAGE: 'n/a',
cte.RELIGIOUS: 'n/a', cte.RELIGIOUS: 'n/a',
cte.NON_HEATED: 'n/a' cte.NON_HEATED: 'n/a',
cte.DATACENTER: 'n/a',
cte.FARM: 'n/a'
} }
@property @property

View File

@ -73,7 +73,9 @@ class HubUsageToComnetUsage:
cte.AUTOMOTIVE_FACILITY: 'BA Automotive Facility', cte.AUTOMOTIVE_FACILITY: 'BA Automotive Facility',
cte.PARKING_GARAGE: 'BA Parking Garage', cte.PARKING_GARAGE: 'BA Parking Garage',
cte.RELIGIOUS: 'BA Religious Building', cte.RELIGIOUS: 'BA Religious Building',
cte.NON_HEATED: 'n/a' cte.NON_HEATED: 'n/a',
cte.DATACENTER: 'n/a',
cte.FARM: 'n/a'
} }
@property @property

View File

@ -17,7 +17,9 @@ class HubUsageToEilatUsage:
self._dictionary = { self._dictionary = {
cte.RESIDENTIAL: 'Residential', cte.RESIDENTIAL: 'Residential',
cte.HOTEL: 'Hotel employees', cte.HOTEL: 'Hotel employees',
cte.DORMITORY: 'Dormitory' cte.DORMITORY: 'Dormitory',
cte.DATACENTER: 'n/a',
cte.FARM: 'n/a'
} }
@property @property

View File

@ -73,7 +73,9 @@ class HubUsageToHftUsage:
cte.AUTOMOTIVE_FACILITY: 'n/a', cte.AUTOMOTIVE_FACILITY: 'n/a',
cte.PARKING_GARAGE: 'n/a', cte.PARKING_GARAGE: 'n/a',
cte.RELIGIOUS: 'event location', cte.RELIGIOUS: 'event location',
cte.NON_HEATED: 'non-heated' cte.NON_HEATED: 'non-heated',
cte.DATACENTER: 'n/a',
cte.FARM: 'n/a'
} }
@property @property

View File

@ -73,7 +73,9 @@ class HubUsageToNrcanUsage:
cte.AUTOMOTIVE_FACILITY: 'Automotive facility', cte.AUTOMOTIVE_FACILITY: 'Automotive facility',
cte.PARKING_GARAGE: 'Storage garage', cte.PARKING_GARAGE: 'Storage garage',
cte.RELIGIOUS: 'Religious building', cte.RELIGIOUS: 'Religious building',
cte.NON_HEATED: 'n/a' cte.NON_HEATED: 'n/a',
cte.DATACENTER: 'n/a',
cte.FARM: 'n/a'
} }
@property @property

View File

@ -623,8 +623,12 @@ class MontrealFunctionToHubFunction:
'8192': cte.FARM, '8192': cte.FARM,
'2439': cte.INDUSTRY, '2439': cte.INDUSTRY,
'3891': cte.INDUSTRY, '3891': cte.INDUSTRY,
'6354': cte.WORKSHOP '6354': cte.WORKSHOP,
} '4815': cte.NON_HEATED,
'6651': cte.WORKSHOP,
'2822': cte.INDUSTRY,
'2821': cte.INDUSTRY
}
@property @property
def dictionary(self) -> dict: def dictionary(self) -> dict:

View File

@ -312,7 +312,7 @@ class GeometryHelper:
country = file_country_code country = file_country_code
city = file_city_name city = file_city_name
region_code = f'{file_country_code}.{admin1_code}.{admin2_code}' region_code = f'{file_country_code}.{admin1_code}.{admin2_code}'
return Location(country, city, region_code) return Location(country, city, region_code, latitude, longitude)
@staticmethod @staticmethod
def distance_between_points(vertex1, vertex2): def distance_between_points(vertex1, vertex2):

View File

@ -11,10 +11,12 @@ class Location:
""" """
Location Location
""" """
def __init__(self, country, city, region_code): def __init__(self, country, city, region_code, climate_reference_city_latitude, climate_reference_city_longitude):
self._country = country self._country = country
self._city = city self._city = city
self._region_code = region_code self._region_code = region_code
self._climate_reference_city_latitude = climate_reference_city_latitude
self._climate_reference_city_longitude = climate_reference_city_longitude
@property @property
def city(self): def city(self):
@ -36,3 +38,17 @@ class Location:
Get region Get region
""" """
return self._region_code return self._region_code
@property
def climate_reference_city_latitude(self):
"""
Get climate-reference-city latitude
"""
return self._climate_reference_city_latitude
@property
def climate_reference_city_longitude(self):
"""
Get climate-reference-city longitude
"""
return self._climate_reference_city_longitude

View File

@ -63,11 +63,10 @@ class LoadsCalculation:
:return: int :return: int
""" """
heating_load_transmitted = 0 heating_load_transmitted = 0
for internal_zone in self._building.internal_zones: for thermal_zone in self._building.thermal_zones_from_internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: internal_temperature = thermal_zone.thermal_control.mean_heating_set_point
internal_temperature = thermal_zone.thermal_control.mean_heating_set_point heating_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature,
heating_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature, ground_temperature)
ground_temperature)
return heating_load_transmitted return heating_load_transmitted
def get_cooling_transmitted_load(self, ambient_temperature, ground_temperature): def get_cooling_transmitted_load(self, ambient_temperature, ground_temperature):
@ -76,11 +75,10 @@ class LoadsCalculation:
:return: int :return: int
""" """
cooling_load_transmitted = 0 cooling_load_transmitted = 0
for internal_zone in self._building.internal_zones: for thermal_zone in self._building.thermal_zones_from_internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point
internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point cooling_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature,
cooling_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature, ground_temperature)
ground_temperature)
return cooling_load_transmitted return cooling_load_transmitted
def get_heating_ventilation_load_sensible(self, ambient_temperature): def get_heating_ventilation_load_sensible(self, ambient_temperature):
@ -89,10 +87,9 @@ class LoadsCalculation:
:return: int :return: int
""" """
heating_ventilation_load = 0 heating_ventilation_load = 0
for internal_zone in self._building.internal_zones: for thermal_zone in self._building.thermal_zones_from_internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: internal_temperature = thermal_zone.thermal_control.mean_heating_set_point
internal_temperature = thermal_zone.thermal_control.mean_heating_set_point heating_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature)
heating_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature)
return heating_ventilation_load return heating_ventilation_load
def get_cooling_ventilation_load_sensible(self, ambient_temperature): def get_cooling_ventilation_load_sensible(self, ambient_temperature):
@ -101,10 +98,9 @@ class LoadsCalculation:
:return: int :return: int
""" """
cooling_ventilation_load = 0 cooling_ventilation_load = 0
for internal_zone in self._building.internal_zones: for thermal_zone in self._building.thermal_zones_from_internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point
internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point cooling_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature)
cooling_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature)
return cooling_ventilation_load return cooling_ventilation_load
def get_internal_load_sensible(self): def get_internal_load_sensible(self):
@ -115,19 +111,18 @@ class LoadsCalculation:
cooling_load_occupancy_sensible = 0 cooling_load_occupancy_sensible = 0
cooling_load_lighting = 0 cooling_load_lighting = 0
cooling_load_equipment_sensible = 0 cooling_load_equipment_sensible = 0
for internal_zone in self._building.internal_zones: for thermal_zone in self._building.thermal_zones_from_internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: cooling_load_occupancy_sensible += (thermal_zone.occupancy.sensible_convective_internal_gain
cooling_load_occupancy_sensible += (thermal_zone.occupancy.sensible_convective_internal_gain + thermal_zone.occupancy.sensible_radiative_internal_gain) \
+ thermal_zone.occupancy.sensible_radiative_internal_gain) \ * thermal_zone.footprint_area
* thermal_zone.footprint_area cooling_load_lighting += (
cooling_load_lighting += ( thermal_zone.lighting.density * thermal_zone.lighting.convective_fraction + thermal_zone.lighting.density *
thermal_zone.lighting.density * thermal_zone.lighting.convective_fraction + thermal_zone.lighting.density * thermal_zone.lighting.radiative_fraction
thermal_zone.lighting.radiative_fraction ) * thermal_zone.footprint_area
) * thermal_zone.footprint_area cooling_load_equipment_sensible += (
cooling_load_equipment_sensible += ( thermal_zone.appliances.density * thermal_zone.appliances.convective_fraction +
thermal_zone.appliances.density * thermal_zone.appliances.convective_fraction + thermal_zone.appliances.density * thermal_zone.appliances.radiative_fraction
thermal_zone.appliances.density * thermal_zone.appliances.radiative_fraction ) * thermal_zone.footprint_area
) * thermal_zone.footprint_area
internal_load = cooling_load_occupancy_sensible + cooling_load_lighting + cooling_load_equipment_sensible internal_load = cooling_load_occupancy_sensible + cooling_load_lighting + cooling_load_equipment_sensible
return internal_load return internal_load
@ -137,12 +132,11 @@ class LoadsCalculation:
:return: int :return: int
""" """
cooling_load_radiation = 0 cooling_load_radiation = 0
for internal_zone in self._building.internal_zones: for thermal_zone in self._building.thermal_zones_from_internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones: for thermal_boundary in thermal_zone.thermal_boundaries:
for thermal_boundary in thermal_zone.thermal_boundaries: for thermal_opening in thermal_boundary.thermal_openings:
for thermal_opening in thermal_boundary.thermal_openings: radiation = thermal_boundary.parent_surface.global_irradiance[cte.HOUR][hour] * cte.WATTS_HOUR_TO_JULES
radiation = thermal_boundary.parent_surface.global_irradiance[cte.HOUR][hour] * cte.WATTS_HOUR_TO_JULES cooling_load_radiation += (
cooling_load_radiation += ( thermal_opening.area * (1 - thermal_opening.frame_ratio) * thermal_opening.g_value * radiation
thermal_opening.area * (1 - thermal_opening.frame_ratio) * thermal_opening.g_value * radiation )
)
return cooling_load_radiation return cooling_load_radiation

View File

@ -54,13 +54,11 @@ class PeakLoads:
""" """
month = 1 month = 1
peaks = [0 for _ in range(12)] peaks = [0 for _ in range(12)]
print('hv', hourly_values)
for i in range(0, len(hourly_values)): for i in range(0, len(hourly_values)):
if _MONTH_STARTING_HOUR[month] <= i: if _MONTH_STARTING_HOUR[month] <= i:
month += 1 month += 1
if hourly_values[i] > peaks[month-1]: if hourly_values[i] > peaks[month-1]:
peaks[month-1] = hourly_values[i] peaks[month-1] = hourly_values[i]
print('peak', peaks)
return peaks return peaks
@property @property
@ -75,18 +73,15 @@ class PeakLoads:
ambient_temperature = self._building.external_temperature[cte.HOUR] ambient_temperature = self._building.external_temperature[cte.HOUR]
for month in range(0, 12): for month in range(0, 12):
ground_temperature = self._building.ground_temperature[cte.MONTH]['2'][month] ground_temperature = self._building.ground_temperature[cte.MONTH]['2'][month]
heating_ambient_temperature = 100
start_hour = _MONTH_STARTING_HOUR[month] start_hour = _MONTH_STARTING_HOUR[month]
end_hour = 8760 end_hour = 8760
if month < 11: if month < 11:
end_hour = _MONTH_STARTING_HOUR[month + 1] end_hour = _MONTH_STARTING_HOUR[month + 1]
for hour in range(start_hour, end_hour): heating_ambient_temperature = min(ambient_temperature[start_hour:end_hour])
temperature = ambient_temperature[hour]
if temperature < heating_ambient_temperature:
heating_ambient_temperature = temperature
loads = LoadsCalculation(self._building) loads = LoadsCalculation(self._building)
heating_load_transmitted = loads.get_heating_transmitted_load(heating_ambient_temperature, ground_temperature) heating_load_transmitted = loads.get_heating_transmitted_load(heating_ambient_temperature, ground_temperature)
heating_load_ventilation_sensible = loads.get_heating_ventilation_load_sensible(heating_ambient_temperature) heating_load_ventilation_sensible = loads.get_heating_ventilation_load_sensible(heating_ambient_temperature)
# todo: include heating ventilation latent
heating_load_ventilation_latent = 0 heating_load_ventilation_latent = 0
heating_load = heating_load_transmitted + heating_load_ventilation_sensible + heating_load_ventilation_latent heating_load = heating_load_transmitted + heating_load_ventilation_sensible + heating_load_ventilation_latent
heating_load = max(heating_load, 0) heating_load = max(heating_load, 0)

View File

@ -71,6 +71,7 @@ class EilatPhysicsParameters:
for catalog_construction in catalog_archetype.constructions: for catalog_construction in catalog_archetype.constructions:
construction = Construction() construction = Construction()
construction.type = catalog_construction.type construction.type = catalog_construction.type
construction.name = catalog_construction.name
if catalog_construction.window_ratio is not None: if catalog_construction.window_ratio is not None:
for _orientation in catalog_construction.window_ratio: for _orientation in catalog_construction.window_ratio:
if catalog_construction.window_ratio[_orientation] is None: if catalog_construction.window_ratio[_orientation] is None:
@ -83,7 +84,7 @@ class EilatPhysicsParameters:
layer.thickness = layer_archetype.thickness layer.thickness = layer_archetype.thickness
total_thickness += layer_archetype.thickness total_thickness += layer_archetype.thickness
archetype_material = layer_archetype.material archetype_material = layer_archetype.material
layer.name = archetype_material.name layer.material_name = archetype_material.name
layer.no_mass = archetype_material.no_mass layer.no_mass = archetype_material.no_mass
if archetype_material.no_mass: if archetype_material.no_mass:
layer.thermal_resistance = archetype_material.thermal_resistance layer.thermal_resistance = archetype_material.thermal_resistance

View File

@ -42,7 +42,6 @@ class ConstructionHelper:
'Varennes': '6', 'Varennes': '6',
'Laval': '6', 'Laval': '6',
'Longueuil': '6', 'Longueuil': '6',
'Saint-Leonard': '6',
'Mont-Royal': '6', 'Mont-Royal': '6',
'Deux-Montagnes': '6', 'Deux-Montagnes': '6',
'Dorval': '6', 'Dorval': '6',
@ -58,6 +57,8 @@ class ConstructionHelper:
'Pointe-Claire': '6', 'Pointe-Claire': '6',
'Boucherville': '6', 'Boucherville': '6',
'Mascouche': '6', 'Mascouche': '6',
'Saint-Leonard': '6',
'La Prairie': '6'
} }
_reference_city_to_israel_climate_zone = { _reference_city_to_israel_climate_zone = {

View File

@ -71,6 +71,7 @@ class NrcanPhysicsParameters:
for catalog_construction in catalog_archetype.constructions: for catalog_construction in catalog_archetype.constructions:
construction = Construction() construction = Construction()
construction.type = catalog_construction.type construction.type = catalog_construction.type
construction.name = catalog_construction.name
if catalog_construction.window_ratio is not None: if catalog_construction.window_ratio is not None:
for _orientation in catalog_construction.window_ratio: for _orientation in catalog_construction.window_ratio:
if catalog_construction.window_ratio[_orientation] is None: if catalog_construction.window_ratio[_orientation] is None:
@ -81,7 +82,7 @@ class NrcanPhysicsParameters:
layer = Layer() layer = Layer()
layer.thickness = layer_archetype.thickness layer.thickness = layer_archetype.thickness
archetype_material = layer_archetype.material archetype_material = layer_archetype.material
layer.name = archetype_material.name layer.material_name = archetype_material.name
layer.no_mass = archetype_material.no_mass layer.no_mass = archetype_material.no_mass
if archetype_material.no_mass: if archetype_material.no_mass:
layer.thermal_resistance = archetype_material.thermal_resistance layer.thermal_resistance = archetype_material.thermal_resistance

View File

@ -73,6 +73,7 @@ class NrelPhysicsParameters:
for catalog_construction in catalog_archetype.constructions: for catalog_construction in catalog_archetype.constructions:
construction = Construction() construction = Construction()
construction.type = catalog_construction.type construction.type = catalog_construction.type
construction.name = catalog_construction.name
if catalog_construction.window_ratio is not None: if catalog_construction.window_ratio is not None:
construction.window_ratio = {'north': catalog_construction.window_ratio, construction.window_ratio = {'north': catalog_construction.window_ratio,
'east': catalog_construction.window_ratio, 'east': catalog_construction.window_ratio,
@ -84,7 +85,7 @@ class NrelPhysicsParameters:
layer = Layer() layer = Layer()
layer.thickness = layer_archetype.thickness layer.thickness = layer_archetype.thickness
archetype_material = layer_archetype.material archetype_material = layer_archetype.material
layer.name = archetype_material.name layer.material_name = archetype_material.name
layer.no_mass = archetype_material.no_mass layer.no_mass = archetype_material.no_mass
if archetype_material.no_mass: if archetype_material.no_mass:
layer.thermal_resistance = archetype_material.thermal_resistance layer.thermal_resistance = archetype_material.thermal_resistance

View File

@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
import logging
import numpy as np import numpy as np
import xmltodict import xmltodict
@ -22,16 +23,17 @@ class CityGml:
def __init__(self, def __init__(self,
path, path,
extrusion_height_field=None,
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
function_to_hub=None): function_to_hub=None,
hub_crs=None):
self._city = None self._city = None
self._lod = None self._lod = None
self._lod1_tags = ['lod1Solid', 'lod1MultiSurface'] self._lod1_tags = ['lod1Solid', 'lod1MultiSurface']
self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve'] self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve']
self._extrusion_height_field = extrusion_height_field
self._function_to_hub = function_to_hub self._function_to_hub = function_to_hub
if hub_crs is None:
hub_crs = 'EPSG:26911'
if function_field is None: if function_field is None:
function_field = 'function' function_field = 'function'
if year_of_construction_field is None: if year_of_construction_field is None:
@ -79,7 +81,8 @@ class CityGml:
self._srs_name = envelope['@srsName'] self._srs_name = envelope['@srsName']
else: else:
# If not coordinate system given assuming hub standard # If not coordinate system given assuming hub standard
self._srs_name = "EPSG:26911" logging.warning(f'gml file contains no coordinate system assuming {hub_crs}')
self._srs_name = hub_crs
else: else:
# get the boundary from the city objects instead # get the boundary from the city objects instead
for city_object_member in self._gml['CityModel']['cityObjectMember']: for city_object_member in self._gml['CityModel']['cityObjectMember']:

View File

@ -49,6 +49,8 @@ class CityGmlLod2(CityGmlBase):
surface_encoding, surface_subtype = cls._surface_encoding(bounded[surface_type]) surface_encoding, surface_subtype = cls._surface_encoding(bounded[surface_type])
except NotImplementedError: except NotImplementedError:
continue continue
if 'surfaceMember' not in bounded[surface_type][surface_encoding][surface_subtype]:
continue
for member in bounded[surface_type][surface_encoding][surface_subtype]['surfaceMember']: for member in bounded[surface_type][surface_encoding][surface_subtype]['surfaceMember']:
if 'CompositeSurface' in member: if 'CompositeSurface' in member:
for composite_members in member['CompositeSurface']['surfaceMember']: for composite_members in member['CompositeSurface']['surfaceMember']:

View File

@ -34,9 +34,13 @@ class Geojson:
extrusion_height_field=None, extrusion_height_field=None,
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
function_to_hub=None): function_to_hub=None,
# todo: destination epsg should change according actual the location hub_crs=None
self._transformer = Transformer.from_crs('epsg:4326', 'epsg:26911') ):
self._hub_crs = hub_crs
if hub_crs is None :
self._hub_crs = 'epsg:26911'
self._transformer = Transformer.from_crs('epsg:4326', self._hub_crs)
self._min_x = cte.MAX_FLOAT self._min_x = cte.MAX_FLOAT
self._min_y = cte.MAX_FLOAT self._min_y = cte.MAX_FLOAT
self._max_x = cte.MIN_FLOAT self._max_x = cte.MIN_FLOAT
@ -116,6 +120,7 @@ class Geojson:
if self._extrusion_height_field is not None: if self._extrusion_height_field is not None:
extrusion_height = float(feature['properties'][self._extrusion_height_field]) extrusion_height = float(feature['properties'][self._extrusion_height_field])
lod = 1 lod = 1
self._max_z = max(self._max_z, extrusion_height)
year_of_construction = None year_of_construction = None
if self._year_of_construction_field is not None: if self._year_of_construction_field is not None:
year_of_construction = int(feature['properties'][self._year_of_construction_field]) year_of_construction = int(feature['properties'][self._year_of_construction_field])
@ -154,7 +159,7 @@ class Geojson:
extrusion_height)) extrusion_height))
else: else:
raise NotImplementedError(f'Geojson geometry type [{geometry["type"]}] unknown') raise NotImplementedError(f'Geojson geometry type [{geometry["type"]}] unknown')
self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911') self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], self._hub_crs)
for building in buildings: for building in buildings:
# Do not include "small building-like structures" to buildings # Do not include "small building-like structures" to buildings
if building.floor_area >= 25: if building.floor_area >= 25:
@ -165,7 +170,6 @@ class Geojson:
if lod > 0: if lod > 0:
lines_information = GeometryHelper.city_mapping(self._city, plot=False) lines_information = GeometryHelper.city_mapping(self._city, plot=False)
self._store_shared_percentage_to_walls(self._city, lines_information) self._store_shared_percentage_to_walls(self._city, lines_information)
return self._city return self._city
def _polygon_coordinates_to_3d(self, polygon_coordinates): def _polygon_coordinates_to_3d(self, polygon_coordinates):

View File

@ -18,21 +18,21 @@ class GeometryFactory:
""" """
def __init__(self, file_type, def __init__(self, file_type,
path=None, path=None,
data_frame=None,
aliases_field=None, aliases_field=None,
height_field=None, height_field=None,
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
function_to_hub=None): function_to_hub=None,
hub_crs=None):
self._file_type = '_' + file_type.lower() self._file_type = '_' + file_type.lower()
validate_import_export_type(GeometryFactory, file_type) validate_import_export_type(GeometryFactory, file_type)
self._path = path self._path = path
self._data_frame = data_frame
self._aliases_field = aliases_field self._aliases_field = aliases_field
self._height_field = height_field self._height_field = height_field
self._year_of_construction_field = year_of_construction_field self._year_of_construction_field = year_of_construction_field
self._function_field = function_field self._function_field = function_field
self._function_to_hub = function_to_hub self._function_to_hub = function_to_hub
self._hub_crs = hub_crs
@property @property
def _citygml(self) -> City: def _citygml(self) -> City:
@ -41,10 +41,10 @@ class GeometryFactory:
:return: City :return: City
""" """
return CityGml(self._path, return CityGml(self._path,
self._height_field,
self._year_of_construction_field, self._year_of_construction_field,
self._function_field, self._function_field,
self._function_to_hub).city self._function_to_hub,
self._hub_crs).city
@property @property
def _obj(self) -> City: def _obj(self) -> City:
@ -65,7 +65,8 @@ class GeometryFactory:
self._height_field, self._height_field,
self._year_of_construction_field, self._year_of_construction_field,
self._function_field, self._function_field,
self._function_to_hub).city self._function_to_hub,
self._hub_crs).city
@property @property
def city(self) -> City: def city(self) -> City:

View File

@ -4,7 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Guillermo.GutierrezMorote@concordia.ca Project Coder Guillermo.GutierrezMorote@concordia.ca
""" """
import logging
from pathlib import Path from pathlib import Path
import csv import csv
@ -43,53 +43,61 @@ class InselMonthlyEnergyBalance:
domestic_hot_water_demand = [] domestic_hot_water_demand = []
lighting_demand = [] lighting_demand = []
appliances_demand = [] appliances_demand = []
if building.internal_zones[0].thermal_zones_from_internal_zones is None:
# todo: REFACTOR after retrofit project, this is a hack for the pickle files
try:
if building.internal_zones[0].thermal_zones_from_internal_zones is None:
domestic_hot_water_demand = [0] * 12
lighting_demand = [0] * 12
appliances_demand = [0] * 12
else:
thermal_zone = building.internal_zones[0].thermal_zones_from_internal_zones[0]
area = thermal_zone.total_floor_area
cold_water = building.cold_water_temperature[cte.MONTH]
peak_flow = thermal_zone.domestic_hot_water.peak_flow
service_temperature = thermal_zone.domestic_hot_water.service_temperature
lighting_density = thermal_zone.lighting.density
appliances_density = thermal_zone.appliances.density
for i_month, month in enumerate(cte.MONTHS):
total_dhw_demand = 0
total_lighting = 0
total_appliances = 0
for schedule in thermal_zone.lighting.schedules:
total_day = 0
for value in schedule.values:
total_day += value
for day_type in schedule.day_types:
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
* lighting_density / cte.WATTS_HOUR_TO_JULES
lighting_demand.append(total_lighting * area)
for schedule in thermal_zone.appliances.schedules:
total_day = 0
for value in schedule.values:
total_day += value
for day_type in schedule.day_types:
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
* appliances_density / cte.WATTS_HOUR_TO_JULES
appliances_demand.append(total_appliances * area)
for schedule in thermal_zone.domestic_hot_water.schedules:
total_day = 0
for value in schedule.values:
total_day += value
for day_type in schedule.day_types:
demand = (
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
* (service_temperature - cold_water[i_month]) / cte.WATTS_HOUR_TO_JULES
)
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] * demand
domestic_hot_water_demand.append(total_dhw_demand * area)
except AttributeError:
domestic_hot_water_demand = [0] * 12 domestic_hot_water_demand = [0] * 12
lighting_demand = [0] * 12 lighting_demand = [0] * 12
appliances_demand = [0] * 12 appliances_demand = [0] * 12
else: logging.warning('Building internal zone raised an error, most likely the building has missing archetypes')
thermal_zone = building.internal_zones[0].thermal_zones_from_internal_zones[0]
area = thermal_zone.total_floor_area
cold_water = building.cold_water_temperature[cte.MONTH]
peak_flow = thermal_zone.domestic_hot_water.peak_flow
service_temperature = thermal_zone.domestic_hot_water.service_temperature
lighting_density = thermal_zone.lighting.density
appliances_density = thermal_zone.appliances.density
for month in range(0, 12):
total_dhw_demand = 0
total_lighting = 0
total_appliances = 0
for schedule in thermal_zone.lighting.schedules:
total_day = 0
for value in schedule.values:
total_day += value
for day_type in schedule.day_types:
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] \
* lighting_density / cte.WATTS_HOUR_TO_JULES
lighting_demand.append(total_lighting * area)
for schedule in thermal_zone.appliances.schedules:
total_day = 0
for value in schedule.values:
total_day += value
for day_type in schedule.day_types:
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] \
* appliances_density / cte.WATTS_HOUR_TO_JULES
appliances_demand.append(total_appliances * area)
for schedule in thermal_zone.domestic_hot_water.schedules:
total_day = 0
for value in schedule.values:
total_day += value
for day_type in schedule.day_types:
demand = (
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
* (service_temperature - cold_water[month]) / cte.WATTS_HOUR_TO_JULES
)
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] * demand
domestic_hot_water_demand.append(total_dhw_demand * area)
building.domestic_hot_water_heat_demand[cte.MONTH] = domestic_hot_water_demand building.domestic_hot_water_heat_demand[cte.MONTH] = domestic_hot_water_demand
building.domestic_hot_water_heat_demand[cte.YEAR] = [sum(domestic_hot_water_demand)] building.domestic_hot_water_heat_demand[cte.YEAR] = [sum(domestic_hot_water_demand)]

View File

@ -31,7 +31,7 @@ class ResultFactory:
self._handler = '_' + handler.lower().replace(' ', '_') self._handler = '_' + handler.lower().replace(' ', '_')
validate_import_export_type(ResultFactory, handler) validate_import_export_type(ResultFactory, handler)
self._city = city self._city = city
self._base_path = base_path self._base_path = Path(base_path)
self._hp_model = hp_model self._hp_model = hp_model
def _sra(self): def _sra(self):

View File

@ -24,7 +24,8 @@ class Weather:
'DE.01.082': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/DEU/DEU_Stuttgart.107380_IWEC/DEU_Stuttgart.107380_IWEC.epw', 'DE.01.082': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/DEU/DEU_Stuttgart.107380_IWEC/DEU_Stuttgart.107380_IWEC.epw',
'US.NY.047': 'https://energyplus-weather.s3.amazonaws.com/north_and_central_america_wmo_region_4/USA/NY/USA_NY_New.York.City-Central.Park.94728_TMY/USA_NY_New.York.City-Central.Park.94728_TMY.epw', 'US.NY.047': 'https://energyplus-weather.s3.amazonaws.com/north_and_central_america_wmo_region_4/USA/NY/USA_NY_New.York.City-Central.Park.94728_TMY/USA_NY_New.York.City-Central.Park.94728_TMY.epw',
'CA.10.12': 'https://energyplus-weather.s3.amazonaws.com/north_and_central_america_wmo_region_4/CAN/PQ/CAN_PQ_Quebec.717140_CWEC/CAN_PQ_Quebec.717140_CWEC.epw', 'CA.10.12': 'https://energyplus-weather.s3.amazonaws.com/north_and_central_america_wmo_region_4/CAN/PQ/CAN_PQ_Quebec.717140_CWEC/CAN_PQ_Quebec.717140_CWEC.epw',
'IL.01.': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/ISR/ISR_Eilat.401990_MSI/ISR_Eilat.401990_MSI.epw' 'IL.01.': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/ISR/ISR_Eilat.401990_MSI/ISR_Eilat.401990_MSI.epw',
'ES.07.PM': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/ESP/ESP_Palma.083060_SWEC/ESP_Palma.083060_SWEC.epw'
} }
# todo: this dictionary need to be completed, a data science student task? # todo: this dictionary need to be completed, a data science student task?

View File

@ -75,10 +75,10 @@ class DBControl:
: :
""" """
cities = self._city.get_by_user_id_application_id_and_scenario(user_id, application_id, scenario) cities = self._city.get_by_user_id_application_id_and_scenario(user_id, application_id, scenario)
for city in cities: c = [c[0].id for c in cities]
result = self.building_info(name, city[0].id) result = self._city_object.building_in_cities_info(name, c)
if result is not None: if result is not None:
return result return result
return None return None
def building_info(self, name, city_id) -> CityObject: def building_info(self, name, city_id) -> CityObject:
@ -90,6 +90,15 @@ class DBControl:
""" """
return self._city_object.get_by_name_or_alias_and_city(name, city_id) return self._city_object.get_by_name_or_alias_and_city(name, city_id)
def building_info_in_cities(self, name, cities) -> CityObject:
"""
Retrieve the building info from the database
:param name: Building name
:param cities: [City ID]
:return: CityObject
"""
return self._city_object.get_by_name_or_alias_in_cities(name, cities)
def buildings_info(self, request_values, city_id) -> [CityObject]: def buildings_info(self, request_values, city_id) -> [CityObject]:
""" """
Retrieve the buildings info from the database Retrieve the buildings info from the database
@ -114,10 +123,7 @@ class DBControl:
result_names = [] result_names = []
results = {} results = {}
for scenario in request_values['scenarios']: for scenario in request_values['scenarios']:
print('scenario', scenario, results)
for scenario_name in scenario.keys(): for scenario_name in scenario.keys():
print('scenario name', scenario_name)
result_sets = self._city.get_by_user_id_application_id_and_scenario( result_sets = self._city.get_by_user_id_application_id_and_scenario(
user_id, user_id,
application_id, application_id,
@ -125,25 +131,21 @@ class DBControl:
) )
if result_sets is None: if result_sets is None:
continue continue
for result_set in result_sets: results[scenario_name] = []
city_id = result_set[0].id city_ids = [r[0].id for r in result_sets]
for building_name in scenario[scenario_name]:
_building = self._city_object.get_by_name_or_alias_in_cities(building_name, city_ids)
if _building is None:
continue
city_object_id = _building.id
_ = self._simulation_results.get_simulation_results_by_city_object_id_and_names(
city_object_id,
result_names)
results[scenario_name] = [] for value in _:
for building_name in scenario[scenario_name]: values = value.values
_building = self._city_object.get_by_name_or_alias_and_city(building_name, city_id) values["building"] = building_name
if _building is None: results[scenario_name].append(values)
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)
print(scenario, results)
return results return results
def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int): def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int):

View File

@ -7,9 +7,10 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
import datetime import datetime
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import Column, Integer, String, Sequence from sqlalchemy import Column, Integer, String, Sequence
from sqlalchemy import DateTime from sqlalchemy import DateTime
from sqlalchemy.dialects.postgresql import UUID
from hub.persistence.configuration import Models from hub.persistence.configuration import Models

View File

@ -21,8 +21,8 @@ class City(Models):
pickle_path = Column(String, nullable=False) pickle_path = Column(String, nullable=False)
name = Column(String, nullable=False) name = Column(String, nullable=False)
scenario = Column(String, nullable=False) scenario = Column(String, nullable=False)
application_id = Column(Integer, ForeignKey('application.id'), nullable=False) application_id = Column(Integer, ForeignKey('application.id', ondelete='CASCADE'), nullable=False)
user_id = Column(Integer, ForeignKey('user.id'), nullable=True) user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE'), 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)

View File

@ -6,6 +6,7 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
""" """
import datetime import datetime
import logging
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float
from sqlalchemy import DateTime from sqlalchemy import DateTime
@ -20,7 +21,7 @@ class CityObject(Models):
""" """
__tablename__ = 'city_object' __tablename__ = 'city_object'
id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True) id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False) city_id = Column(Integer, ForeignKey('city.id', ondelete='CASCADE'), nullable=False)
name = Column(String, nullable=False) name = Column(String, nullable=False)
aliases = Column(String, nullable=True) aliases = Column(String, nullable=True)
type = Column(String, nullable=False) type = Column(String, nullable=False)
@ -51,19 +52,27 @@ class CityObject(Models):
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) 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:
storeys = building.max_height / building.average_storey_height
self.total_heating_area = building.floor_area * storeys
wall_area = 0 wall_area = 0
window_ratio = 0
try:
if storeys is None:
storeys = building.max_height / building.average_storey_height
for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
for thermal_boundary in thermal_zone.thermal_boundaries:
window_ratio = thermal_boundary.window_ratio
break
except TypeError:
storeys = 0
logging.warning(
'building %s has no storey height so heating area, storeys and window ratio cannot be calculated',
self.name
)
self.total_heating_area = building.floor_area * storeys
for wall in building.walls: for wall in building.walls:
wall_area += wall.solid_polygon.area wall_area += wall.solid_polygon.area
self.wall_area = wall_area self.wall_area = wall_area
window_ratio = 0
for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
for thermal_boundary in thermal_zone.thermal_boundaries:
window_ratio = thermal_boundary.window_ratio
break
self.windows_area = wall_area * window_ratio self.windows_area = wall_area * window_ratio
system_name = building.energy_systems_archetype_name system_name = building.energy_systems_archetype_name
if system_name is None: if system_name is None:

View File

@ -19,8 +19,8 @@ class SimulationResults(Models):
""" """
__tablename__ = 'simulation_results' __tablename__ = 'simulation_results'
id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True) id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=True) city_id = Column(Integer, ForeignKey('city.id', ondelete='CASCADE'), nullable=True)
city_object_id = Column(Integer, ForeignKey('city_object.id'), nullable=True) city_object_id = Column(Integer, ForeignKey('city_object.id', ondelete='CASCADE'), nullable=True)
name = Column(String, nullable=False) name = Column(String, nullable=False)
values = Column(JSONB, nullable=False) values = Column(JSONB, nullable=False)
created = Column(DateTime, default=datetime.datetime.utcnow) created = Column(DateTime, default=datetime.datetime.utcnow)

View File

@ -10,6 +10,7 @@ import logging
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm.session import Session
from hub.persistence.repository import Repository from hub.persistence.repository import Repository
from hub.persistence.models import Application as Model from hub.persistence.models import Application as Model
@ -48,10 +49,11 @@ class Application(Repository):
pass pass
try: try:
application = Model(name=name, description=description, application_uuid=application_uuid) application = Model(name=name, description=description, application_uuid=application_uuid)
self.session.add(application) with Session(self.engine) as session:
self.session.commit() session.add(application)
self.session.refresh(application) session.commit()
return application.id session.refresh(application)
return application.id
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('An error occurred while creating application %s', err) logging.error('An error occurred while creating application %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -65,10 +67,11 @@ class Application(Repository):
:return: None :return: None
""" """
try: try:
self.session.query(Model).filter( with Session(self.engine) as session:
Model.application_uuid == application_uuid session.query(Model).filter(
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()}) Model.application_uuid == application_uuid
self.session.commit() ).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while updating application %s', err) logging.error('Error while updating application %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -80,9 +83,10 @@ class Application(Repository):
:return: None :return: None
""" """
try: try:
self.session.query(Model).filter(Model.application_uuid == application_uuid).delete() with Session(self.engine) as session:
self.session.flush() session.query(Model).filter(Model.application_uuid == application_uuid).delete()
self.session.commit() session.flush()
session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while deleting application %s', err) logging.error('Error while deleting application %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -94,10 +98,11 @@ class Application(Repository):
:return: Application with the provided application_uuid :return: Application with the provided application_uuid
""" """
try: try:
result_set = self.session.execute(select(Model).where( with Session(self.engine) as session:
Model.application_uuid == application_uuid) result_set = session.execute(select(Model).where(
).first() Model.application_uuid == application_uuid)
return result_set[0] ).first()
return result_set[0]
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching application by application_uuid %s', err) logging.error('Error while fetching application by application_uuid %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err

View File

@ -54,18 +54,18 @@ class City(Repository):
application_id, application_id,
user_id, user_id,
__version__) __version__)
with Session(self.engine) as session:
self.session.add(db_city) session.add(db_city)
self.session.flush() session.flush()
self.session.commit() session.commit()
for building in city.buildings: for building in city.buildings:
db_city_object = CityObject(db_city.id, db_city_object = CityObject(db_city.id,
building) building)
self.session.add(db_city_object) session.add(db_city_object)
self.session.flush() session.flush()
self.session.commit() session.commit()
self.session.refresh(db_city) session.refresh(db_city)
return db_city.id return db_city.id
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('An error occurred while creating a city %s', err) logging.error('An error occurred while creating a city %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -79,8 +79,9 @@ class City(Repository):
""" """
try: try:
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
self.session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now}) with Session(self.engine) as session:
self.session.commit() session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while updating city %s', err) logging.error('Error while updating city %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -92,9 +93,10 @@ class City(Repository):
:return: None :return: None
""" """
try: try:
self.session.query(CityObject).filter(CityObject.city_id == city_id).delete() with Session(self.engine) as session:
self.session.query(Model).filter(Model.id == city_id).delete() session.query(CityObject).filter(CityObject.city_id == city_id).delete()
self.session.commit() session.query(Model).filter(Model.id == city_id).delete()
session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching city %s', err) logging.error('Error while fetching city %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -108,13 +110,12 @@ class City(Repository):
:return: [ModelCity] :return: [ModelCity]
""" """
try: try:
result_set = self.session.execute(select(Model).where(Model.user_id == user_id, with Session(self.engine) as session:
Model.application_id == application_id, result_set = session.execute(select(Model).where(Model.user_id == user_id,
Model.scenario == scenario Model.application_id == application_id,
)).all() Model.scenario == scenario
self.session.close() )).all()
self.session = Session(self.engine) 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)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -127,11 +128,11 @@ class City(Repository):
:return: ModelCity :return: ModelCity
""" """
try: try:
result_set = self.session.execute( with Session(self.engine) as session:
select(Model).where(Model.user_id == user_id, Model.application_id == application_id) result_set = session.execute(
) select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
return [r[0] for r in result_set] )
return [r[0] for r in 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)
raise SQLAlchemyError from err raise SQLAlchemyError from err

View File

@ -6,14 +6,15 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
""" """
import datetime import datetime
import logging import logging
from typing import Union
from sqlalchemy import select, or_ from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from hub.city_model_structure.building import Building from hub.city_model_structure.building import Building
from hub.persistence.repository import Repository
from hub.persistence.models import CityObject as Model from hub.persistence.models import CityObject as Model
from hub.persistence.repository import Repository
class CityObject(Repository): class CityObject(Repository):
@ -46,10 +47,11 @@ class CityObject(Repository):
try: try:
city_object = Model(city_id=city_id, city_object = Model(city_id=city_id,
building=building) building=building)
self.session.add(city_object) with Session(self.engine) as session:
self.session.flush() session.add(city_object)
self.session.commit() session.flush()
self.session.refresh(city_object) session.commit()
session.refresh(city_object)
return city_object.id return city_object.id
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('An error occurred while creating city_object %s', err) logging.error('An error occurred while creating city_object %s', err)
@ -68,17 +70,18 @@ class CityObject(Repository):
for usage in internal_zone.usages: for usage in internal_zone.usages:
object_usage = f'{object_usage}{usage.name}_{usage.percentage} ' object_usage = f'{object_usage}{usage.name}_{usage.percentage} '
object_usage = object_usage.rstrip() object_usage = object_usage.rstrip()
self.session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update( with Session(self.engine) as session:
{'name': building.name, session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
'alias': building.alias, {'name': building.name,
'object_type': building.type, 'alias': building.alias,
'year_of_construction': building.year_of_construction, 'object_type': building.type,
'function': building.function, 'year_of_construction': building.year_of_construction,
'usage': object_usage, 'function': building.function,
'volume': building.volume, 'usage': object_usage,
'area': building.floor_area, 'volume': building.volume,
'updated': datetime.datetime.utcnow()}) 'area': building.floor_area,
self.session.commit() 'updated': datetime.datetime.utcnow()})
session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while updating city object %s', err) logging.error('Error while updating city object %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -91,30 +94,96 @@ class CityObject(Repository):
:return: None :return: None
""" """
try: try:
self.session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete() with Session(self.engine) as session:
self.session.commit() session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
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_or_alias_and_city(self, name, city_id) -> Model: def building_in_cities_info(self, name, cities):
""" """
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 cities: city identifiers
:return: [CityObject] with the provided name or alias belonging to the city with id city_id :return: [CityObject] with the provided name or alias belonging to the city with id city_id
""" """
try: try:
# search by name first # search by name first
city_object = self.session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first() with Session(self.engine) as session:
if city_object is not None: city_object = session.execute(select(Model).where(
return city_object[0] Model.name == name, Model.city_id.in_(cities))
# name not found, so search by alias instead ).first()
city_objects = self.session.execute( if city_object is not None:
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id) return city_object[0]
).all() # name not found, so search by alias instead
self.session.close() city_objects = session.execute(
self.session = Session(self.engine) select(Model).where(Model.aliases.contains(name), Model.city_id.in_(cities))
).all()
for city_object in city_objects:
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
for alias in aliases:
if alias == name:
# force the name as the alias
city_object[0].name = name
return city_object[0]
return None
except SQLAlchemyError as err:
logging.error('Error while fetching city object by name and city: %s', err)
raise SQLAlchemyError from err
except IndexError as err:
logging.error('Error while fetching city object by name and city, empty result %s', err)
raise IndexError from err
def get_by_name_or_alias_and_city(self, name, city_id) -> Union[Model, None]:
"""
Fetch a city object based on name and city id
:param name: city object name
:param city_id: a city identifier
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
"""
try:
# search by name first
with Session(self.engine) as session:
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
if city_object is not None:
return city_object[0]
# name not found, so search by alias instead
city_objects = session.execute(
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
).all()
for city_object in city_objects:
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
for alias in aliases:
if alias == name:
# force the name as the alias
city_object[0].name = name
return city_object[0]
return None
except SQLAlchemyError as err:
logging.error('Error while fetching city object by name and city: %s', err)
raise SQLAlchemyError from err
except IndexError as err:
logging.error('Error while fetching city object by name and city, empty result %s', err)
raise IndexError from err
def get_by_name_or_alias_in_cities(self, name, city_ids) -> Model:
"""
Fetch a city object based on name and city ids
:param name: city object name
:param city_ids: a list of city identifiers
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
"""
try:
# search by name first
with Session(self.engine) as session:
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id.in_(tuple(city_ids)))).first()
if city_object is not None:
return city_object[0]
# name not found, so search by alias instead
city_objects = session.execute(
select(Model).where(Model.aliases.contains(name), Model.city_id.in_(tuple(city_ids)))
).all()
for city_object in city_objects: for city_object in city_objects:
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',') aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
for alias in aliases: for alias in aliases:

View File

@ -10,6 +10,7 @@ import logging
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from hub.persistence.repository import Repository from hub.persistence.repository import Repository
from hub.persistence.models import City from hub.persistence.models import City
@ -52,11 +53,12 @@ class SimulationResults(Repository):
values=values, values=values,
city_id=city_id, city_id=city_id,
city_object_id=city_object_id) city_object_id=city_object_id)
self.session.add(simulation_result) with Session(self.engine) as session:
self.session.flush() session.add(simulation_result)
self.session.commit() session.flush()
self.session.refresh(simulation_result) session.commit()
return simulation_result.id session.refresh(simulation_result)
return simulation_result.id
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('An error occurred while creating city_object %s', err) logging.error('An error occurred while creating city_object %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -71,22 +73,23 @@ class SimulationResults(Repository):
:return: None :return: None
""" """
try: try:
if city_id is not None: with Session(self.engine) as session:
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).update( if city_id is not None:
session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
{ {
'values': values, 'values': values,
'updated': datetime.datetime.utcnow() 'updated': datetime.datetime.utcnow()
}) })
self.session.commit() session.commit()
elif city_object_id is not None: elif city_object_id is not None:
self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update( session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
{ {
'values': values, 'values': values,
'updated': datetime.datetime.utcnow() 'updated': datetime.datetime.utcnow()
}) })
self.session.commit() session.commit()
else: else:
raise NotImplementedError('Missing either city_id or city_object_id') raise NotImplementedError('Missing either city_id or city_object_id')
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while updating city object %s', err) logging.error('Error while updating city object %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -100,14 +103,15 @@ class SimulationResults(Repository):
:return: None :return: None
""" """
try: try:
if city_id is not None: with Session(self.engine) as session:
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete() if city_id is not None:
self.session.commit() session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
elif city_object_id is not None: session.commit()
self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete() elif city_object_id is not None:
self.session.commit() session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
else: session.commit()
raise NotImplementedError('Missing either city_id or city_object_id') else:
raise NotImplementedError('Missing either city_id or city_object_id')
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while deleting application: %s', err) logging.error('Error while deleting application: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -119,7 +123,8 @@ class SimulationResults(Repository):
:return: [City] with the provided city_id :return: [City] with the provided city_id
""" """
try: try:
return self.session.execute(select(City).where(City.id == city_id)).first() with Session(self.engine) as session:
return session.execute(select(City).where(City.id == city_id)).first()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching city by city_id: %s', err) logging.error('Error while fetching city by city_id: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -131,7 +136,8 @@ class SimulationResults(Repository):
:return: [CityObject] with the provided city_object_id :return: [CityObject] with the provided city_object_id
""" """
try: try:
return self.session.execute(select(CityObject).where(CityObject.id == city_object_id)).first() with Session(self.engine) as session:
return session.execute(select(CityObject).where(CityObject.id == city_object_id)).first()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching city by city_id: %s', err) logging.error('Error while fetching city by city_id: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -145,18 +151,43 @@ class SimulationResults(Repository):
:return: [SimulationResult] :return: [SimulationResult]
""" """
try: try:
result_set = self.session.execute(select(Model).where(or_( with Session(self.engine) as session:
Model.city_id == city_id, result_set = session.execute(select(Model).where(or_(
Model.city_object_id == city_object_id Model.city_id == city_id,
))) Model.city_object_id == city_object_id
results = [r[0] for r in result_set] )))
if not result_names: results = [r[0] for r in result_set]
return results if not result_names:
filtered_results = [] return results
for result in results: filtered_results = []
if result.name in result_names: for result in results:
filtered_results.append(result) if result.name in result_names:
return filtered_results filtered_results.append(result)
return filtered_results
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching city by city_id: %s', err) logging.error('Error while fetching city by city_id: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
def get_simulation_results_by_city_object_id_and_names(self, city_object_id, result_names=None) -> [Model]:
"""
Fetch the simulation results based in the city_object_id with the given names or all
:param city_object_id: the city object id
:param result_names: if given filter the results
:return: [SimulationResult]
"""
try:
with Session(self.engine) as session:
result_set = session.execute(select(Model).where(
Model.city_object_id == city_object_id
))
results = [r[0] for r in result_set]
if not result_names:
return results
filtered_results = []
for result in results:
if result.name in result_names:
filtered_results.append(result)
return filtered_results
except SQLAlchemyError as err:
logging.error('Error while fetching city by city_id: %s', err)
raise SQLAlchemyError from err

View File

@ -9,6 +9,7 @@ import logging
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from hub.helpers.auth import Auth from hub.helpers.auth import Auth
from hub.persistence.repository import Repository from hub.persistence.repository import Repository
@ -49,10 +50,11 @@ class User(Repository):
pass pass
try: try:
user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id) user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id)
self.session.add(user) with Session(self.engine) as session:
self.session.flush() session.add(user)
self.session.commit() session.flush()
self.session.refresh(user) session.commit()
session.refresh(user)
return user.id return user.id
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('An error occurred while creating user %s', err) logging.error('An error occurred while creating user %s', err)
@ -68,13 +70,14 @@ class User(Repository):
:return: None :return: None
""" """
try: try:
self.session.query(Model).filter(Model.id == user_id).update({ with Session(self.engine) as session:
'name': name, session.query(Model).filter(Model.id == user_id).update({
'password': Auth.hash_password(password), 'name': name,
'role': role, 'password': Auth.hash_password(password),
'updated': datetime.datetime.utcnow() 'role': role,
}) 'updated': datetime.datetime.utcnow()
self.session.commit() })
session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while updating user: %s', err) logging.error('Error while updating user: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -86,8 +89,9 @@ class User(Repository):
:return: None :return: None
""" """
try: try:
self.session.query(Model).filter(Model.id == user_id).delete() with Session(self.engine) as session:
self.session.commit() session.query(Model).filter(Model.id == user_id).delete()
session.commit()
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching user: %s', err) logging.error('Error while fetching user: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -100,10 +104,12 @@ class User(Repository):
:return: User matching the search criteria or None :return: User matching the search criteria or None
""" """
try: try:
user = self.session.execute( with Session(self.engine) as session:
select(Model).where(Model.name == name, Model.application_id == application_id) user = session.execute(
).first() select(Model).where(Model.name == name, Model.application_id == application_id)
return user[0] ).first()
session.commit()
return user[0]
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching user by name and application: %s', err) logging.error('Error while fetching user by name and application: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -120,12 +126,13 @@ class User(Repository):
:return: User :return: User
""" """
try: try:
user = self.session.execute( with Session(self.engine) as session:
select(Model).where(Model.name == name, Model.application_id == application_id) user = session.execute(
).first() select(Model).where(Model.name == name, Model.application_id == application_id)
if user: ).first()
if Auth.check_password(password, user[0].password): if user:
return user[0] if Auth.check_password(password, user[0].password):
return user[0]
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching user by name: %s', err) logging.error('Error while fetching user by name: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err
@ -140,10 +147,11 @@ class User(Repository):
:return: User :return: User
""" """
try: try:
application = self.session.execute( with Session(self.engine) as session:
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid) application = session.execute(
).first() select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
return self.get_by_name_application_id_and_password(name, password, application[0].id) ).first()
return self.get_by_name_application_id_and_password(name, password, application[0].id)
except SQLAlchemyError as err: except SQLAlchemyError as err:
logging.error('Error while fetching user by name: %s', err) logging.error('Error while fetching user by name: %s', err)
raise SQLAlchemyError from err raise SQLAlchemyError from err

View File

@ -6,7 +6,6 @@ Project Coder Peter Yefi peteryefi@gmail.com
""" """
import logging import logging
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from hub.persistence.configuration import Configuration from hub.persistence.configuration import Configuration
@ -19,6 +18,5 @@ class Repository:
try: try:
self.configuration = Configuration(db_name, dotenv_path, app_env) self.configuration = Configuration(db_name, dotenv_path, app_env)
self.engine = create_engine(self.configuration.connection_string) self.engine = create_engine(self.configuration.connection_string)
self.session = Session(self.engine)
except ValueError as err: except ValueError as err:
logging.error('Missing value for credentials: %s', err) logging.error('Missing value for credentials: %s', err)

View File

@ -1,4 +1,4 @@
""" """
Hub version number Hub version number
""" """
__version__ = '0.1.8.4' __version__ = '0.1.8.34'

View File

@ -18,24 +18,22 @@ with open(version) as f:
exec(f.read(), main_ns) exec(f.read(), main_ns)
setup( setup(
name='cerc-hub', name='cerc-hub',
version=main_ns['__version__'], version=main_ns['__version__'],
description="CERC Hub consist in a set of classes (Central data model), importers and exporters to help researchers " description="CERC Hub consist of a set of classes (Central data model), importers and exporters to help researchers "
"to create better and sustainable cities", "to create better and more sustainable cities",
long_description="CERC Hub consist in a set of classes (Central data model), importers and exporters to help " long_description="CERC Hub consist of a set of classes (Central data model), importers and exporters to help "
"researchers to create better and sustainable cities.\n\nDevelop at Concordia university in canada " "researchers to create better and more sustainable cities.\n\nDeveloped at Concordia university in Canada "
"as part of the research group from the next generation cities institute our aim among others it's " "as part of the research group from the Next Generation Cities Institute, our aim among others is "
"to provide a comprehensive set of tools to help researchers and urban developers to make decisions " "to provide a comprehensive set of tools to help researchers and urban developers to make decisions "
"to improve the livability and efficiency of our cities", "to improve the livability and efficiency of our cities",
classifiers=[ classifiers=[
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
], ],
include_package_data=True, include_package_data=True,
packages=['hub', packages=['hub',
'hub.catalog_factories', 'hub.catalog_factories',
'hub.catalog_factories.construction', 'hub.catalog_factories.construction',
@ -88,7 +86,8 @@ setup(
data_files=[ data_files=[
('hub', glob.glob('requirements.txt')), ('hub', glob.glob('requirements.txt')),
('hub/config', glob.glob('hub/config/*.ini')), ('hub/config', glob.glob('hub/config/*.ini')),
('hub/catalog_factories/greenery/ecore_greenery', glob.glob('hub/catalog_factories/greenery/ecore_greenery/*.ecore')), ('hub/catalog_factories/greenery/ecore_greenery',
glob.glob('hub/catalog_factories/greenery/ecore_greenery/*.ecore')),
('hub/data/construction', glob.glob('hub/data/construction/*')), ('hub/data/construction', glob.glob('hub/data/construction/*')),
('hub/data/costs', glob.glob('hub/data/costs/montreal_costs.xml')), ('hub/data/costs', glob.glob('hub/data/costs/montreal_costs.xml')),
('hub/data/customized_imports', glob.glob('hub/data/customized_imports/ashrae_archetypes.xml')), ('hub/data/customized_imports', glob.glob('hub/data/customized_imports/ashrae_archetypes.xml')),
@ -109,4 +108,4 @@ setup(
('hub/exports/building_energy/idf_files', glob.glob('hub/exports/building_energy/idf_files/*.idd')) ('hub/exports/building_energy/idf_files', glob.glob('hub/exports/building_energy/idf_files/*.idd'))
], ],
) )

View File

@ -103,16 +103,16 @@ class Control:
app_env='TEST', app_env='TEST',
dotenv_path=dotenv_path) dotenv_path=dotenv_path)
self._application_uuid = '60b7fc1b-f389-4254-9ffd-22a4cf32c7a3' self._application_uuid = 'b9e0ce80-1218-410c-8a64-9d9b7026aad8'
self._application_id = 1 self._application_id = 1
self._user_id = 1 self._user_id = 1
self._application_id = self._database.persist_application( self._application_id = self._database.persist_application(
'City_layers', 'test',
'City layers test user', 'test',
self.application_uuid self.application_uuid
) )
self._user_id = self._database.create_user('city_layers', self._application_id, 'city_layers', UserRoles.Admin) self._user_id = self._database.create_user('test', self._application_id, 'test', UserRoles.Admin)
self._pickle_path = Path('tests_data/pickle_path.bz2').resolve() self._pickle_path = Path('tests_data/pickle_path.bz2').resolve()
@ -248,36 +248,36 @@ TestDBFactory
for x in building.onsite_electrical_production[cte.MONTH]] for x in building.onsite_electrical_production[cte.MONTH]]
yearly_on_site_electrical_production = [x * cte.WATTS_HOUR_TO_JULES yearly_on_site_electrical_production = [x * cte.WATTS_HOUR_TO_JULES
for x in building.onsite_electrical_production[cte.YEAR]] for x in building.onsite_electrical_production[cte.YEAR]]
results = json.dumps({cte.INSEL_MEB: [ results = {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_lighting_peak_load': monthly_lighting_peak_load}, 'monthly_lighting_peak_load': monthly_lighting_peak_load,
{'yearly_lighting_peak_load': yearly_lighting_peak_load}, 'yearly_lighting_peak_load': yearly_lighting_peak_load,
{'monthly_appliances_peak_load': monthly_appliances_peak_load}, 'monthly_appliances_peak_load': monthly_appliances_peak_load,
{'yearly_appliances_peak_load': yearly_appliances_peak_load}, 'yearly_appliances_peak_load': yearly_appliances_peak_load,
{'monthly_cooling_demand': monthly_cooling_demand}, 'monthly_cooling_demand': monthly_cooling_demand,
{'yearly_cooling_demand': yearly_cooling_demand}, 'yearly_cooling_demand': yearly_cooling_demand,
{'monthly_heating_demand': monthly_heating_demand}, 'monthly_heating_demand': monthly_heating_demand,
{'yearly_heating_demand': yearly_heating_demand}, 'yearly_heating_demand': yearly_heating_demand,
{'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand}, 'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand,
{'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand}, 'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand,
{'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand}, 'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand,
{'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand}, 'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand,
{'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand}, 'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand,
{'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand}, 'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand,
{'monthly_heating_consumption': monthly_heating_consumption}, 'monthly_heating_consumption': monthly_heating_consumption,
{'yearly_heating_consumption': yearly_heating_consumption}, 'yearly_heating_consumption': yearly_heating_consumption,
{'monthly_cooling_consumption': monthly_cooling_consumption}, 'monthly_cooling_consumption': monthly_cooling_consumption,
{'yearly_cooling_consumption': yearly_cooling_consumption}, 'yearly_cooling_consumption': yearly_cooling_consumption,
{'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption}, 'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption,
{'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption}, 'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption,
{'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption}, 'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption,
{'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption}, 'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption,
{'monthly_on_site_electrical_production': monthly_on_site_electrical_production}, 'monthly_on_site_electrical_production': monthly_on_site_electrical_production,
{'yearly_on_site_electrical_production': yearly_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)

View File

@ -5,19 +5,20 @@ Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
import json
import logging.handlers import os
from pathlib import Path from pathlib import Path
from unittest import TestCase from unittest import TestCase
from hub.imports.geometry_factory import GeometryFactory
from hub.helpers.dictionaries import Dictionaries
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
from hub.exports.exports_factory import ExportsFactory
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
import hub.helpers.constants as cte import hub.helpers.constants as cte
from hub.city_model_structure.city import City from hub.city_model_structure.city import City
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
from hub.exports.exports_factory import ExportsFactory
from hub.helpers.dictionaries import Dictionaries
from hub.imports.construction_factory import ConstructionFactory
from hub.imports.geometry_factory import GeometryFactory
from hub.imports.usage_factory import UsageFactory
from hub.imports.weather_factory import WeatherFactory
class TestExports(TestCase): class TestExports(TestCase):
@ -66,12 +67,7 @@ class TestExports(TestCase):
def _export(self, export_type, from_pickle=False): def _export(self, export_type, from_pickle=False):
self._complete_city = self._get_complete_city(from_pickle) self._complete_city = self._get_complete_city(from_pickle)
try: ExportsFactory(export_type, self._complete_city, self._output_path, base_uri='../glb').export()
ExportsFactory(export_type, self._complete_city, self._output_path).export()
except ValueError as err:
if export_type != 'stl':
logging.warning('No backend export for STL test, skipped')
raise err
def _export_building_energy(self, export_type, from_pickle=False): def _export_building_energy(self, export_type, from_pickle=False):
self._complete_city = self._get_complete_city(from_pickle) self._complete_city = self._get_complete_city(from_pickle)
@ -83,11 +79,38 @@ class TestExports(TestCase):
""" """
self._export('obj', False) self._export('obj', False)
def test_stl_export(self): def test_cesiumjs_tileset_export(self):
""" """
export to stl export to cesiumjs tileset
""" """
self._export('stl', False) self._export('cesiumjs_tileset', False)
tileset = Path(self._output_path / f'{self._city.name}.json')
self.assertTrue(tileset.exists())
with open(tileset, 'r') as f:
json_tileset = json.load(f)
self.assertEqual(1, len(json_tileset['root']['children']), "Wrong number of children")
def test_glb_export(self):
"""
export to glb format
"""
self._export('glb', False)
for building in self._city.buildings:
glb_file = Path(self._output_path / f'{building.name}.glb')
self.assertTrue(glb_file.exists(), f'{building.name} Building glb wasn\'t correctly generated')
def test_geojson_export(self):
self._export('geojson', False)
geojson_file = Path(self._output_path / f'{self._city.name}.geojson')
self.assertTrue(geojson_file.exists(), f'{geojson_file} doesn\'t exists')
with open(geojson_file, 'r') as f:
geojson = json.load(f)
self.assertEqual(1, len(geojson['features']), 'Wrong number of buildings')
geometry = geojson['features'][0]['geometry']
self.assertEqual('Polygon', geometry['type'], 'Wrong geometry type')
self.assertEqual(1, len(geometry['coordinates']), 'Wrong polygon structure')
self.assertEqual(11, len(geometry['coordinates'][0]), 'Wrong number of vertices')
os.unlink(geojson_file) # todo: this test need to cover a multipolygon example too
def test_energy_ade_export(self): def test_energy_ade_export(self):
""" """
@ -125,4 +148,3 @@ class TestExports(TestCase):
EnergyBuildingsExportsFactory('idf', city, self._output_path).export() EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
except Exception: except Exception:
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!") self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")

View File

@ -40,7 +40,7 @@ class TestResultsImport(TestCase):
function_to_hub=Dictionaries().montreal_function_to_hub_function).city function_to_hub=Dictionaries().montreal_function_to_hub_function).city
ConstructionFactory('nrcan', self._city).enrich() ConstructionFactory('nrcan', self._city).enrich()
UsageFactory('nrcan', self._city).enrich() UsageFactory('comnet', self._city).enrich()
def test_sra_import(self): def test_sra_import(self):
ExportsFactory('sra', self._city, self._output_path).export() ExportsFactory('sra', self._city, self._output_path).export()