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

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

14
.gitignore vendored
View File

@ -1,2 +1,12 @@
# Default ignored files
.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

View File

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

12
hub/.gitignore vendored
View File

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

50
hub/LINUX_INSTALL.md Normal file
View File

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

View File

@ -2,16 +2,16 @@
This is an installation guide for Windows, covering all the steps needed to begin developing code for the Urban
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.

View File

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

View File

@ -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
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 += hours * cte.WEEK_DAYS_A_YEAR[key]
return _total_hours
_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]

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ from hub.city_model_structure.attributes.point import Point
from hub.city_model_structure.greenery.vegetation import Vegetation
from hub.city_model_structure.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):
"""

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -12,6 +12,7 @@ from geomeppy import IDF
import hub.helpers.constants as cte
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,8 +679,7 @@ 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 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'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,9 +4,10 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
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:
obj.write("# cerc-hub export\n")
vertex_index = 0
faces = []
vertex_index = 0
normal_index = 0
with open(obj_file_path, 'w', encoding='utf-8') as obj:
obj.write("# cerc-hub export\n")
obj.write(f'mtllib {mtl_name}\n')
for building in self._city.buildings:
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 = []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -623,7 +623,11 @@ 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

View File

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

View File

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

View File

@ -63,8 +63,7 @@ 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:
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)
@ -76,8 +75,7 @@ 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:
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)
@ -89,8 +87,7 @@ 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:
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
@ -101,8 +98,7 @@ 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:
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
@ -115,8 +111,7 @@ 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:
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
@ -137,8 +132,7 @@ 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_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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,6 +43,9 @@ class InselMonthlyEnergyBalance:
domestic_hot_water_demand = []
lighting_demand = []
appliances_demand = []
# todo: REFACTOR after retrofit project, this is a hack for the pickle files
try:
if building.internal_zones[0].thermal_zones_from_internal_zones is None:
domestic_hot_water_demand = [0] * 12
lighting_demand = [0] * 12
@ -56,7 +59,7 @@ class InselMonthlyEnergyBalance:
lighting_density = thermal_zone.lighting.density
appliances_density = thermal_zone.appliances.density
for month in range(0, 12):
for i_month, month in enumerate(cte.MONTHS):
total_dhw_demand = 0
total_lighting = 0
total_appliances = 0
@ -66,7 +69,7 @@ class InselMonthlyEnergyBalance:
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] \
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)
@ -75,7 +78,7 @@ class InselMonthlyEnergyBalance:
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] \
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)
@ -86,10 +89,15 @@ class InselMonthlyEnergyBalance:
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
* (service_temperature - cold_water[i_month]) / cte.WATTS_HOUR_TO_JULES
)
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[day_type][month] * demand
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] * demand
domestic_hot_water_demand.append(total_dhw_demand * area)
except AttributeError:
domestic_hot_water_demand = [0] * 12
lighting_demand = [0] * 12
appliances_demand = [0] * 12
logging.warning('Building internal zone raised an error, most likely the building has missing archetypes')
building.domestic_hot_water_heat_demand[cte.MONTH] = domestic_hot_water_demand
building.domestic_hot_water_heat_demand[cte.YEAR] = [sum(domestic_hot_water_demand)]

View File

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

View File

@ -24,7 +24,8 @@ class Weather:
'DE.01.082': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/DEU/DEU_Stuttgart.107380_IWEC/DEU_Stuttgart.107380_IWEC.epw',
'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?

View File

@ -75,8 +75,8 @@ 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)
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
@ -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_and_city(building_name, city_id)
_building = self._city_object.get_by_name_or_alias_in_cities(building_name, city_ids)
if _building is None:
continue
city_object_id = _building.id
_ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names(
city_id,
_ = self._simulation_results.get_simulation_results_by_city_object_id_and_names(
city_object_id,
result_names)
for value in _:
values = json.loads(value.values)
values = value.values
values["building"] = building_name
results[scenario_name].append(values)
print(scenario, results)
return results
def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int):

View File

@ -7,9 +7,10 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
import datetime
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

View File

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

View File

@ -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
wall_area = 0
window_ratio = 0
try:
if storeys is None:
storeys = building.max_height / building.average_storey_height
self.total_heating_area = building.floor_area * storeys
wall_area = 0
for wall in building.walls:
wall_area += wall.solid_polygon.area
self.wall_area = wall_area
window_ratio = 0
for internal_zone in building.internal_zones:
for 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
self.windows_area = wall_area * window_ratio
system_name = building.energy_systems_archetype_name
if system_name is None:

View File

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

View File

@ -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,9 +49,10 @@ 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)
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)
@ -65,10 +67,11 @@ class Application(Repository):
:return: None
"""
try:
self.session.query(Model).filter(
with Session(self.engine) as session:
session.query(Model).filter(
Model.application_uuid == application_uuid
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
self.session.commit()
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,7 +98,8 @@ class Application(Repository):
:return: Application with the provided application_uuid
"""
try:
result_set = self.session.execute(select(Model).where(
with Session(self.engine) as session:
result_set = session.execute(select(Model).where(
Model.application_uuid == application_uuid)
).first()
return result_set[0]

View File

@ -54,17 +54,17 @@ class City(Repository):
application_id,
user_id,
__version__)
self.session.add(db_city)
self.session.flush()
self.session.commit()
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)
self.session.add(db_city_object)
self.session.flush()
self.session.commit()
self.session.refresh(db_city)
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)
@ -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,12 +110,11 @@ class City(Repository):
:return: [ModelCity]
"""
try:
result_set = self.session.execute(select(Model).where(Model.user_id == user_id,
with Session(self.engine) as session:
result_set = session.execute(select(Model).where(Model.user_id == user_id,
Model.application_id == application_id,
Model.scenario == scenario
)).all()
self.session.close()
self.session = Session(self.engine)
return result_set
except SQLAlchemyError as err:
logging.error('Error while fetching city by name %s', err)
@ -127,11 +128,11 @@ class City(Repository):
:return: ModelCity
"""
try:
result_set = self.session.execute(
with Session(self.engine) as session:
result_set = session.execute(
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
)
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

View File

@ -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,7 +70,8 @@ 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(
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,
@ -78,7 +81,7 @@ class CityObject(Repository):
'volume': building.volume,
'area': building.floor_area,
'updated': datetime.datetime.utcnow()})
self.session.commit()
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()
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 = self.session.execute(
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
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()
self.session.close()
self.session = Session(self.engine)
for city_object in city_objects:
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
for alias in aliases:

View File

@ -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,10 +53,11 @@ 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)
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)
@ -71,20 +73,21 @@ class SimulationResults(Repository):
:return: None
"""
try:
with Session(self.engine) as session:
if city_id is not None:
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
{
'values': values,
'updated': datetime.datetime.utcnow()
})
self.session.commit()
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(
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
{
'values': values,
'updated': datetime.datetime.utcnow()
})
self.session.commit()
session.commit()
else:
raise NotImplementedError('Missing either city_id or city_object_id')
except SQLAlchemyError as err:
@ -100,12 +103,13 @@ class SimulationResults(Repository):
:return: None
"""
try:
with Session(self.engine) as session:
if city_id is not None:
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
self.session.commit()
session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
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()
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:
@ -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,7 +151,8 @@ class SimulationResults(Repository):
:return: [SimulationResult]
"""
try:
result_set = self.session.execute(select(Model).where(or_(
with Session(self.engine) as session:
result_set = session.execute(select(Model).where(or_(
Model.city_id == city_id,
Model.city_object_id == city_object_id
)))
@ -160,3 +167,27 @@ class SimulationResults(Repository):
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

View File

@ -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({
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()
})
self.session.commit()
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,9 +104,11 @@ class User(Repository):
:return: User matching the search criteria or None
"""
try:
user = self.session.execute(
with Session(self.engine) as session:
user = session.execute(
select(Model).where(Model.name == name, Model.application_id == application_id)
).first()
session.commit()
return user[0]
except SQLAlchemyError as err:
logging.error('Error while fetching user by name and application: %s', err)
@ -120,7 +126,8 @@ class User(Repository):
:return: User
"""
try:
user = self.session.execute(
with Session(self.engine) as session:
user = session.execute(
select(Model).where(Model.name == name, Model.application_id == application_id)
).first()
if user:
@ -140,7 +147,8 @@ class User(Repository):
:return: User
"""
try:
application = self.session.execute(
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)

View File

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

View File

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

View File

@ -18,16 +18,14 @@ 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=[
@ -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')),

View File

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

View File

@ -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!")

View File

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