Merge remote-tracking branch 'origin/main' into Stochastic_occupancy_model
# Conflicts: # .gitignore
14
.gitignore
vendored
@ -1,2 +1,12 @@
|
||||
# Default ignored files
|
||||
.idea
|
||||
!.gitignore
|
||||
**/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
|
||||
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
|
||||
g
|
||||
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:
|
||||
* Miniconda
|
||||
* SRA Files
|
||||
* 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
|
||||
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.
|
||||
|
||||
### 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
|
||||
|
||||
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,
|
||||
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)
|
||||
|
||||
@ -70,14 +93,12 @@ You can find it also at **Git->Clone...**
|
||||
|
||||
![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)
|
||||
|
||||
(You can also copy this URL by going to the Hub repository in [Gitlab](https://rs-loy-gitlab.concordia.ca/Guille/hub.git)
|
||||
and clicking on the **Copy URL** button, next to **Clone with HTTPS**)
|
||||
|
||||
![gitlab get https](docs/img_windows_install/img_17.png)
|
||||
![gitea get https](docs/img_windows_install/img_39.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.
|
||||
|
||||
@ -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.
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
## 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.
|
||||
|
||||
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)).
|
||||
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.
|
||||
|
||||
![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.
|
||||
|
||||
## 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
|
||||
and then click on the **Copy URL** button, next to the **Clone with HTTPS** link.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
**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!
|
||||
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.
|
||||
|
@ -188,7 +188,7 @@ class EilatCatalog(Catalog):
|
||||
schedules_key = {}
|
||||
for j in range(0, number_usage_types):
|
||||
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()
|
||||
plug_loads_data[usage_type] = usage_parameters[8:13].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_y = min(self._min_y, surface.lower_corner[1])
|
||||
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
|
||||
if surface.type == cte.GROUND:
|
||||
self._grounds.append(surface)
|
||||
@ -440,8 +443,7 @@ class Building(CityObject):
|
||||
"""
|
||||
results = {}
|
||||
if cte.HOUR in self.heating_demand:
|
||||
monthly_values = PeakLoads().\
|
||||
peak_loads_from_hourly(self.heating_demand[cte.HOUR])
|
||||
monthly_values = PeakLoads().peak_loads_from_hourly(self.heating_demand[cte.HOUR])
|
||||
else:
|
||||
monthly_values = PeakLoads(self).heating_peak_loads_from_methodology
|
||||
if monthly_values is None:
|
||||
@ -682,11 +684,17 @@ class Building(CityObject):
|
||||
for i, value in enumerate(item):
|
||||
_working_hours[key][i] = max(_working_hours[key][i], saved_values[i])
|
||||
|
||||
_total_hours = 0
|
||||
for key in _working_hours:
|
||||
hours = sum(_working_hours[key])
|
||||
_total_hours += hours * cte.WEEK_DAYS_A_YEAR[key]
|
||||
return _total_hours
|
||||
working_hours = {}
|
||||
values_months = []
|
||||
for month in cte.WEEK_DAYS_A_MONTH.keys():
|
||||
_total_hours_month = 0
|
||||
for key in _working_hours:
|
||||
hours = sum(_working_hours[key])
|
||||
_total_hours_month += hours * cte.WEEK_DAYS_A_MONTH[month][key]
|
||||
values_months.append(_total_hours_month)
|
||||
working_hours[cte.MONTH] = values_months
|
||||
working_hours[cte.YEAR] = sum(working_hours[cte.MONTH])
|
||||
return working_hours
|
||||
|
||||
@property
|
||||
def distribution_systems_electrical_consumption(self):
|
||||
@ -735,8 +743,13 @@ class Building(CityObject):
|
||||
|
||||
for key, item in self._distribution_systems_electrical_consumption.items():
|
||||
for i in range(0, len(item)):
|
||||
self._distribution_systems_electrical_consumption[key][i] += _peak_load * _consumption_fix_flow \
|
||||
* _working_hours
|
||||
_working_hours_value = _working_hours[key]
|
||||
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
|
||||
|
||||
def _calculate_consumption(self, consumption_type, demand):
|
||||
@ -797,3 +810,17 @@ class Building(CityObject):
|
||||
orientation_losses_factor[_key]['south'])]
|
||||
self._onsite_electrical_production[_key] = _results
|
||||
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):
|
||||
self._type = None
|
||||
self._name = None
|
||||
self._layers = None
|
||||
self._window_ratio = None
|
||||
self._window_frame_ratio = None
|
||||
@ -37,6 +38,22 @@ class Construction:
|
||||
"""
|
||||
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
|
||||
def layers(self) -> [Layer]:
|
||||
"""
|
||||
|
@ -130,6 +130,7 @@ class InternalZone:
|
||||
for hole in surface.holes_polygons:
|
||||
windows_areas.append(hole.area)
|
||||
_thermal_boundary = ThermalBoundary(surface, surface.solid_polygon.area, windows_areas)
|
||||
surface.associated_thermal_boundaries = [_thermal_boundary]
|
||||
_thermal_boundaries.append(_thermal_boundary)
|
||||
_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)
|
||||
|
@ -16,7 +16,7 @@ class Layer:
|
||||
def __init__(self):
|
||||
self._thickness = None
|
||||
self._id = None
|
||||
self._name = None
|
||||
self._material_name = None
|
||||
self._conductivity = None
|
||||
self._specific_heat = None
|
||||
self._density = None
|
||||
@ -54,20 +54,20 @@ class Layer:
|
||||
self._thickness = float(value)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def material_name(self):
|
||||
"""
|
||||
Get material name
|
||||
:return: str
|
||||
"""
|
||||
return self._name
|
||||
return self._material_name
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
@material_name.setter
|
||||
def material_name(self, value):
|
||||
"""
|
||||
Set material name
|
||||
:param value: string
|
||||
"""
|
||||
self._name = str(value)
|
||||
self._material_name = str(value)
|
||||
|
||||
@property
|
||||
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.building_demand.thermal_boundary import ThermalBoundary
|
||||
import hub.helpers.constants as cte
|
||||
from hub.helpers.configuration_helper import ConfigurationHelper
|
||||
|
||||
|
||||
class Surface:
|
||||
@ -154,7 +155,6 @@ class Surface:
|
||||
if self._inclination is None:
|
||||
self._inclination = np.arccos(self.perimeter_polygon.normal[2])
|
||||
return self._inclination
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
|
@ -256,6 +256,19 @@ class ThermalBoundary:
|
||||
raise TypeError('Constructions layers are not initialized') from TypeError
|
||||
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
|
||||
def u_value(self, value):
|
||||
"""
|
||||
|
@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
"""
|
||||
from math import inf
|
||||
from typing import Union, List
|
||||
from hub.city_model_structure.attributes.schedule import Schedule
|
||||
|
||||
@ -22,20 +23,16 @@ class ThermalControl:
|
||||
|
||||
@staticmethod
|
||||
def _maximum_value(schedules):
|
||||
maximum = -1000
|
||||
maximum = -inf
|
||||
for schedule in schedules:
|
||||
for value in schedule.values:
|
||||
if value > maximum:
|
||||
maximum = value
|
||||
maximum = max(maximum, max(schedule.values))
|
||||
return maximum
|
||||
|
||||
@staticmethod
|
||||
def _minimum_value(schedules):
|
||||
minimum = 1000
|
||||
minimum = inf
|
||||
for schedule in schedules:
|
||||
for value in schedule.values:
|
||||
if value < minimum:
|
||||
minimum = value
|
||||
minimum = min(minimum, min(schedule.values))
|
||||
return minimum
|
||||
|
||||
@property
|
||||
|
@ -14,6 +14,7 @@ import math
|
||||
import pickle
|
||||
import sys
|
||||
import pathlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
|
||||
@ -101,7 +102,7 @@ class City:
|
||||
Get city location
|
||||
:return: Location
|
||||
"""
|
||||
return self._get_location().city
|
||||
return self._get_location()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -113,6 +114,15 @@ class City:
|
||||
return self._get_location().city
|
||||
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
|
||||
def climate_reference_city(self) -> Union[None, str]:
|
||||
"""
|
||||
@ -275,15 +285,6 @@ class City:
|
||||
"""
|
||||
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
|
||||
def load(city_filename) -> City:
|
||||
"""
|
||||
@ -299,6 +300,20 @@ class City:
|
||||
with open(city_filename, 'rb') as 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):
|
||||
"""
|
||||
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
|
||||
from hub.city_model_structure.attributes.schedule import Schedule
|
||||
from hub.city_model_structure.building_demand.thermal_zone import ThermalZone
|
||||
from hub.helpers.configuration_helper import ConfigurationHelper
|
||||
|
||||
|
||||
class Idf:
|
||||
@ -55,7 +56,6 @@ class Idf:
|
||||
_SIMPLE = 'Simple'
|
||||
|
||||
idf_surfaces = {
|
||||
# todo: make an enum for all the surface types
|
||||
cte.WALL: 'wall',
|
||||
cte.GROUND: 'floor',
|
||||
cte.ROOF: 'roof'
|
||||
@ -148,28 +148,28 @@ class Idf:
|
||||
|
||||
def _add_material(self, layer):
|
||||
for material in self._idf.idfobjects[self._MATERIAL]:
|
||||
if material.Name == layer.material.name:
|
||||
if material.Name == layer.material_name:
|
||||
return
|
||||
for material in self._idf.idfobjects[self._MATERIAL_NOMASS]:
|
||||
if material.Name == layer.material.name:
|
||||
if material.Name == layer.material_name:
|
||||
return
|
||||
if layer.material.no_mass:
|
||||
if layer.no_mass:
|
||||
self._idf.newidfobject(self._MATERIAL_NOMASS,
|
||||
Name=layer.material.name,
|
||||
Name=layer.material_name,
|
||||
Roughness=self._ROUGHNESS,
|
||||
Thermal_Resistance=layer.material.thermal_resistance
|
||||
Thermal_Resistance=layer.thermal_resistance
|
||||
)
|
||||
else:
|
||||
self._idf.newidfobject(self._MATERIAL,
|
||||
Name=layer.material.name,
|
||||
Name=layer.material_name,
|
||||
Roughness=self._ROUGHNESS,
|
||||
Thickness=layer.thickness,
|
||||
Conductivity=layer.material.conductivity,
|
||||
Density=layer.material.density,
|
||||
Specific_Heat=layer.material.specific_heat,
|
||||
Thermal_Absorptance=layer.material.thermal_absorptance,
|
||||
Solar_Absorptance=layer.material.solar_absorptance,
|
||||
Visible_Absorptance=layer.material.visible_absorptance
|
||||
Conductivity=layer.conductivity,
|
||||
Density=layer.density,
|
||||
Specific_Heat=layer.specific_heat,
|
||||
Thermal_Absorptance=layer.thermal_absorptance,
|
||||
Solar_Absorptance=layer.solar_absorptance,
|
||||
Visible_Absorptance=layer.visible_absorptance
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@ -338,11 +338,11 @@ class Idf:
|
||||
_kwargs = {'Name': vegetation_name,
|
||||
'Outside_Layer': thermal_boundary.parent_surface.vegetation.name}
|
||||
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:
|
||||
_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):
|
||||
_kwargs[f'Layer_{i + 1}'] = layers[i].material.name
|
||||
_kwargs[f'Layer_{i + 1}'] = layers[i].material_name
|
||||
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
||||
|
||||
def _add_window_construction_and_material(self, thermal_opening):
|
||||
@ -512,12 +512,12 @@ class Idf:
|
||||
self._rename_building(self._city.name)
|
||||
self._lod = self._city.level_of_detail.geometry
|
||||
for building in self._city.buildings:
|
||||
print('building name', building.name)
|
||||
for internal_zone in building.internal_zones:
|
||||
if internal_zone.thermal_zones_from_internal_zones is None:
|
||||
continue
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
|
||||
self._add_construction(thermal_boundary)
|
||||
if thermal_boundary.parent_surface.vegetation is not None:
|
||||
self._add_vegetation_material(thermal_boundary.parent_surface.vegetation)
|
||||
@ -560,7 +560,7 @@ class Idf:
|
||||
self._add_dhw(thermal_zone, building.name)
|
||||
if self._export_type == "Surfaces":
|
||||
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)
|
||||
else:
|
||||
self._add_pure_geometry(building, building.name)
|
||||
@ -633,6 +633,8 @@ class Idf:
|
||||
self._city.lower_corner)
|
||||
shading.setcoords(coordinates)
|
||||
solar_reflectance = surface.short_wave_reflectance
|
||||
if solar_reflectance is None:
|
||||
solar_reflectance = ConfigurationHelper().short_wave_reflectance
|
||||
self._idf.newidfobject(self._SHADING_PROPERTY,
|
||||
Shading_Surface_Name=f'{surface.name}',
|
||||
Diffuse_Solar_Reflectance_of_Unglazed_Part_of_Shading_Surface=solar_reflectance,
|
||||
@ -677,41 +679,40 @@ class Idf:
|
||||
self._idf.set_wwr(wwr)
|
||||
|
||||
def _add_surfaces(self, building, zone_name):
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
for boundary in thermal_zone.thermal_boundaries:
|
||||
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
|
||||
outside_boundary_condition = 'Outdoors'
|
||||
sun_exposure = 'SunExposed'
|
||||
wind_exposure = 'WindExposed'
|
||||
_kwargs = {'Name': f'{boundary.parent_surface.name}',
|
||||
'Surface_Type': idf_surface_type,
|
||||
'Zone_Name': zone_name}
|
||||
if boundary.parent_surface.type == cte.GROUND:
|
||||
outside_boundary_condition = 'Ground'
|
||||
sun_exposure = 'NoSun'
|
||||
wind_exposure = 'NoWind'
|
||||
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5:
|
||||
outside_boundary_condition = 'Surface'
|
||||
outside_boundary_condition_object = boundary.parent_surface.name
|
||||
sun_exposure = 'NoSun'
|
||||
wind_exposure = 'NoWind'
|
||||
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object
|
||||
_kwargs['Outside_Boundary_Condition'] = outside_boundary_condition
|
||||
_kwargs['Sun_Exposure'] = sun_exposure
|
||||
_kwargs['Wind_Exposure'] = wind_exposure
|
||||
for thermal_zone in building.thermal_zones_from_internal_zones:
|
||||
for boundary in thermal_zone.thermal_boundaries:
|
||||
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
|
||||
outside_boundary_condition = 'Outdoors'
|
||||
sun_exposure = 'SunExposed'
|
||||
wind_exposure = 'WindExposed'
|
||||
_kwargs = {'Name': f'{boundary.parent_surface.name}',
|
||||
'Surface_Type': idf_surface_type,
|
||||
'Zone_Name': zone_name}
|
||||
if boundary.parent_surface.type == cte.GROUND:
|
||||
outside_boundary_condition = 'Ground'
|
||||
sun_exposure = 'NoSun'
|
||||
wind_exposure = 'NoWind'
|
||||
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5:
|
||||
outside_boundary_condition = 'Surface'
|
||||
outside_boundary_condition_object = boundary.parent_surface.name
|
||||
sun_exposure = 'NoSun'
|
||||
wind_exposure = 'NoWind'
|
||||
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object
|
||||
_kwargs['Outside_Boundary_Condition'] = outside_boundary_condition
|
||||
_kwargs['Sun_Exposure'] = sun_exposure
|
||||
_kwargs['Wind_Exposure'] = wind_exposure
|
||||
|
||||
if boundary.parent_surface.vegetation is not None:
|
||||
construction_name = f'{boundary.construction_name}_{boundary.parent_surface.vegetation.name}'
|
||||
else:
|
||||
construction_name = boundary.construction_name
|
||||
_kwargs['Construction_Name'] = construction_name
|
||||
if boundary.parent_surface.vegetation is not None:
|
||||
construction_name = f'{boundary.construction_name}_{boundary.parent_surface.vegetation.name}'
|
||||
else:
|
||||
construction_name = boundary.construction_name
|
||||
_kwargs['Construction_Name'] = construction_name
|
||||
|
||||
surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
|
||||
surface = self._idf.newidfobject(self._SURFACE, **_kwargs)
|
||||
|
||||
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
|
||||
self._city.lower_corner)
|
||||
surface.setcoords(coordinates)
|
||||
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
|
||||
self._city.lower_corner)
|
||||
surface.setcoords(coordinates)
|
||||
|
||||
if self._lod >= 3:
|
||||
for internal_zone in building.internal_zones:
|
||||
|
@ -42,6 +42,14 @@ class InselMonthlyEnergyBalance:
|
||||
self._insel_files_paths.append(building.name + '.insel')
|
||||
file_name_out = building.name + '.out'
|
||||
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:
|
||||
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 hub.exports.formats.glb import Glb
|
||||
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.stl import Stl
|
||||
from hub.exports.formats.cesiumjs_tileset import CesiumjsTileset
|
||||
from hub.helpers.utils import validate_import_export_type
|
||||
|
||||
|
||||
@ -17,7 +20,7 @@ class ExportsFactory:
|
||||
"""
|
||||
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._handler = '_' + handler.lower()
|
||||
validate_import_export_type(ExportsFactory, handler)
|
||||
@ -26,18 +29,7 @@ class ExportsFactory:
|
||||
self._path = path
|
||||
self._target_buildings = target_buildings
|
||||
self._adjacent_buildings = adjacent_buildings
|
||||
|
||||
@property
|
||||
def _citygml(self):
|
||||
"""
|
||||
Export to citygml
|
||||
:return: None
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def _collada(self):
|
||||
raise NotImplementedError
|
||||
self._base_uri = base_uri
|
||||
|
||||
@property
|
||||
def _stl(self):
|
||||
@ -61,9 +53,30 @@ class ExportsFactory:
|
||||
Export the city to Simplified Radiosity Algorithm xml format
|
||||
:return: None
|
||||
"""
|
||||
return SimplifiedRadiosityAlgorithm(self._city,
|
||||
(self._path / f'{self._city.name}_sra.xml'),
|
||||
target_buildings=self._target_buildings)
|
||||
return SimplifiedRadiosityAlgorithm(
|
||||
self._city, (self._path / f'{self._city.name}_sra.xml'), 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):
|
||||
"""
|
||||
|
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
|
||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Obj:
|
||||
"""
|
||||
@ -17,29 +18,64 @@ class Obj:
|
||||
self._path = path
|
||||
self._export()
|
||||
|
||||
def _to_vertex(self, coordinate):
|
||||
def _ground(self, coordinate):
|
||||
x = coordinate[0] - self._city.lower_corner[0]
|
||||
y = coordinate[1] - self._city.lower_corner[1]
|
||||
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):
|
||||
if self._city.name is None:
|
||||
self._city.name = 'unknown_city'
|
||||
file_name = self._city.name + '.obj'
|
||||
file_path = (Path(self._path).resolve() / file_name).resolve()
|
||||
obj_name = f'{self._city.name}.obj'
|
||||
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 = {}
|
||||
with open(file_path, 'w', encoding='utf-8') as obj:
|
||||
faces = []
|
||||
vertex_index = 0
|
||||
normal_index = 0
|
||||
with open(obj_file_path, 'w', encoding='utf-8') as obj:
|
||||
obj.write("# cerc-hub export\n")
|
||||
vertex_index = 0
|
||||
faces = []
|
||||
obj.write(f'mtllib {mtl_name}\n')
|
||||
|
||||
for building in self._city.buildings:
|
||||
obj.write(f'# building {building.name}\n')
|
||||
obj.write(f'g {building.name}\n')
|
||||
obj.write('s off\n')
|
||||
|
||||
for surface in building.surfaces:
|
||||
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:
|
||||
vertex = self._to_vertex(coordinate)
|
||||
if vertex not in vertices:
|
||||
@ -47,11 +83,13 @@ class Obj:
|
||||
vertices[vertex] = vertex_index
|
||||
current = vertex_index
|
||||
obj.write(vertex)
|
||||
textures.append(self._to_texture_vertex(coordinate)) # only append if non-existing
|
||||
else:
|
||||
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'{face} {face.split(" ")[1]}\n')
|
||||
faces.append(f"f {' '.join(face)}\n")
|
||||
obj.writelines(faces)
|
||||
faces = []
|
||||
|
@ -49,36 +49,140 @@ WEEK_DAYS = 'Weekdays'
|
||||
WEEK_ENDS = 'Weekends'
|
||||
ALL_DAYS = 'Alldays'
|
||||
|
||||
WEEK_DAYS_A_MONTH = {'monday': [5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 5],
|
||||
'tuesday': [5, 4, 4, 4, 5, 4, 5, 4, 4, 5, 4, 4],
|
||||
'wednesday': [5, 4, 4, 4, 5, 4, 4, 5, 4, 5, 4, 4],
|
||||
'thursday': [4, 4, 5, 4, 5, 4, 4, 5, 4, 4, 5, 4],
|
||||
'friday': [4, 4, 5, 4, 4, 5, 4, 5, 4, 4, 5, 4],
|
||||
'saturday': [4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5],
|
||||
'sunday': [4, 4, 4, 5, 4, 4, 5, 4, 5, 4, 4, 5],
|
||||
'holiday': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
|
||||
JANUARY = 'January'
|
||||
FEBRUARY = 'February'
|
||||
MARCH = 'March'
|
||||
APRIL = 'April'
|
||||
MAY = 'May'
|
||||
JUNE = 'June'
|
||||
JULY = 'July'
|
||||
AUGUST = 'August'
|
||||
SEPTEMBER = 'September'
|
||||
OCTOBER = 'October'
|
||||
NOVEMBER = 'November'
|
||||
DECEMBER = 'December'
|
||||
|
||||
WEEK_DAYS_A_YEAR = {'monday': 51,
|
||||
'tuesday': 50,
|
||||
'wednesday': 50,
|
||||
'thursday': 50,
|
||||
'friday': 50,
|
||||
'saturday': 52,
|
||||
'sunday': 52,
|
||||
'holiday': 10}
|
||||
MONTHS = [JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER]
|
||||
|
||||
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}
|
||||
WEEK_DAYS_A_MONTH = {JANUARY: {MONDAY: 5,
|
||||
TUESDAY: 5,
|
||||
WEDNESDAY: 5,
|
||||
THURSDAY: 4,
|
||||
FRIDAY: 4,
|
||||
SATURDAY: 4,
|
||||
SUNDAY: 4,
|
||||
HOLIDAY: 0},
|
||||
FEBRUARY: {MONDAY: 4,
|
||||
TUESDAY: 4,
|
||||
WEDNESDAY: 4,
|
||||
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
|
||||
ANY_NUMBER = 'any_number'
|
||||
|
@ -16,7 +16,9 @@ class HubFunctionToEilatConstructionFunction:
|
||||
self._dictionary = {
|
||||
cte.RESIDENTIAL: 'Residential_building',
|
||||
cte.HOTEL: 'Residential_building',
|
||||
cte.DORMITORY: 'Residential_building'
|
||||
cte.DORMITORY: 'Residential_building',
|
||||
cte.DATACENTER: 'n/a',
|
||||
cte.FARM: 'n/a'
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -73,7 +73,9 @@ class HubFunctionToMontrealCustomCostsFunction:
|
||||
cte.AUTOMOTIVE_FACILITY: 'non-residential',
|
||||
cte.PARKING_GARAGE: '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
|
||||
|
@ -72,7 +72,9 @@ class HubFunctionToNrcanConstructionFunction:
|
||||
cte.AUTOMOTIVE_FACILITY: 'n/a',
|
||||
cte.PARKING_GARAGE: '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
|
||||
|
@ -73,7 +73,9 @@ class HubFunctionToNrelConstructionFunction:
|
||||
cte.AUTOMOTIVE_FACILITY: 'n/a',
|
||||
cte.PARKING_GARAGE: '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
|
||||
|
@ -73,7 +73,9 @@ class HubUsageToComnetUsage:
|
||||
cte.AUTOMOTIVE_FACILITY: 'BA Automotive Facility',
|
||||
cte.PARKING_GARAGE: 'BA Parking Garage',
|
||||
cte.RELIGIOUS: 'BA Religious Building',
|
||||
cte.NON_HEATED: 'n/a'
|
||||
cte.NON_HEATED: 'n/a',
|
||||
cte.DATACENTER: 'n/a',
|
||||
cte.FARM: 'n/a'
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -17,7 +17,9 @@ class HubUsageToEilatUsage:
|
||||
self._dictionary = {
|
||||
cte.RESIDENTIAL: 'Residential',
|
||||
cte.HOTEL: 'Hotel employees',
|
||||
cte.DORMITORY: 'Dormitory'
|
||||
cte.DORMITORY: 'Dormitory',
|
||||
cte.DATACENTER: 'n/a',
|
||||
cte.FARM: 'n/a'
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -73,7 +73,9 @@ class HubUsageToHftUsage:
|
||||
cte.AUTOMOTIVE_FACILITY: 'n/a',
|
||||
cte.PARKING_GARAGE: 'n/a',
|
||||
cte.RELIGIOUS: 'event location',
|
||||
cte.NON_HEATED: 'non-heated'
|
||||
cte.NON_HEATED: 'non-heated',
|
||||
cte.DATACENTER: 'n/a',
|
||||
cte.FARM: 'n/a'
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -73,7 +73,9 @@ class HubUsageToNrcanUsage:
|
||||
cte.AUTOMOTIVE_FACILITY: 'Automotive facility',
|
||||
cte.PARKING_GARAGE: 'Storage garage',
|
||||
cte.RELIGIOUS: 'Religious building',
|
||||
cte.NON_HEATED: 'n/a'
|
||||
cte.NON_HEATED: 'n/a',
|
||||
cte.DATACENTER: 'n/a',
|
||||
cte.FARM: 'n/a'
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -623,8 +623,12 @@ class MontrealFunctionToHubFunction:
|
||||
'8192': cte.FARM,
|
||||
'2439': 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
|
||||
def dictionary(self) -> dict:
|
||||
|
@ -312,7 +312,7 @@ class GeometryHelper:
|
||||
country = file_country_code
|
||||
city = file_city_name
|
||||
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
|
||||
def distance_between_points(vertex1, vertex2):
|
||||
|
@ -11,10 +11,12 @@ class 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._city = city
|
||||
self._region_code = region_code
|
||||
self._climate_reference_city_latitude = climate_reference_city_latitude
|
||||
self._climate_reference_city_longitude = climate_reference_city_longitude
|
||||
|
||||
@property
|
||||
def city(self):
|
||||
@ -36,3 +38,17 @@ class Location:
|
||||
Get region
|
||||
"""
|
||||
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,11 +63,10 @@ class LoadsCalculation:
|
||||
:return: int
|
||||
"""
|
||||
heating_load_transmitted = 0
|
||||
for internal_zone in self._building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
internal_temperature = thermal_zone.thermal_control.mean_heating_set_point
|
||||
heating_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature,
|
||||
ground_temperature)
|
||||
for thermal_zone in self._building.thermal_zones_from_internal_zones:
|
||||
internal_temperature = thermal_zone.thermal_control.mean_heating_set_point
|
||||
heating_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature,
|
||||
ground_temperature)
|
||||
return heating_load_transmitted
|
||||
|
||||
def get_cooling_transmitted_load(self, ambient_temperature, ground_temperature):
|
||||
@ -76,11 +75,10 @@ class LoadsCalculation:
|
||||
:return: int
|
||||
"""
|
||||
cooling_load_transmitted = 0
|
||||
for internal_zone in self._building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point
|
||||
cooling_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature,
|
||||
ground_temperature)
|
||||
for thermal_zone in self._building.thermal_zones_from_internal_zones:
|
||||
internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point
|
||||
cooling_load_transmitted += self._get_load_transmitted(thermal_zone, internal_temperature, ambient_temperature,
|
||||
ground_temperature)
|
||||
return cooling_load_transmitted
|
||||
|
||||
def get_heating_ventilation_load_sensible(self, ambient_temperature):
|
||||
@ -89,10 +87,9 @@ class LoadsCalculation:
|
||||
:return: int
|
||||
"""
|
||||
heating_ventilation_load = 0
|
||||
for internal_zone in self._building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
internal_temperature = thermal_zone.thermal_control.mean_heating_set_point
|
||||
heating_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature)
|
||||
for thermal_zone in self._building.thermal_zones_from_internal_zones:
|
||||
internal_temperature = thermal_zone.thermal_control.mean_heating_set_point
|
||||
heating_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature)
|
||||
return heating_ventilation_load
|
||||
|
||||
def get_cooling_ventilation_load_sensible(self, ambient_temperature):
|
||||
@ -101,10 +98,9 @@ class LoadsCalculation:
|
||||
:return: int
|
||||
"""
|
||||
cooling_ventilation_load = 0
|
||||
for internal_zone in self._building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point
|
||||
cooling_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature)
|
||||
for thermal_zone in self._building.thermal_zones_from_internal_zones:
|
||||
internal_temperature = thermal_zone.thermal_control.mean_cooling_set_point
|
||||
cooling_ventilation_load += self._get_load_ventilation(thermal_zone, internal_temperature, ambient_temperature)
|
||||
return cooling_ventilation_load
|
||||
|
||||
def get_internal_load_sensible(self):
|
||||
@ -115,19 +111,18 @@ class LoadsCalculation:
|
||||
cooling_load_occupancy_sensible = 0
|
||||
cooling_load_lighting = 0
|
||||
cooling_load_equipment_sensible = 0
|
||||
for internal_zone in self._building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
cooling_load_occupancy_sensible += (thermal_zone.occupancy.sensible_convective_internal_gain
|
||||
+ thermal_zone.occupancy.sensible_radiative_internal_gain) \
|
||||
* thermal_zone.footprint_area
|
||||
cooling_load_lighting += (
|
||||
thermal_zone.lighting.density * thermal_zone.lighting.convective_fraction + thermal_zone.lighting.density *
|
||||
thermal_zone.lighting.radiative_fraction
|
||||
) * thermal_zone.footprint_area
|
||||
cooling_load_equipment_sensible += (
|
||||
thermal_zone.appliances.density * thermal_zone.appliances.convective_fraction +
|
||||
thermal_zone.appliances.density * thermal_zone.appliances.radiative_fraction
|
||||
) * thermal_zone.footprint_area
|
||||
for thermal_zone in self._building.thermal_zones_from_internal_zones:
|
||||
cooling_load_occupancy_sensible += (thermal_zone.occupancy.sensible_convective_internal_gain
|
||||
+ thermal_zone.occupancy.sensible_radiative_internal_gain) \
|
||||
* thermal_zone.footprint_area
|
||||
cooling_load_lighting += (
|
||||
thermal_zone.lighting.density * thermal_zone.lighting.convective_fraction + thermal_zone.lighting.density *
|
||||
thermal_zone.lighting.radiative_fraction
|
||||
) * thermal_zone.footprint_area
|
||||
cooling_load_equipment_sensible += (
|
||||
thermal_zone.appliances.density * thermal_zone.appliances.convective_fraction +
|
||||
thermal_zone.appliances.density * thermal_zone.appliances.radiative_fraction
|
||||
) * thermal_zone.footprint_area
|
||||
internal_load = cooling_load_occupancy_sensible + cooling_load_lighting + cooling_load_equipment_sensible
|
||||
return internal_load
|
||||
|
||||
@ -137,12 +132,11 @@ class LoadsCalculation:
|
||||
:return: int
|
||||
"""
|
||||
cooling_load_radiation = 0
|
||||
for internal_zone in self._building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
radiation = thermal_boundary.parent_surface.global_irradiance[cte.HOUR][hour] * cte.WATTS_HOUR_TO_JULES
|
||||
cooling_load_radiation += (
|
||||
thermal_opening.area * (1 - thermal_opening.frame_ratio) * thermal_opening.g_value * radiation
|
||||
)
|
||||
for thermal_zone in self._building.thermal_zones_from_internal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
radiation = thermal_boundary.parent_surface.global_irradiance[cte.HOUR][hour] * cte.WATTS_HOUR_TO_JULES
|
||||
cooling_load_radiation += (
|
||||
thermal_opening.area * (1 - thermal_opening.frame_ratio) * thermal_opening.g_value * radiation
|
||||
)
|
||||
return cooling_load_radiation
|
||||
|
@ -54,13 +54,11 @@ class PeakLoads:
|
||||
"""
|
||||
month = 1
|
||||
peaks = [0 for _ in range(12)]
|
||||
print('hv', hourly_values)
|
||||
for i in range(0, len(hourly_values)):
|
||||
if _MONTH_STARTING_HOUR[month] <= i:
|
||||
month += 1
|
||||
if hourly_values[i] > peaks[month-1]:
|
||||
peaks[month-1] = hourly_values[i]
|
||||
print('peak', peaks)
|
||||
return peaks
|
||||
|
||||
@property
|
||||
@ -75,18 +73,15 @@ class PeakLoads:
|
||||
ambient_temperature = self._building.external_temperature[cte.HOUR]
|
||||
for month in range(0, 12):
|
||||
ground_temperature = self._building.ground_temperature[cte.MONTH]['2'][month]
|
||||
heating_ambient_temperature = 100
|
||||
start_hour = _MONTH_STARTING_HOUR[month]
|
||||
end_hour = 8760
|
||||
if month < 11:
|
||||
end_hour = _MONTH_STARTING_HOUR[month + 1]
|
||||
for hour in range(start_hour, end_hour):
|
||||
temperature = ambient_temperature[hour]
|
||||
if temperature < heating_ambient_temperature:
|
||||
heating_ambient_temperature = temperature
|
||||
heating_ambient_temperature = min(ambient_temperature[start_hour:end_hour])
|
||||
loads = LoadsCalculation(self._building)
|
||||
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)
|
||||
# todo: include heating ventilation latent
|
||||
heating_load_ventilation_latent = 0
|
||||
heating_load = heating_load_transmitted + heating_load_ventilation_sensible + heating_load_ventilation_latent
|
||||
heating_load = max(heating_load, 0)
|
||||
|
@ -71,6 +71,7 @@ class EilatPhysicsParameters:
|
||||
for catalog_construction in catalog_archetype.constructions:
|
||||
construction = Construction()
|
||||
construction.type = catalog_construction.type
|
||||
construction.name = catalog_construction.name
|
||||
if catalog_construction.window_ratio is not None:
|
||||
for _orientation in catalog_construction.window_ratio:
|
||||
if catalog_construction.window_ratio[_orientation] is None:
|
||||
@ -83,7 +84,7 @@ class EilatPhysicsParameters:
|
||||
layer.thickness = layer_archetype.thickness
|
||||
total_thickness += layer_archetype.thickness
|
||||
archetype_material = layer_archetype.material
|
||||
layer.name = archetype_material.name
|
||||
layer.material_name = archetype_material.name
|
||||
layer.no_mass = archetype_material.no_mass
|
||||
if archetype_material.no_mass:
|
||||
layer.thermal_resistance = archetype_material.thermal_resistance
|
||||
|
@ -42,7 +42,6 @@ class ConstructionHelper:
|
||||
'Varennes': '6',
|
||||
'Laval': '6',
|
||||
'Longueuil': '6',
|
||||
'Saint-Leonard': '6',
|
||||
'Mont-Royal': '6',
|
||||
'Deux-Montagnes': '6',
|
||||
'Dorval': '6',
|
||||
@ -58,6 +57,8 @@ class ConstructionHelper:
|
||||
'Pointe-Claire': '6',
|
||||
'Boucherville': '6',
|
||||
'Mascouche': '6',
|
||||
'Saint-Leonard': '6',
|
||||
'La Prairie': '6'
|
||||
}
|
||||
|
||||
_reference_city_to_israel_climate_zone = {
|
||||
|
@ -71,6 +71,7 @@ class NrcanPhysicsParameters:
|
||||
for catalog_construction in catalog_archetype.constructions:
|
||||
construction = Construction()
|
||||
construction.type = catalog_construction.type
|
||||
construction.name = catalog_construction.name
|
||||
if catalog_construction.window_ratio is not None:
|
||||
for _orientation in catalog_construction.window_ratio:
|
||||
if catalog_construction.window_ratio[_orientation] is None:
|
||||
@ -81,7 +82,7 @@ class NrcanPhysicsParameters:
|
||||
layer = Layer()
|
||||
layer.thickness = layer_archetype.thickness
|
||||
archetype_material = layer_archetype.material
|
||||
layer.name = archetype_material.name
|
||||
layer.material_name = archetype_material.name
|
||||
layer.no_mass = archetype_material.no_mass
|
||||
if archetype_material.no_mass:
|
||||
layer.thermal_resistance = archetype_material.thermal_resistance
|
||||
|
@ -73,6 +73,7 @@ class NrelPhysicsParameters:
|
||||
for catalog_construction in catalog_archetype.constructions:
|
||||
construction = Construction()
|
||||
construction.type = catalog_construction.type
|
||||
construction.name = catalog_construction.name
|
||||
if catalog_construction.window_ratio is not None:
|
||||
construction.window_ratio = {'north': catalog_construction.window_ratio,
|
||||
'east': catalog_construction.window_ratio,
|
||||
@ -84,7 +85,7 @@ class NrelPhysicsParameters:
|
||||
layer = Layer()
|
||||
layer.thickness = layer_archetype.thickness
|
||||
archetype_material = layer_archetype.material
|
||||
layer.name = archetype_material.name
|
||||
layer.material_name = archetype_material.name
|
||||
layer.no_mass = archetype_material.no_mass
|
||||
if archetype_material.no_mass:
|
||||
layer.thermal_resistance = archetype_material.thermal_resistance
|
||||
|
@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
import xmltodict
|
||||
@ -22,16 +23,17 @@ class CityGml:
|
||||
|
||||
def __init__(self,
|
||||
path,
|
||||
extrusion_height_field=None,
|
||||
year_of_construction_field=None,
|
||||
function_field=None,
|
||||
function_to_hub=None):
|
||||
function_to_hub=None,
|
||||
hub_crs=None):
|
||||
self._city = None
|
||||
self._lod = None
|
||||
self._lod1_tags = ['lod1Solid', 'lod1MultiSurface']
|
||||
self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve']
|
||||
self._extrusion_height_field = extrusion_height_field
|
||||
self._function_to_hub = function_to_hub
|
||||
if hub_crs is None:
|
||||
hub_crs = 'EPSG:26911'
|
||||
if function_field is None:
|
||||
function_field = 'function'
|
||||
if year_of_construction_field is None:
|
||||
@ -79,7 +81,8 @@ class CityGml:
|
||||
self._srs_name = envelope['@srsName']
|
||||
else:
|
||||
# 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:
|
||||
# get the boundary from the city objects instead
|
||||
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])
|
||||
except NotImplementedError:
|
||||
continue
|
||||
if 'surfaceMember' not in bounded[surface_type][surface_encoding][surface_subtype]:
|
||||
continue
|
||||
for member in bounded[surface_type][surface_encoding][surface_subtype]['surfaceMember']:
|
||||
if 'CompositeSurface' in member:
|
||||
for composite_members in member['CompositeSurface']['surfaceMember']:
|
||||
|
@ -34,9 +34,13 @@ class Geojson:
|
||||
extrusion_height_field=None,
|
||||
year_of_construction_field=None,
|
||||
function_field=None,
|
||||
function_to_hub=None):
|
||||
# todo: destination epsg should change according actual the location
|
||||
self._transformer = Transformer.from_crs('epsg:4326', 'epsg:26911')
|
||||
function_to_hub=None,
|
||||
hub_crs=None
|
||||
):
|
||||
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_y = cte.MAX_FLOAT
|
||||
self._max_x = cte.MIN_FLOAT
|
||||
@ -116,6 +120,7 @@ class Geojson:
|
||||
if self._extrusion_height_field is not None:
|
||||
extrusion_height = float(feature['properties'][self._extrusion_height_field])
|
||||
lod = 1
|
||||
self._max_z = max(self._max_z, extrusion_height)
|
||||
year_of_construction = None
|
||||
if self._year_of_construction_field is not None:
|
||||
year_of_construction = int(feature['properties'][self._year_of_construction_field])
|
||||
@ -154,7 +159,7 @@ class Geojson:
|
||||
extrusion_height))
|
||||
else:
|
||||
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:
|
||||
# Do not include "small building-like structures" to buildings
|
||||
if building.floor_area >= 25:
|
||||
@ -165,7 +170,6 @@ class Geojson:
|
||||
if lod > 0:
|
||||
lines_information = GeometryHelper.city_mapping(self._city, plot=False)
|
||||
self._store_shared_percentage_to_walls(self._city, lines_information)
|
||||
|
||||
return self._city
|
||||
|
||||
def _polygon_coordinates_to_3d(self, polygon_coordinates):
|
||||
|
@ -18,21 +18,21 @@ class GeometryFactory:
|
||||
"""
|
||||
def __init__(self, file_type,
|
||||
path=None,
|
||||
data_frame=None,
|
||||
aliases_field=None,
|
||||
height_field=None,
|
||||
year_of_construction_field=None,
|
||||
function_field=None,
|
||||
function_to_hub=None):
|
||||
function_to_hub=None,
|
||||
hub_crs=None):
|
||||
self._file_type = '_' + file_type.lower()
|
||||
validate_import_export_type(GeometryFactory, file_type)
|
||||
self._path = path
|
||||
self._data_frame = data_frame
|
||||
self._aliases_field = aliases_field
|
||||
self._height_field = height_field
|
||||
self._year_of_construction_field = year_of_construction_field
|
||||
self._function_field = function_field
|
||||
self._function_to_hub = function_to_hub
|
||||
self._hub_crs = hub_crs
|
||||
|
||||
@property
|
||||
def _citygml(self) -> City:
|
||||
@ -41,10 +41,10 @@ class GeometryFactory:
|
||||
:return: City
|
||||
"""
|
||||
return CityGml(self._path,
|
||||
self._height_field,
|
||||
self._year_of_construction_field,
|
||||
self._function_field,
|
||||
self._function_to_hub).city
|
||||
self._function_to_hub,
|
||||
self._hub_crs).city
|
||||
|
||||
@property
|
||||
def _obj(self) -> City:
|
||||
@ -65,7 +65,8 @@ class GeometryFactory:
|
||||
self._height_field,
|
||||
self._year_of_construction_field,
|
||||
self._function_field,
|
||||
self._function_to_hub).city
|
||||
self._function_to_hub,
|
||||
self._hub_crs).city
|
||||
|
||||
@property
|
||||
def city(self) -> City:
|
||||
|
@ -4,7 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guillermo.GutierrezMorote@concordia.ca
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import csv
|
||||
|
||||
@ -43,53 +43,61 @@ class InselMonthlyEnergyBalance:
|
||||
domestic_hot_water_demand = []
|
||||
lighting_demand = []
|
||||
appliances_demand = []
|
||||
if building.internal_zones[0].thermal_zones_from_internal_zones is None:
|
||||
|
||||
# todo: REFACTOR after retrofit project, this is a hack for the pickle files
|
||||
try:
|
||||
if building.internal_zones[0].thermal_zones_from_internal_zones is None:
|
||||
domestic_hot_water_demand = [0] * 12
|
||||
lighting_demand = [0] * 12
|
||||
appliances_demand = [0] * 12
|
||||
else:
|
||||
thermal_zone = building.internal_zones[0].thermal_zones_from_internal_zones[0]
|
||||
area = thermal_zone.total_floor_area
|
||||
cold_water = building.cold_water_temperature[cte.MONTH]
|
||||
peak_flow = thermal_zone.domestic_hot_water.peak_flow
|
||||
service_temperature = thermal_zone.domestic_hot_water.service_temperature
|
||||
lighting_density = thermal_zone.lighting.density
|
||||
appliances_density = thermal_zone.appliances.density
|
||||
|
||||
for i_month, month in enumerate(cte.MONTHS):
|
||||
total_dhw_demand = 0
|
||||
total_lighting = 0
|
||||
total_appliances = 0
|
||||
|
||||
for schedule in thermal_zone.lighting.schedules:
|
||||
total_day = 0
|
||||
for value in schedule.values:
|
||||
total_day += value
|
||||
for day_type in schedule.day_types:
|
||||
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
|
||||
* lighting_density / cte.WATTS_HOUR_TO_JULES
|
||||
lighting_demand.append(total_lighting * area)
|
||||
|
||||
for schedule in thermal_zone.appliances.schedules:
|
||||
total_day = 0
|
||||
for value in schedule.values:
|
||||
total_day += value
|
||||
for day_type in schedule.day_types:
|
||||
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
|
||||
* appliances_density / cte.WATTS_HOUR_TO_JULES
|
||||
appliances_demand.append(total_appliances * area)
|
||||
|
||||
for schedule in thermal_zone.domestic_hot_water.schedules:
|
||||
total_day = 0
|
||||
for value in schedule.values:
|
||||
total_day += value
|
||||
for day_type in schedule.day_types:
|
||||
demand = (
|
||||
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
|
||||
* (service_temperature - cold_water[i_month]) / cte.WATTS_HOUR_TO_JULES
|
||||
)
|
||||
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] * demand
|
||||
domestic_hot_water_demand.append(total_dhw_demand * area)
|
||||
except AttributeError:
|
||||
domestic_hot_water_demand = [0] * 12
|
||||
lighting_demand = [0] * 12
|
||||
appliances_demand = [0] * 12
|
||||
else:
|
||||
thermal_zone = building.internal_zones[0].thermal_zones_from_internal_zones[0]
|
||||
area = thermal_zone.total_floor_area
|
||||
cold_water = building.cold_water_temperature[cte.MONTH]
|
||||
peak_flow = thermal_zone.domestic_hot_water.peak_flow
|
||||
service_temperature = thermal_zone.domestic_hot_water.service_temperature
|
||||
lighting_density = thermal_zone.lighting.density
|
||||
appliances_density = thermal_zone.appliances.density
|
||||
|
||||
for month in range(0, 12):
|
||||
total_dhw_demand = 0
|
||||
total_lighting = 0
|
||||
total_appliances = 0
|
||||
|
||||
for schedule in thermal_zone.lighting.schedules:
|
||||
total_day = 0
|
||||
for value in schedule.values:
|
||||
total_day += value
|
||||
for day_type in schedule.day_types:
|
||||
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] \
|
||||
* lighting_density / cte.WATTS_HOUR_TO_JULES
|
||||
lighting_demand.append(total_lighting * area)
|
||||
|
||||
for schedule in thermal_zone.appliances.schedules:
|
||||
total_day = 0
|
||||
for value in schedule.values:
|
||||
total_day += value
|
||||
for day_type in schedule.day_types:
|
||||
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] \
|
||||
* appliances_density / cte.WATTS_HOUR_TO_JULES
|
||||
appliances_demand.append(total_appliances * area)
|
||||
|
||||
for schedule in thermal_zone.domestic_hot_water.schedules:
|
||||
total_day = 0
|
||||
for value in schedule.values:
|
||||
total_day += value
|
||||
for day_type in schedule.day_types:
|
||||
demand = (
|
||||
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
|
||||
* (service_temperature - cold_water[month]) / cte.WATTS_HOUR_TO_JULES
|
||||
)
|
||||
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] * demand
|
||||
domestic_hot_water_demand.append(total_dhw_demand * area)
|
||||
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.YEAR] = [sum(domestic_hot_water_demand)]
|
||||
|
@ -31,7 +31,7 @@ class ResultFactory:
|
||||
self._handler = '_' + handler.lower().replace(' ', '_')
|
||||
validate_import_export_type(ResultFactory, handler)
|
||||
self._city = city
|
||||
self._base_path = base_path
|
||||
self._base_path = Path(base_path)
|
||||
self._hp_model = hp_model
|
||||
|
||||
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',
|
||||
'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',
|
||||
'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?
|
||||
|
||||
|
@ -75,10 +75,10 @@ class DBControl:
|
||||
:
|
||||
"""
|
||||
cities = self._city.get_by_user_id_application_id_and_scenario(user_id, application_id, scenario)
|
||||
for city in cities:
|
||||
result = self.building_info(name, city[0].id)
|
||||
if result is not None:
|
||||
return result
|
||||
c = [c[0].id for c in cities]
|
||||
result = self._city_object.building_in_cities_info(name, c)
|
||||
if result is not None:
|
||||
return result
|
||||
return None
|
||||
|
||||
def building_info(self, name, city_id) -> CityObject:
|
||||
@ -90,6 +90,15 @@ class DBControl:
|
||||
"""
|
||||
return self._city_object.get_by_name_or_alias_and_city(name, city_id)
|
||||
|
||||
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]:
|
||||
"""
|
||||
Retrieve the buildings info from the database
|
||||
@ -114,10 +123,7 @@ class DBControl:
|
||||
result_names = []
|
||||
results = {}
|
||||
for scenario in request_values['scenarios']:
|
||||
print('scenario', scenario, results)
|
||||
for scenario_name in scenario.keys():
|
||||
print('scenario name', scenario_name)
|
||||
|
||||
result_sets = self._city.get_by_user_id_application_id_and_scenario(
|
||||
user_id,
|
||||
application_id,
|
||||
@ -125,25 +131,21 @@ class DBControl:
|
||||
)
|
||||
if result_sets is None:
|
||||
continue
|
||||
for result_set in result_sets:
|
||||
city_id = result_set[0].id
|
||||
results[scenario_name] = []
|
||||
city_ids = [r[0].id for r in result_sets]
|
||||
for building_name in scenario[scenario_name]:
|
||||
_building = self._city_object.get_by_name_or_alias_in_cities(building_name, city_ids)
|
||||
if _building is None:
|
||||
continue
|
||||
city_object_id = _building.id
|
||||
_ = self._simulation_results.get_simulation_results_by_city_object_id_and_names(
|
||||
city_object_id,
|
||||
result_names)
|
||||
|
||||
results[scenario_name] = []
|
||||
for building_name in scenario[scenario_name]:
|
||||
_building = self._city_object.get_by_name_or_alias_and_city(building_name, city_id)
|
||||
if _building is None:
|
||||
continue
|
||||
city_object_id = _building.id
|
||||
_ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names(
|
||||
city_id,
|
||||
city_object_id,
|
||||
result_names)
|
||||
|
||||
for value in _:
|
||||
values = json.loads(value.values)
|
||||
values["building"] = building_name
|
||||
results[scenario_name].append(values)
|
||||
print(scenario, results)
|
||||
for value in _:
|
||||
values = value.values
|
||||
values["building"] = building_name
|
||||
results[scenario_name].append(values)
|
||||
return results
|
||||
|
||||
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
|
||||
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy import Column, Integer, String, Sequence
|
||||
from sqlalchemy import DateTime
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from hub.persistence.configuration import Models
|
||||
|
||||
|
||||
|
@ -21,8 +21,8 @@ class City(Models):
|
||||
pickle_path = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
scenario = Column(String, nullable=False)
|
||||
application_id = Column(Integer, ForeignKey('application.id'), nullable=False)
|
||||
user_id = Column(Integer, ForeignKey('user.id'), nullable=True)
|
||||
application_id = Column(Integer, ForeignKey('application.id', ondelete='CASCADE'), nullable=False)
|
||||
user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE'), nullable=True)
|
||||
hub_release = Column(String, nullable=False)
|
||||
created = 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 logging
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey, Float
|
||||
from sqlalchemy import DateTime
|
||||
@ -20,7 +21,7 @@ class CityObject(Models):
|
||||
"""
|
||||
__tablename__ = 'city_object'
|
||||
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)
|
||||
aliases = Column(String, nullable=True)
|
||||
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.total_pv_area = sum(roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor for roof in building.roofs)
|
||||
storeys = building.storeys_above_ground
|
||||
if storeys is None:
|
||||
storeys = building.max_height / building.average_storey_height
|
||||
self.total_heating_area = building.floor_area * storeys
|
||||
wall_area = 0
|
||||
window_ratio = 0
|
||||
try:
|
||||
if storeys is None:
|
||||
storeys = building.max_height / building.average_storey_height
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
window_ratio = thermal_boundary.window_ratio
|
||||
break
|
||||
except TypeError:
|
||||
storeys = 0
|
||||
logging.warning(
|
||||
'building %s has no storey height so heating area, storeys and window ratio cannot be calculated',
|
||||
self.name
|
||||
)
|
||||
self.total_heating_area = building.floor_area * storeys
|
||||
|
||||
for wall in building.walls:
|
||||
wall_area += wall.solid_polygon.area
|
||||
self.wall_area = wall_area
|
||||
window_ratio = 0
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones_from_internal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
window_ratio = thermal_boundary.window_ratio
|
||||
break
|
||||
self.windows_area = wall_area * window_ratio
|
||||
system_name = building.energy_systems_archetype_name
|
||||
if system_name is None:
|
||||
|
@ -19,8 +19,8 @@ class SimulationResults(Models):
|
||||
"""
|
||||
__tablename__ = 'simulation_results'
|
||||
id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True)
|
||||
city_id = Column(Integer, ForeignKey('city.id'), nullable=True)
|
||||
city_object_id = Column(Integer, ForeignKey('city_object.id'), nullable=True)
|
||||
city_id = Column(Integer, ForeignKey('city.id', ondelete='CASCADE'), nullable=True)
|
||||
city_object_id = Column(Integer, ForeignKey('city_object.id', ondelete='CASCADE'), nullable=True)
|
||||
name = Column(String, nullable=False)
|
||||
values = Column(JSONB, nullable=False)
|
||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
|
@ -10,6 +10,7 @@ import logging
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from hub.persistence.repository import Repository
|
||||
from hub.persistence.models import Application as Model
|
||||
@ -48,10 +49,11 @@ class Application(Repository):
|
||||
pass
|
||||
try:
|
||||
application = Model(name=name, description=description, application_uuid=application_uuid)
|
||||
self.session.add(application)
|
||||
self.session.commit()
|
||||
self.session.refresh(application)
|
||||
return application.id
|
||||
with Session(self.engine) as session:
|
||||
session.add(application)
|
||||
session.commit()
|
||||
session.refresh(application)
|
||||
return application.id
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('An error occurred while creating application %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -65,10 +67,11 @@ class Application(Repository):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.session.query(Model).filter(
|
||||
Model.application_uuid == application_uuid
|
||||
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
|
||||
self.session.commit()
|
||||
with Session(self.engine) as session:
|
||||
session.query(Model).filter(
|
||||
Model.application_uuid == application_uuid
|
||||
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
|
||||
session.commit()
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while updating application %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -80,9 +83,10 @@ class Application(Repository):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.session.query(Model).filter(Model.application_uuid == application_uuid).delete()
|
||||
self.session.flush()
|
||||
self.session.commit()
|
||||
with Session(self.engine) as session:
|
||||
session.query(Model).filter(Model.application_uuid == application_uuid).delete()
|
||||
session.flush()
|
||||
session.commit()
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while deleting application %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -94,10 +98,11 @@ class Application(Repository):
|
||||
:return: Application with the provided application_uuid
|
||||
"""
|
||||
try:
|
||||
result_set = self.session.execute(select(Model).where(
|
||||
Model.application_uuid == application_uuid)
|
||||
).first()
|
||||
return result_set[0]
|
||||
with Session(self.engine) as session:
|
||||
result_set = session.execute(select(Model).where(
|
||||
Model.application_uuid == application_uuid)
|
||||
).first()
|
||||
return result_set[0]
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching application by application_uuid %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
|
@ -54,18 +54,18 @@ class City(Repository):
|
||||
application_id,
|
||||
user_id,
|
||||
__version__)
|
||||
|
||||
self.session.add(db_city)
|
||||
self.session.flush()
|
||||
self.session.commit()
|
||||
for building in city.buildings:
|
||||
db_city_object = CityObject(db_city.id,
|
||||
building)
|
||||
self.session.add(db_city_object)
|
||||
self.session.flush()
|
||||
self.session.commit()
|
||||
self.session.refresh(db_city)
|
||||
return db_city.id
|
||||
with Session(self.engine) as session:
|
||||
session.add(db_city)
|
||||
session.flush()
|
||||
session.commit()
|
||||
for building in city.buildings:
|
||||
db_city_object = CityObject(db_city.id,
|
||||
building)
|
||||
session.add(db_city_object)
|
||||
session.flush()
|
||||
session.commit()
|
||||
session.refresh(db_city)
|
||||
return db_city.id
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('An error occurred while creating a city %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -79,8 +79,9 @@ class City(Repository):
|
||||
"""
|
||||
try:
|
||||
now = datetime.datetime.utcnow()
|
||||
self.session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
|
||||
self.session.commit()
|
||||
with Session(self.engine) as session:
|
||||
session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
|
||||
session.commit()
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while updating city %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -92,9 +93,10 @@ class City(Repository):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.session.query(CityObject).filter(CityObject.city_id == city_id).delete()
|
||||
self.session.query(Model).filter(Model.id == city_id).delete()
|
||||
self.session.commit()
|
||||
with Session(self.engine) as session:
|
||||
session.query(CityObject).filter(CityObject.city_id == city_id).delete()
|
||||
session.query(Model).filter(Model.id == city_id).delete()
|
||||
session.commit()
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching city %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -108,13 +110,12 @@ class City(Repository):
|
||||
:return: [ModelCity]
|
||||
"""
|
||||
try:
|
||||
result_set = self.session.execute(select(Model).where(Model.user_id == user_id,
|
||||
Model.application_id == application_id,
|
||||
Model.scenario == scenario
|
||||
)).all()
|
||||
self.session.close()
|
||||
self.session = Session(self.engine)
|
||||
return result_set
|
||||
with Session(self.engine) as session:
|
||||
result_set = session.execute(select(Model).where(Model.user_id == user_id,
|
||||
Model.application_id == application_id,
|
||||
Model.scenario == scenario
|
||||
)).all()
|
||||
return result_set
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching city by name %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -127,11 +128,11 @@ class City(Repository):
|
||||
:return: ModelCity
|
||||
"""
|
||||
try:
|
||||
result_set = self.session.execute(
|
||||
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
|
||||
)
|
||||
return [r[0] for r in result_set]
|
||||
with Session(self.engine) as session:
|
||||
result_set = session.execute(
|
||||
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
|
||||
)
|
||||
return [r[0] for r in result_set]
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching city by name %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
|
||||
|
@ -6,14 +6,15 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||
"""
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import select, or_
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
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.repository import Repository
|
||||
|
||||
|
||||
class CityObject(Repository):
|
||||
@ -46,10 +47,11 @@ class CityObject(Repository):
|
||||
try:
|
||||
city_object = Model(city_id=city_id,
|
||||
building=building)
|
||||
self.session.add(city_object)
|
||||
self.session.flush()
|
||||
self.session.commit()
|
||||
self.session.refresh(city_object)
|
||||
with Session(self.engine) as session:
|
||||
session.add(city_object)
|
||||
session.flush()
|
||||
session.commit()
|
||||
session.refresh(city_object)
|
||||
return city_object.id
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('An error occurred while creating city_object %s', err)
|
||||
@ -68,17 +70,18 @@ class CityObject(Repository):
|
||||
for usage in internal_zone.usages:
|
||||
object_usage = f'{object_usage}{usage.name}_{usage.percentage} '
|
||||
object_usage = object_usage.rstrip()
|
||||
self.session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
|
||||
{'name': building.name,
|
||||
'alias': building.alias,
|
||||
'object_type': building.type,
|
||||
'year_of_construction': building.year_of_construction,
|
||||
'function': building.function,
|
||||
'usage': object_usage,
|
||||
'volume': building.volume,
|
||||
'area': building.floor_area,
|
||||
'updated': datetime.datetime.utcnow()})
|
||||
self.session.commit()
|
||||
with Session(self.engine) as session:
|
||||
session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
|
||||
{'name': building.name,
|
||||
'alias': building.alias,
|
||||
'object_type': building.type,
|
||||
'year_of_construction': building.year_of_construction,
|
||||
'function': building.function,
|
||||
'usage': object_usage,
|
||||
'volume': building.volume,
|
||||
'area': building.floor_area,
|
||||
'updated': datetime.datetime.utcnow()})
|
||||
session.commit()
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while updating city object %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -91,30 +94,96 @@ class CityObject(Repository):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
|
||||
self.session.commit()
|
||||
with Session(self.engine) as session:
|
||||
session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
|
||||
session.commit()
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while deleting application %s', 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
|
||||
: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
|
||||
"""
|
||||
try:
|
||||
# search by name first
|
||||
city_object = self.session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
|
||||
if city_object is not None:
|
||||
return city_object[0]
|
||||
# name not found, so search by alias instead
|
||||
city_objects = self.session.execute(
|
||||
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
|
||||
).all()
|
||||
self.session.close()
|
||||
self.session = Session(self.engine)
|
||||
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:
|
||||
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_(cities))
|
||||
).all()
|
||||
for city_object in city_objects:
|
||||
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
||||
for alias in aliases:
|
||||
if alias == name:
|
||||
# force the name as the alias
|
||||
city_object[0].name = name
|
||||
return city_object[0]
|
||||
return None
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching city object by name and city: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
except IndexError as err:
|
||||
logging.error('Error while fetching city object by name and city, empty result %s', err)
|
||||
raise IndexError from err
|
||||
|
||||
def get_by_name_or_alias_and_city(self, name, city_id) -> Union[Model, None]:
|
||||
"""
|
||||
Fetch a city object based on name and city id
|
||||
:param name: city object name
|
||||
:param city_id: a city identifier
|
||||
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
|
||||
"""
|
||||
try:
|
||||
# search by name first
|
||||
with Session(self.engine) as session:
|
||||
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
|
||||
if city_object is not None:
|
||||
return city_object[0]
|
||||
# name not found, so search by alias instead
|
||||
city_objects = session.execute(
|
||||
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
|
||||
).all()
|
||||
for city_object in city_objects:
|
||||
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
||||
for alias in aliases:
|
||||
if alias == name:
|
||||
# force the name as the alias
|
||||
city_object[0].name = name
|
||||
return city_object[0]
|
||||
return None
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching city object by name and city: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
except IndexError as err:
|
||||
logging.error('Error while fetching city object by name and city, empty result %s', err)
|
||||
raise IndexError from err
|
||||
|
||||
def get_by_name_or_alias_in_cities(self, name, city_ids) -> Model:
|
||||
"""
|
||||
Fetch a city object based on name and city ids
|
||||
:param name: city object name
|
||||
:param city_ids: a list of city identifiers
|
||||
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
|
||||
"""
|
||||
try:
|
||||
# search by name first
|
||||
with Session(self.engine) as session:
|
||||
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id.in_(tuple(city_ids)))).first()
|
||||
if city_object is not None:
|
||||
return city_object[0]
|
||||
# name not found, so search by alias instead
|
||||
city_objects = session.execute(
|
||||
select(Model).where(Model.aliases.contains(name), Model.city_id.in_(tuple(city_ids)))
|
||||
).all()
|
||||
for city_object in city_objects:
|
||||
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
||||
for alias in aliases:
|
||||
|
@ -10,6 +10,7 @@ import logging
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from hub.persistence.repository import Repository
|
||||
from hub.persistence.models import City
|
||||
@ -52,11 +53,12 @@ class SimulationResults(Repository):
|
||||
values=values,
|
||||
city_id=city_id,
|
||||
city_object_id=city_object_id)
|
||||
self.session.add(simulation_result)
|
||||
self.session.flush()
|
||||
self.session.commit()
|
||||
self.session.refresh(simulation_result)
|
||||
return simulation_result.id
|
||||
with Session(self.engine) as session:
|
||||
session.add(simulation_result)
|
||||
session.flush()
|
||||
session.commit()
|
||||
session.refresh(simulation_result)
|
||||
return simulation_result.id
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('An error occurred while creating city_object %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -71,22 +73,23 @@ class SimulationResults(Repository):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
if city_id is not None:
|
||||
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
|
||||
with Session(self.engine) as session:
|
||||
if city_id is not None:
|
||||
session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
|
||||
{
|
||||
'values': values,
|
||||
'updated': datetime.datetime.utcnow()
|
||||
})
|
||||
self.session.commit()
|
||||
elif city_object_id is not None:
|
||||
self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
|
||||
{
|
||||
'values': values,
|
||||
'updated': datetime.datetime.utcnow()
|
||||
})
|
||||
self.session.commit()
|
||||
else:
|
||||
raise NotImplementedError('Missing either city_id or city_object_id')
|
||||
session.commit()
|
||||
elif city_object_id is not None:
|
||||
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
|
||||
{
|
||||
'values': values,
|
||||
'updated': datetime.datetime.utcnow()
|
||||
})
|
||||
session.commit()
|
||||
else:
|
||||
raise NotImplementedError('Missing either city_id or city_object_id')
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while updating city object %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -100,14 +103,15 @@ class SimulationResults(Repository):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
if city_id is not None:
|
||||
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
|
||||
self.session.commit()
|
||||
elif city_object_id is not None:
|
||||
self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
|
||||
self.session.commit()
|
||||
else:
|
||||
raise NotImplementedError('Missing either city_id or city_object_id')
|
||||
with Session(self.engine) as session:
|
||||
if city_id is not None:
|
||||
session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
|
||||
session.commit()
|
||||
elif city_object_id is not None:
|
||||
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
|
||||
session.commit()
|
||||
else:
|
||||
raise NotImplementedError('Missing either city_id or city_object_id')
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while deleting application: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -119,7 +123,8 @@ class SimulationResults(Repository):
|
||||
:return: [City] with the provided city_id
|
||||
"""
|
||||
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:
|
||||
logging.error('Error while fetching city by city_id: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -131,7 +136,8 @@ class SimulationResults(Repository):
|
||||
:return: [CityObject] with the provided city_object_id
|
||||
"""
|
||||
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:
|
||||
logging.error('Error while fetching city by city_id: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -145,18 +151,43 @@ class SimulationResults(Repository):
|
||||
:return: [SimulationResult]
|
||||
"""
|
||||
try:
|
||||
result_set = self.session.execute(select(Model).where(or_(
|
||||
Model.city_id == city_id,
|
||||
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
|
||||
with Session(self.engine) as session:
|
||||
result_set = session.execute(select(Model).where(or_(
|
||||
Model.city_id == city_id,
|
||||
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
|
||||
|
||||
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.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from hub.helpers.auth import Auth
|
||||
from hub.persistence.repository import Repository
|
||||
@ -49,10 +50,11 @@ class User(Repository):
|
||||
pass
|
||||
try:
|
||||
user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id)
|
||||
self.session.add(user)
|
||||
self.session.flush()
|
||||
self.session.commit()
|
||||
self.session.refresh(user)
|
||||
with Session(self.engine) as session:
|
||||
session.add(user)
|
||||
session.flush()
|
||||
session.commit()
|
||||
session.refresh(user)
|
||||
return user.id
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('An error occurred while creating user %s', err)
|
||||
@ -68,13 +70,14 @@ class User(Repository):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.session.query(Model).filter(Model.id == user_id).update({
|
||||
'name': name,
|
||||
'password': Auth.hash_password(password),
|
||||
'role': role,
|
||||
'updated': datetime.datetime.utcnow()
|
||||
})
|
||||
self.session.commit()
|
||||
with Session(self.engine) as session:
|
||||
session.query(Model).filter(Model.id == user_id).update({
|
||||
'name': name,
|
||||
'password': Auth.hash_password(password),
|
||||
'role': role,
|
||||
'updated': datetime.datetime.utcnow()
|
||||
})
|
||||
session.commit()
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while updating user: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -86,8 +89,9 @@ class User(Repository):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.session.query(Model).filter(Model.id == user_id).delete()
|
||||
self.session.commit()
|
||||
with Session(self.engine) as session:
|
||||
session.query(Model).filter(Model.id == user_id).delete()
|
||||
session.commit()
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching user: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -100,10 +104,12 @@ class User(Repository):
|
||||
:return: User matching the search criteria or None
|
||||
"""
|
||||
try:
|
||||
user = self.session.execute(
|
||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||
).first()
|
||||
return user[0]
|
||||
with Session(self.engine) as session:
|
||||
user = session.execute(
|
||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||
).first()
|
||||
session.commit()
|
||||
return user[0]
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching user by name and application: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -120,12 +126,13 @@ class User(Repository):
|
||||
:return: User
|
||||
"""
|
||||
try:
|
||||
user = self.session.execute(
|
||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||
).first()
|
||||
if user:
|
||||
if Auth.check_password(password, user[0].password):
|
||||
return user[0]
|
||||
with Session(self.engine) as session:
|
||||
user = session.execute(
|
||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||
).first()
|
||||
if user:
|
||||
if Auth.check_password(password, user[0].password):
|
||||
return user[0]
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching user by name: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
@ -140,10 +147,11 @@ class User(Repository):
|
||||
:return: User
|
||||
"""
|
||||
try:
|
||||
application = self.session.execute(
|
||||
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
|
||||
).first()
|
||||
return self.get_by_name_application_id_and_password(name, password, application[0].id)
|
||||
with Session(self.engine) as session:
|
||||
application = session.execute(
|
||||
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
|
||||
).first()
|
||||
return self.get_by_name_application_id_and_password(name, password, application[0].id)
|
||||
except SQLAlchemyError as err:
|
||||
logging.error('Error while fetching user by name: %s', err)
|
||||
raise SQLAlchemyError from err
|
||||
|
@ -6,7 +6,6 @@ Project Coder Peter Yefi peteryefi@gmail.com
|
||||
"""
|
||||
import logging
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
from hub.persistence.configuration import Configuration
|
||||
|
||||
|
||||
@ -19,6 +18,5 @@ class Repository:
|
||||
try:
|
||||
self.configuration = Configuration(db_name, dotenv_path, app_env)
|
||||
self.engine = create_engine(self.configuration.connection_string)
|
||||
self.session = Session(self.engine)
|
||||
except ValueError as err:
|
||||
logging.error('Missing value for credentials: %s', err)
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""
|
||||
Hub version number
|
||||
"""
|
||||
__version__ = '0.1.8.4'
|
||||
__version__ = '0.1.8.34'
|
||||
|
25
setup.py
@ -18,24 +18,22 @@ with open(version) as f:
|
||||
exec(f.read(), main_ns)
|
||||
|
||||
|
||||
|
||||
|
||||
setup(
|
||||
name='cerc-hub',
|
||||
version=main_ns['__version__'],
|
||||
description="CERC Hub consist in a set of classes (Central data model), importers and exporters to help researchers "
|
||||
"to create better and sustainable cities",
|
||||
long_description="CERC Hub consist in 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 "
|
||||
"as part of the research group from the next generation cities institute our aim among others it's "
|
||||
description="CERC Hub consist of a set of classes (Central data model), importers and exporters to help researchers "
|
||||
"to create better and more sustainable cities",
|
||||
long_description="CERC Hub consist of a set of classes (Central data model), importers and exporters to help "
|
||||
"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 is "
|
||||
"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",
|
||||
classifiers=[
|
||||
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
],
|
||||
include_package_data=True,
|
||||
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
],
|
||||
include_package_data=True,
|
||||
packages=['hub',
|
||||
'hub.catalog_factories',
|
||||
'hub.catalog_factories.construction',
|
||||
@ -88,7 +86,8 @@ setup(
|
||||
data_files=[
|
||||
('hub', glob.glob('requirements.txt')),
|
||||
('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/costs', glob.glob('hub/data/costs/montreal_costs.xml')),
|
||||
('hub/data/customized_imports', glob.glob('hub/data/customized_imports/ashrae_archetypes.xml')),
|
||||
|
@ -103,16 +103,16 @@ class Control:
|
||||
app_env='TEST',
|
||||
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._user_id = 1
|
||||
|
||||
self._application_id = self._database.persist_application(
|
||||
'City_layers',
|
||||
'City layers test user',
|
||||
'test',
|
||||
'test',
|
||||
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()
|
||||
|
||||
@ -248,36 +248,36 @@ TestDBFactory
|
||||
for x in building.onsite_electrical_production[cte.MONTH]]
|
||||
yearly_on_site_electrical_production = [x * cte.WATTS_HOUR_TO_JULES
|
||||
for x in building.onsite_electrical_production[cte.YEAR]]
|
||||
results = json.dumps({cte.INSEL_MEB: [
|
||||
{'monthly_cooling_peak_load': monthly_cooling_peak_load},
|
||||
{'yearly_cooling_peak_load': yearly_cooling_peak_load},
|
||||
{'monthly_heating_peak_load': monthly_heating_peak_load},
|
||||
{'yearly_heating_peak_load': yearly_heating_peak_load},
|
||||
{'monthly_lighting_peak_load': monthly_lighting_peak_load},
|
||||
{'yearly_lighting_peak_load': yearly_lighting_peak_load},
|
||||
{'monthly_appliances_peak_load': monthly_appliances_peak_load},
|
||||
{'yearly_appliances_peak_load': yearly_appliances_peak_load},
|
||||
{'monthly_cooling_demand': monthly_cooling_demand},
|
||||
{'yearly_cooling_demand': yearly_cooling_demand},
|
||||
{'monthly_heating_demand': monthly_heating_demand},
|
||||
{'yearly_heating_demand': yearly_heating_demand},
|
||||
{'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand},
|
||||
{'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand},
|
||||
{'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand},
|
||||
{'yearly_appliances_electrical_demand': yearly_appliances_electrical_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},
|
||||
{'monthly_heating_consumption': monthly_heating_consumption},
|
||||
{'yearly_heating_consumption': yearly_heating_consumption},
|
||||
{'monthly_cooling_consumption': monthly_cooling_consumption},
|
||||
{'yearly_cooling_consumption': yearly_cooling_consumption},
|
||||
{'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption},
|
||||
{'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption},
|
||||
{'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption},
|
||||
{'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption},
|
||||
{'monthly_on_site_electrical_production': monthly_on_site_electrical_production},
|
||||
{'yearly_on_site_electrical_production': yearly_on_site_electrical_production}
|
||||
]})
|
||||
results = {cte.INSEL_MEB: {
|
||||
'monthly_cooling_peak_load': monthly_cooling_peak_load,
|
||||
'yearly_cooling_peak_load': yearly_cooling_peak_load,
|
||||
'monthly_heating_peak_load': monthly_heating_peak_load,
|
||||
'yearly_heating_peak_load': yearly_heating_peak_load,
|
||||
'monthly_lighting_peak_load': monthly_lighting_peak_load,
|
||||
'yearly_lighting_peak_load': yearly_lighting_peak_load,
|
||||
'monthly_appliances_peak_load': monthly_appliances_peak_load,
|
||||
'yearly_appliances_peak_load': yearly_appliances_peak_load,
|
||||
'monthly_cooling_demand': monthly_cooling_demand,
|
||||
'yearly_cooling_demand': yearly_cooling_demand,
|
||||
'monthly_heating_demand': monthly_heating_demand,
|
||||
'yearly_heating_demand': yearly_heating_demand,
|
||||
'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand,
|
||||
'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand,
|
||||
'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand,
|
||||
'yearly_appliances_electrical_demand': yearly_appliances_electrical_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,
|
||||
'monthly_heating_consumption': monthly_heating_consumption,
|
||||
'yearly_heating_consumption': yearly_heating_consumption,
|
||||
'monthly_cooling_consumption': monthly_cooling_consumption,
|
||||
'yearly_cooling_consumption': yearly_cooling_consumption,
|
||||
'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption,
|
||||
'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption,
|
||||
'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption,
|
||||
'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption,
|
||||
'monthly_on_site_electrical_production': monthly_on_site_electrical_production,
|
||||
'yearly_on_site_electrical_production': yearly_on_site_electrical_production
|
||||
}}
|
||||
|
||||
db_building_id = _building.id
|
||||
city_objects_id.append(db_building_id)
|
||||
|
@ -5,19 +5,20 @@ Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
"""
|
||||
|
||||
import logging.handlers
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
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
|
||||
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):
|
||||
@ -66,12 +67,7 @@ class TestExports(TestCase):
|
||||
|
||||
def _export(self, export_type, from_pickle=False):
|
||||
self._complete_city = self._get_complete_city(from_pickle)
|
||||
try:
|
||||
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
|
||||
ExportsFactory(export_type, self._complete_city, self._output_path, base_uri='../glb').export()
|
||||
|
||||
def _export_building_energy(self, export_type, from_pickle=False):
|
||||
self._complete_city = self._get_complete_city(from_pickle)
|
||||
@ -83,11 +79,38 @@ class TestExports(TestCase):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@ -125,4 +148,3 @@ class TestExports(TestCase):
|
||||
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
|
||||
except Exception:
|
||||
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
|
||||
|
||||
|
@ -40,7 +40,7 @@ class TestResultsImport(TestCase):
|
||||
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
|
||||
|
||||
ConstructionFactory('nrcan', self._city).enrich()
|
||||
UsageFactory('nrcan', self._city).enrich()
|
||||
UsageFactory('comnet', self._city).enrich()
|
||||
|
||||
def test_sra_import(self):
|
||||
ExportsFactory('sra', self._city, self._output_path).export()
|
||||
|