Merge remote-tracking branch 'origin/main' into Stochastic_occupancy_model
# Conflicts: # .gitignore
14
.gitignore
vendored
|
@ -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
|
||||||
|
|
3
cerc_hub.egg-info/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
# Except this file
|
|
||||||
*
|
|
||||||
!.gitignore
|
|
12
hub/.gitignore
vendored
|
@ -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
|
@ -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.
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
values_months = []
|
||||||
|
for month in cte.WEEK_DAYS_A_MONTH.keys():
|
||||||
|
_total_hours_month = 0
|
||||||
for key in _working_hours:
|
for key in _working_hours:
|
||||||
hours = sum(_working_hours[key])
|
hours = sum(_working_hours[key])
|
||||||
_total_hours += hours * cte.WEEK_DAYS_A_YEAR[key]
|
_total_hours_month += hours * cte.WEEK_DAYS_A_MONTH[month][key]
|
||||||
return _total_hours
|
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]
|
||||||
|
|
|
@ -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]:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]:
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
BIN
hub/docs/img_windows_install/img_34.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
hub/docs/img_windows_install/img_35.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
hub/docs/img_windows_install/img_36.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
hub/docs/img_windows_install/img_37.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
hub/docs/img_windows_install/img_38.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
hub/docs/img_windows_install/img_39.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
hub/docs/img_windows_install/img_40.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
hub/docs/img_windows_install/img_41.png
Normal file
After Width: | Height: | Size: 23 KiB |
|
@ -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,8 +679,7 @@ 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'
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
159
hub/exports/formats/cesiumjs_tileset.py
Normal 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)
|
112
hub/exports/formats/geojson.py
Normal 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
|
||||||
|
|
||||||
|
|
54
hub/exports/formats/glb.py
Normal 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
|
|
@ -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:
|
|
||||||
obj.write("# cerc-hub export\n")
|
|
||||||
vertex_index = 0
|
|
||||||
faces = []
|
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(f'mtllib {mtl_name}\n')
|
||||||
|
|
||||||
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 = []
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -623,7 +623,11 @@ 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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -63,8 +63,7 @@ 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)
|
||||||
|
@ -76,8 +75,7 @@ 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)
|
||||||
|
@ -89,8 +87,7 @@ 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
|
||||||
|
@ -101,8 +98,7 @@ 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
|
||||||
|
@ -115,8 +111,7 @@ 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
|
||||||
|
@ -137,8 +132,7 @@ 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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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']:
|
||||||
|
|
|
@ -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']:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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,6 +43,9 @@ class InselMonthlyEnergyBalance:
|
||||||
domestic_hot_water_demand = []
|
domestic_hot_water_demand = []
|
||||||
lighting_demand = []
|
lighting_demand = []
|
||||||
appliances_demand = []
|
appliances_demand = []
|
||||||
|
|
||||||
|
# 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:
|
if building.internal_zones[0].thermal_zones_from_internal_zones is None:
|
||||||
domestic_hot_water_demand = [0] * 12
|
domestic_hot_water_demand = [0] * 12
|
||||||
lighting_demand = [0] * 12
|
lighting_demand = [0] * 12
|
||||||
|
@ -56,7 +59,7 @@ class InselMonthlyEnergyBalance:
|
||||||
lighting_density = thermal_zone.lighting.density
|
lighting_density = thermal_zone.lighting.density
|
||||||
appliances_density = thermal_zone.appliances.density
|
appliances_density = thermal_zone.appliances.density
|
||||||
|
|
||||||
for month in range(0, 12):
|
for i_month, month in enumerate(cte.MONTHS):
|
||||||
total_dhw_demand = 0
|
total_dhw_demand = 0
|
||||||
total_lighting = 0
|
total_lighting = 0
|
||||||
total_appliances = 0
|
total_appliances = 0
|
||||||
|
@ -66,7 +69,7 @@ class InselMonthlyEnergyBalance:
|
||||||
for value in schedule.values:
|
for value in schedule.values:
|
||||||
total_day += value
|
total_day += value
|
||||||
for day_type in schedule.day_types:
|
for day_type in schedule.day_types:
|
||||||
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] \
|
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
|
||||||
* lighting_density / cte.WATTS_HOUR_TO_JULES
|
* lighting_density / cte.WATTS_HOUR_TO_JULES
|
||||||
lighting_demand.append(total_lighting * area)
|
lighting_demand.append(total_lighting * area)
|
||||||
|
|
||||||
|
@ -75,7 +78,7 @@ class InselMonthlyEnergyBalance:
|
||||||
for value in schedule.values:
|
for value in schedule.values:
|
||||||
total_day += value
|
total_day += value
|
||||||
for day_type in schedule.day_types:
|
for day_type in schedule.day_types:
|
||||||
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] \
|
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
|
||||||
* appliances_density / cte.WATTS_HOUR_TO_JULES
|
* appliances_density / cte.WATTS_HOUR_TO_JULES
|
||||||
appliances_demand.append(total_appliances * area)
|
appliances_demand.append(total_appliances * area)
|
||||||
|
|
||||||
|
@ -86,10 +89,15 @@ class InselMonthlyEnergyBalance:
|
||||||
for day_type in schedule.day_types:
|
for day_type in schedule.day_types:
|
||||||
demand = (
|
demand = (
|
||||||
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
|
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
|
||||||
* (service_temperature - cold_water[month]) / cte.WATTS_HOUR_TO_JULES
|
* (service_temperature - cold_water[i_month]) / cte.WATTS_HOUR_TO_JULES
|
||||||
)
|
)
|
||||||
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] * demand
|
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] * demand
|
||||||
domestic_hot_water_demand.append(total_dhw_demand * area)
|
domestic_hot_water_demand.append(total_dhw_demand * area)
|
||||||
|
except AttributeError:
|
||||||
|
domestic_hot_water_demand = [0] * 12
|
||||||
|
lighting_demand = [0] * 12
|
||||||
|
appliances_demand = [0] * 12
|
||||||
|
logging.warning('Building internal zone raised an error, most likely the building has missing archetypes')
|
||||||
|
|
||||||
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)]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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?
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,8 @@ 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
|
||||||
|
@ -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:
|
|
||||||
city_id = result_set[0].id
|
|
||||||
|
|
||||||
results[scenario_name] = []
|
results[scenario_name] = []
|
||||||
|
city_ids = [r[0].id for r in result_sets]
|
||||||
for building_name in scenario[scenario_name]:
|
for building_name in scenario[scenario_name]:
|
||||||
_building = self._city_object.get_by_name_or_alias_and_city(building_name, city_id)
|
_building = self._city_object.get_by_name_or_alias_in_cities(building_name, city_ids)
|
||||||
if _building is None:
|
if _building is None:
|
||||||
continue
|
continue
|
||||||
city_object_id = _building.id
|
city_object_id = _building.id
|
||||||
_ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names(
|
_ = self._simulation_results.get_simulation_results_by_city_object_id_and_names(
|
||||||
city_id,
|
|
||||||
city_object_id,
|
city_object_id,
|
||||||
result_names)
|
result_names)
|
||||||
|
|
||||||
for value in _:
|
for value in _:
|
||||||
values = json.loads(value.values)
|
values = value.values
|
||||||
values["building"] = building_name
|
values["building"] = building_name
|
||||||
results[scenario_name].append(values)
|
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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
wall_area = 0
|
||||||
|
window_ratio = 0
|
||||||
|
try:
|
||||||
if storeys is None:
|
if storeys is None:
|
||||||
storeys = building.max_height / building.average_storey_height
|
storeys = building.max_height / building.average_storey_height
|
||||||
self.total_heating_area = building.floor_area * storeys
|
|
||||||
wall_area = 0
|
|
||||||
for wall in building.walls:
|
|
||||||
wall_area += wall.solid_polygon.area
|
|
||||||
self.wall_area = wall_area
|
|
||||||
window_ratio = 0
|
|
||||||
for internal_zone in building.internal_zones:
|
for internal_zone in building.internal_zones:
|
||||||
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:
|
||||||
window_ratio = thermal_boundary.window_ratio
|
window_ratio = thermal_boundary.window_ratio
|
||||||
break
|
break
|
||||||
|
except TypeError:
|
||||||
|
storeys = 0
|
||||||
|
logging.warning(
|
||||||
|
'building %s has no storey height so heating area, storeys and window ratio cannot be calculated',
|
||||||
|
self.name
|
||||||
|
)
|
||||||
|
self.total_heating_area = building.floor_area * storeys
|
||||||
|
|
||||||
|
for wall in building.walls:
|
||||||
|
wall_area += wall.solid_polygon.area
|
||||||
|
self.wall_area = wall_area
|
||||||
self.windows_area = wall_area * window_ratio
|
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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,9 +49,10 @@ 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()
|
||||||
|
session.refresh(application)
|
||||||
return application.id
|
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)
|
||||||
|
@ -65,10 +67,11 @@ class Application(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.session.query(Model).filter(
|
with Session(self.engine) as session:
|
||||||
|
session.query(Model).filter(
|
||||||
Model.application_uuid == application_uuid
|
Model.application_uuid == application_uuid
|
||||||
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
|
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
|
||||||
self.session.commit()
|
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,7 +98,8 @@ 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:
|
||||||
|
result_set = session.execute(select(Model).where(
|
||||||
Model.application_uuid == application_uuid)
|
Model.application_uuid == application_uuid)
|
||||||
).first()
|
).first()
|
||||||
return result_set[0]
|
return result_set[0]
|
||||||
|
|
|
@ -54,17 +54,17 @@ 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)
|
||||||
|
@ -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,12 +110,11 @@ 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:
|
||||||
|
result_set = session.execute(select(Model).where(Model.user_id == user_id,
|
||||||
Model.application_id == application_id,
|
Model.application_id == application_id,
|
||||||
Model.scenario == scenario
|
Model.scenario == scenario
|
||||||
)).all()
|
)).all()
|
||||||
self.session.close()
|
|
||||||
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)
|
||||||
|
@ -127,11 +128,11 @@ class City(Repository):
|
||||||
:return: ModelCity
|
:return: ModelCity
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result_set = self.session.execute(
|
with Session(self.engine) as session:
|
||||||
|
result_set = session.execute(
|
||||||
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
|
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
|
||||||
|
|
||||||
|
|
|
@ -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,7 +70,8 @@ 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:
|
||||||
|
session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
|
||||||
{'name': building.name,
|
{'name': building.name,
|
||||||
'alias': building.alias,
|
'alias': building.alias,
|
||||||
'object_type': building.type,
|
'object_type': building.type,
|
||||||
|
@ -78,7 +81,7 @@ class CityObject(Repository):
|
||||||
'volume': building.volume,
|
'volume': building.volume,
|
||||||
'area': building.floor_area,
|
'area': building.floor_area,
|
||||||
'updated': datetime.datetime.utcnow()})
|
'updated': datetime.datetime.utcnow()})
|
||||||
self.session.commit()
|
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:
|
||||||
|
city_object = session.execute(select(Model).where(
|
||||||
|
Model.name == name, Model.city_id.in_(cities))
|
||||||
|
).first()
|
||||||
if city_object is not None:
|
if city_object is not None:
|
||||||
return city_object[0]
|
return city_object[0]
|
||||||
# name not found, so search by alias instead
|
# name not found, so search by alias instead
|
||||||
city_objects = self.session.execute(
|
city_objects = session.execute(
|
||||||
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
|
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()
|
).all()
|
||||||
self.session.close()
|
|
||||||
self.session = Session(self.engine)
|
|
||||||
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:
|
||||||
|
|
|
@ -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,10 +53,11 @@ 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()
|
||||||
|
session.refresh(simulation_result)
|
||||||
return simulation_result.id
|
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)
|
||||||
|
@ -71,20 +73,21 @@ class SimulationResults(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
if city_id is not None:
|
if city_id is not None:
|
||||||
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
|
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:
|
||||||
|
@ -100,12 +103,13 @@ class SimulationResults(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
if city_id is not None:
|
if city_id is not None:
|
||||||
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
|
session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
|
||||||
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).delete()
|
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
|
||||||
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:
|
||||||
|
@ -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,7 +151,8 @@ class SimulationResults(Repository):
|
||||||
:return: [SimulationResult]
|
:return: [SimulationResult]
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result_set = self.session.execute(select(Model).where(or_(
|
with Session(self.engine) as session:
|
||||||
|
result_set = session.execute(select(Model).where(or_(
|
||||||
Model.city_id == city_id,
|
Model.city_id == city_id,
|
||||||
Model.city_object_id == city_object_id
|
Model.city_object_id == city_object_id
|
||||||
)))
|
)))
|
||||||
|
@ -160,3 +167,27 @@ class SimulationResults(Repository):
|
||||||
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
|
|
@ -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:
|
||||||
|
session.query(Model).filter(Model.id == user_id).update({
|
||||||
'name': name,
|
'name': name,
|
||||||
'password': Auth.hash_password(password),
|
'password': Auth.hash_password(password),
|
||||||
'role': role,
|
'role': role,
|
||||||
'updated': datetime.datetime.utcnow()
|
'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,9 +104,11 @@ 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:
|
||||||
|
user = session.execute(
|
||||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||||
).first()
|
).first()
|
||||||
|
session.commit()
|
||||||
return user[0]
|
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)
|
||||||
|
@ -120,7 +126,8 @@ class User(Repository):
|
||||||
:return: User
|
:return: User
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
user = self.session.execute(
|
with Session(self.engine) as session:
|
||||||
|
user = session.execute(
|
||||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||||
).first()
|
).first()
|
||||||
if user:
|
if user:
|
||||||
|
@ -140,7 +147,8 @@ class User(Repository):
|
||||||
:return: User
|
:return: User
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
application = self.session.execute(
|
with Session(self.engine) as session:
|
||||||
|
application = session.execute(
|
||||||
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
|
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
|
||||||
).first()
|
).first()
|
||||||
return self.get_by_name_application_id_and_password(name, password, application[0].id)
|
return self.get_by_name_application_id_and_password(name, password, application[0].id)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""
|
"""
|
||||||
Hub version number
|
Hub version number
|
||||||
"""
|
"""
|
||||||
__version__ = '0.1.8.4'
|
__version__ = '0.1.8.34'
|
||||||
|
|
15
setup.py
|
@ -18,16 +18,14 @@ 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=[
|
||||||
|
@ -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')),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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!")
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|