This is the main commit containing all developments related to the CityBEM-CityLayers project.

This commit is contained in:
saeed-rayegan 2024-11-05 14:50:10 -05:00
parent dc23e30d1f
commit 8052d360dd
479 changed files with 747582 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
!.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
/out_files
/input_files/output_buildings.geojson

97
css/base.css Normal file
View File

@ -0,0 +1,97 @@
@media (prefers-color-scheme: dark) {
body { background: #333; color: white; }
}
@media (prefers-color-scheme: light) {
body { background: #ddd; color: black; }
}
html {
font-family: "Noto Sans","Open Sans",Arial,Helvetica,sans-serif;
}
.stretch {
width:100%;
height:50px;
}
#url, #name {
padding: 1px;
border-radius: 5px;
width: 500px;
color: #333;
}
#start, #end{
padding: 1px;
border-radius: 5px;
width: 50px;
color: #333;
}
button {
color: #333;
}
#main {
margin: 5px auto;
padding-left: 20px;
}
#controls {
border-radius: 10px;
overflow: hidden;
width: 600px;
margin: 10px;
position: relative;
text-align: left;
margin-top: 20px;
margin-left: 10px;
padding-left: 20px;
vertical-align: middle;
}
label {
padding-right: 10px;
}
#url_box {
padding-bottom: 20px;
}
#buttons button {
width: 200px;
}
#buttons {
padding-top: 50px;
padding-left: 50px;
}
#debug {
border-color: darkred;
color: #0CFF0C;
min-width: 100px;
}
.highlight {
color: white;
font-width: bold;
}
.error {
color: red;
font-width: bold;
}
#links a {
color: #0CFF0C;
}
#chart {
float:right;
}
#links {
z-index: 10;
position: absolute;
right: 0;
top: 0;
margin-right: -100;
}

4647
css/bootstrap.min.css vendored Normal file

File diff suppressed because it is too large Load Diff

64
energy_system_retrofit.py Normal file
View File

@ -0,0 +1,64 @@
from scripts.geojson_creator import process_geojson
from pathlib import Path
import subprocess
from scripts.ep_run_enrich import energy_plus_workflow
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.imports.results_factory import ResultFactory
from scripts.energy_system_analysis_report import EnergySystemAnalysisReport
from scripts import random_assignation
from hub.imports.energy_systems_factory import EnergySystemsFactory
from scripts.energy_system_sizing import SystemSizing
from scripts.energy_system_retrofit_results import system_results, new_system_results
from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory
from scripts.costs.cost import Cost
from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV
import hub.helpers.constants as cte
from hub.exports.exports_factory import ExportsFactory
# Specify the GeoJSON file path
geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001)
file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson')
# Specify the output path for the PDF file
output_path = (Path(__file__).parent / 'out_files').resolve()
# Create city object from GeoJSON file
city = GeometryFactory('geojson',
path=file_path,
height_field='height',
year_of_construction_field='year_of_construction',
function_field='function',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
# Enrich city data
ConstructionFactory('nrcan', city).enrich()
UsageFactory('nrcan', city).enrich()
WeatherFactory('epw', city).enrich()
ExportsFactory('sra', city, output_path).export()
sra_path = (output_path / f'{city.name}_sra.xml').resolve()
subprocess.run(['sra', str(sra_path)])
ResultFactory('sra', city, output_path).enrich()
energy_plus_workflow(city)
random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage)
EnergySystemsFactory('montreal_custom', city).enrich()
SystemSizing(city.buildings).montreal_custom()
current_system = new_system_results(city.buildings)
random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage)
EnergySystemsFactory('montreal_future', city).enrich()
for building in city.buildings:
EnergySystemsSimulationFactory('archetype1', building=building, output_path=output_path).enrich()
print(building.energy_consumption_breakdown[cte.ELECTRICITY][cte.COOLING] +
building.energy_consumption_breakdown[cte.ELECTRICITY][cte.HEATING] +
building.energy_consumption_breakdown[cte.ELECTRICITY][cte.DOMESTIC_HOT_WATER])
new_system = new_system_results(city.buildings)
# EnergySystemAnalysisReport(city, output_path).create_report(current_system, new_system)
for building in city.buildings:
costs = Cost(building=building, retrofit_scenario=SYSTEM_RETROFIT_AND_PV).life_cycle
costs.to_csv(output_path / f'{building.name}_lcc.csv')
(costs.loc['global_operational_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].
to_csv(output_path / f'{building.name}_op.csv'))
costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv(
output_path / f'{building.name}_cc.csv')
costs.loc['global_maintenance_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv(
output_path / f'{building.name}_m.csv')

60
hub/CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,60 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, rejecting any kind of discrimination.
We pledge to act and interact in ways that contribute to an open, welcoming,
and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people.
* Being respectful of differing opinions, viewpoints, and experiences.
* Giving and gracefully accepting constructive feedback.
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience.
* Focusing on what is best not just for us as individuals, but for the
overall community.
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind.
* Trolling, insulting or derogatory comments, and personal or political attacks.
* Public or private harassment.
* Publishing others' private information, such as a physical or email
address, without their explicit permission.
* Other conduct which could reasonably be considered inappropriate in a
professional setting.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, participating in a congress
or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior might be
reported to guillermo.gutierrezmorote@concordia.ca or ursula.eicker@concordia.ca.
All complaints will be reviewed and investigated promptly and fairly.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org

View File

@ -0,0 +1,164 @@
# How to create a class for the Central Data Model
This document explains the steps to follow if you want to contribute to the city data model by adding new classes or new attributes.
Use this document after having a clear idea of how your data model should look like already integrated in the Central Data Model.
Please, refer to the [cerclibs.pdf](https://liveconcordia.sharepoint.com/:b:/s/CERC-Next-GenCities-Platform/EfPNAGXexCFOju2sKBr6pNMBcwnvLin1Wio1Ahpfu4cxag?e=rhkdca)
to integrate your data model with the Central Data Model.
## Starting with the basics
- Install all requirements and download the hub project. [Here](WINDOWS_INSTALL.md) how to do it for windows.
In order to maintain a good quality code, we will work in branches. New codes will need to pass quality standards before being accepted in the main branch.
- Check and follow our [coding style](PYGUIDE.md).
- Dont forget to create unit tests and ensure that the old ones pass normally after your changes.
- Imperative! Document your work using comments in the code and, if needed, adding text files with extended explanations.
If the code doesn't pass the quality review, it will be rejected.
## Adding new parameters to existing classes
Adding a new parameter is an easy task. Open the desired class, for example, CityObject:
![city object](docs/img_contributing/img_5.png)
Add the name of your new parameter to the list at the constructor and initialize it as desired:
![new parameter](docs/img_contributing/img_6.png)
At the end of the class, add the corresponding getter and setter. It is very important that they are documented!
![getter and setter](docs/img_contributing/img_7.png)
You will see that the name of the file (city_object.py) changes from white to blue. That means that your version is different
from that one in the git. Once you finish doing your changes, you should commit and push them to your branch. The name of the file will turn back white.
## Creating a new class
Create a new class in the corresponding folder (if it does not exist, create a new folder ad hoc).
![new folder](docs/img_contributing/img_0.png)
![new file](docs/img_contributing/img_1.png)
![add to git](docs/img_contributing/img_2.png)
And add it to git (the name of the file will turn from red to green).
Every new class must have:
- A header with the following information:
```python
"""
My New Data Class module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Name of Project Coder name.project.coder@concordia.ca
"""
```
- A brief explanation of what it is, what it does, what it can be used for, etc. under its name:
```python
"""
MyNewDataClass class
This class models this and does that
"""
```
- All imported libraries together at the beginning.
![new class](docs/img_contributing/img_3.png)
A data class contains properties that describe the data model. Therefore, it should be mainly composed by getters and setters.
We would like to avoid having methods in the data classes. All those methods that could be done in the factories must be written there.
The properties can be divided into two groups, those that can be modified during the use of the model, and those that
are set only once and stay unchangeable. The line that divides these to groups is sometimes difficult to draw.
An example to get a taste of this difference could be the following. A building is formed by surfaces, the list of surfaces
is something that defines the building and, for our purposes, is unchangeable. On the other hand, if one of our studies is
to show the effect of the construction on the building demand, we may want to modify this during the run, so the construction
becomes changeable. This is important because those parameters that are static (unchangeable), must be provided for the
initialization and dont have setter, while the others are initialized at None and do have setter:
It is important to highlight that all setters and getters (@property) must have comments to describe the parameters, as shown in the previous image.
![new class getters and setters](docs/img_contributing/img_4.png)
Once you finish doing your changes, you should commit and push them to your branch. The name of the new files will change from green to white.
## Requesting a merge to the main branch
### Add a plugin
First, it is required to install a plugin for such purpose. We recommend [GitLab Merge Requests](https://plugins.jetbrains.com/plugin/18689-gitlab-merge-requests),
but you are free to choose the one you prefer. In order to install the plug, be sure that you have the latest pycharm version.
Go to Help -> Check for Updates... It will ask you to Update the new version, click on Update and Restart and follow the instructions.
![update pycharm](docs/img_contributing/img_9.png)
Don't forget to look in the bottom-right corner, there you always find the instructions, warnings, errors, announcements...
![pycharm announcement](docs/img_contributing/img_10.png)
Once you updated pycharm, go to File -> Settings... -> Plugins and search for _GitLab Merge Request_ and press Install.
![pycharm plugins](docs/img_contributing/img_11.png)
### Select the project
This step needs to be done only the first time.
Once the plugin is installed, it will appear a new tab at the bottom list called Gitlab Merge Requests as in the image:
![new tab](docs/img_contributing/img_12.png)
Click on _Clik to discover servers_ and select the gitlab.concordia.ca.
![new server](docs/img_contributing/img_13.png)
Observe that in the top-right corner of the tab, the message has changed from _No Repository_ to _Repo: /Guille/libs_.
![new repo](docs/img_contributing/img_14.png)
If you now click on Refresh Merge Request (see previous image), you will get a message asking for a token. As you don't have one yet, click on Create token.
![create token](docs/img_contributing/img_15.png)
You will be sent to the gitlab repository to create a new token. Give a name to it and check all options.
You are creating a token that has the same permits as your gitlab account has.
![create token in gitlab](docs/img_contributing/img_16.png)
A new personal access token will be created. Copy and paste it in the Access Token box.
A token is a personal and no-transferable key. Don't show it to anyone!
![copy token](docs/img_contributing/img_17.png)
![paste token](docs/img_contributing/img_18.png)
### Create merge request
Every time you want to send some changes to the main branch (merge your branch to the main one)
you will need to follow these steps.
Right clic on the blanc area and select + Create Merge Request.
![new merge request](docs/img_contributing/img_19.png)
A window will appear with the information of the request:
![request info](docs/img_contributing/img_20.png)
Clic on Assignees + and look for the project owner, in this case, Guillermo Gutierrez Morote.
Select him as assignee and clic OK.
This action will send a request for the merge. Now wait until this is accepted or rejected. You will receive an email to
the email account you use for gitlab with the answer.
Once the changes are accepted, go back to the main branch by selecting the Git tab (bottom-left). Right clic on Master and select Checkout.
![checkout master](docs/img_contributing/img_24.png)
Now pull (blue arrow), and delete the branch.
![erase branch](docs/img_contributing/img_26.png)
Now you have again the same version as in gitlab. For new changes, create a new branch and repeat the process.
## Documentation and authoring
There are two types of authors, that one who created the model and that one who coded it. If they are not the same person,
in the headers of the classes must appear just the name of the coder, who is the reference person to ask anything about the code,
and the one in charge of maintaining it, and interacting with the git.
The author of the data model will appear in the official documentation of the Insel4Cities platform. In those documents,
a larger explanation of the data model should be also added. This official documentation is under development and will be
linked here as soon as it is available.

View File

@ -0,0 +1,99 @@
# Contributing guidelines
## Push Request Checklist
Before sending your pull requests, make sure you completed this checklist:
- Read to the end [this document](CONTRIBUTING_EXTERNALS.md).
- Read [Code of Conduct](CODE_OF_CONDUCT.md).
- Check if your changes are consistent with the [Guidelines](CONTRIBUTING_EXTERNALS.md#user-content-general-guidelines-and-philosophy-for-contribution).
- Check if your changes are consistent with the [Coding Style](CONTRIBUTING_EXTERNALS.md#user-content-coding-style).
- Manually test your code and add [Unit Tests](CONTRIBUTING_EXTERNALS.md#user-content-testing-best-practices).
- Be sure that you didn't brake anything by running all unit tests in folder unittests (right click on the folder and click on the green play).
- [Document your work](CONTRIBUTING_EXTERNALS.md#user-content-documentation).
## How to become a contributor and submit your own code
### Contributor License Agreements
CERC Hub is an [LGPL licensed](LICENSE.md) software, so even if we'd love to accept your patches, before we can take them,
please be sure that you are the intellectual property owner of your code and that do you fully understand and respect our software license.
***NOTE***: Only source code that you own will go into the main repository.
### Contributing code
If you made any changes in your own project, just push them to git. You are the owner, you are the manager.
To do so, first, commit your changes by clicking on the green check at 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 one in gitlab (remote).
Once the conflicts are solved and the merge in local is done, push the changes by clicking on the green arrow.
If you have improvements to CERC Hub or want to extend the functionality, please send us your pull request as seen at [git pull request documentation](https://git-scm.com/docs/git-request-pull).
Or ASK GUILLE!!!!
Once the pull requests are approved and pass continuous integration checks, a team member will merge your changes on CERC Hub, and your code will become an integral part of Insel4D platform.
If you prefer to contribute, instead of adding new functionality, you can also take a look at our ticket system and try to fix any of the listed issues.
??????????? SURE WE WANT THIS?
### Contribution guidelines and standards
Before sending your pull request for review, make sure your changes are consistent with the guidelines, and follow the CERC Hub coding style.
#### General guidelines and philosophy for contribution
* Include unit tests when you contribute new features, as they help to:
* Prove that your code works correctly.
* Guard against future breaking changes to lower the maintenance cost.
* Bug fixes also generally require unit tests, because the presence of bugs usually indicates insufficient test coverage.
* Keep backward compatibility in mind when you change code in CERC Hub, and if you need to brake the backward compatibility, please ensure that you:
* Clearly indicate which features are affected by your changes.
* Technical reasons for the changes.
??????????? SURE WE WANT THIS?
* Tests should follow the
[testing best practices](CONTRIBUTING_EXTERNALS.md#user-content-testing-best-practices)
guide.
* [Document your contribution](CONTRIBUTING_EXTERNALS.md#user-content-documentation)
#### License
Include a small header with contact information and the code license at the top of any new file like in the following example.
"""
Name module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder name mail@concordia.ca
"""
#### Coding style
Changes to CERC Hub python code should conform to our coding style [Cerc Python Style Guide](PYGUIDE.md)
As a general basis, all contributions need to be focused on the concept of code clarity and use pylint to check your Python changes.
To install pylint and check your files against Cerc custom style definition:
To install `pylint` and check a file
with `pylint` against Cerc custom style definition:
```bash
pip install pylint
pylint --rcfile=pylintrc myfile.py
```
#### Testing best practices
Before any pull request, the code must been manually and automatically tested to ensure at least some quality minimum. There are a few practices for unit tests that we believe are important, so we encourage you to follow it.
* The test should be cls-contained, which implies that your tests will prepare and clean up everything before and after the test execution.
* We encourage you to create if possible functional tests that cover the complete workflow of the implemented functionality.
* Maximize your code coverage by ensuring that you are testing as much of your code as possible.
#### Documentation
In case of new functionality, a general overview, configuration, installation, and user manuals need to be provided by the developer; this will provide an excellent starting point for all the future users and help you detect any inconsistencies in your design.

83
hub/DEPLOYMENT.md Normal file
View File

@ -0,0 +1,83 @@
## Installing PostgreSQL Database Server on Linux (Ubuntu) ##
In the terminal, add the key to the keyring
`
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
`
Update your repositories with
`sudo apt-get update`
Install postgresql
sudo apt-get install postgresql
`
*NB: PostgreSQL DB Server runs on a default port of 5432.*
## Installing PostgreSQL Database Server on Windows ##
1. Download a Windows installer from [this link](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads).
2. Double click on the installer file and follow the prompts of the installation wizard
3. On the component selection page of the installation wizard make sure to select *PostgreSQL Server and Commandline tools*
4. You can optionally select pgAdmin 4 to install a graphical UI to access your database
5. On the password page when prompted, enter the default password (postgres) and confirm it
6. You can change the default password of 5432 on the port page. You should ensure that whatever port number you
provide is not used by another service.
7. Follow the installation wizard prompt to complete your installation. You can verify your installation by
searching for the *psql* tool from your start menu
## Installing PostgreSQL Database Server on Mac OS X ##
1. Download the Mac OS X installer from [this link](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads).
2. Launch the installation wizard by double-clicking it and follow the rest of steps as described above on
Installing PostgreSQL Database Server on Windows.
NB: Hub has been tested with version 15 of PostgreSQL
## Create Database and Database User ##
1. Connect to the PostgreSQL database server via psql by executing `sudo -u postgres psql`. You will be
be prompted for a password, the default password of *postgres* user is *postgres*. The above command may not work on
a Windows system using the default command line tool. You can access the psql tool from Windows start menu and follow
the rest of the instructions from step 2 below
2. Execute `create user <username> with encrypted password '<password>';` in the psql console to create a user.
3. Execute `create database <database-name>;` in the psql console to create a database.
4. Execute `grant all privileges on database <database-name> to <username>;` to grant the all privileges on the database
to the user created in step 2 above.
5. The created database by default, has on schema named public which you can use. However, if you wish to create
another schema, you can do so by following [this link](https://www.postgresqltutorial.com/postgresql-administration/postgresql-create-schema/).
**NB: You can grant selected privileges to the user on the database using commands [on this page](https://tableplus.com/blog/2018/04/postgresql-how-to-grant-access-to-users.html).*
The user should however have read and write permission to all tables in the database. You can as well create a database and user using the PgAdmin UI tool*
## Setting Up Database Connection Parameters
1. Create a .env file that contains the configuration parameters as explained in the *Database Configuration Parameters*
section in persistence/README.md file.
2. The .env file should contain the following credentials: database user, database password, database host an,d database port
3. Provide the *absolute path* to the .env file to the persistence importers and exporters whenever using them in your code
as shown below:
```python
from hub.persistence.db_control import DBFactory
from pathlib import Path
dotenv_path = (Path(__file__).parent / '.env').resolve()
factory = DBFactory(db_name='hub_db', app_env='PROD', dotenv_path=dotenv_path)
```
## Create Database Tables ##
Use the *DBSetup* class in the persistence package to create the required database tables as described below
```python
from hub.persistence import DBSetup
from pathlib import Path
dotenv_path = (Path(__file__).parent / '.env').resolve()
DBSetup(db_name='hub_db', app_env='PROD', dotenv_path=dotenv_path, admin_password="your password here", application_uuid="your admin application uuid")
```
The *DBSetUp* class also creates a default admin user with default credentials that can be changed.
with the import UserFactory class. The admin user (name, email, password and role) is logged into the console after it is created by the
*constructor of DBSetup*. Use can also manage users (create, read, update and delete) with user import and export factories.
**NB: Make sure to change the default admin user credentials**

163
hub/LICENSE.md Normal file
View File

@ -0,0 +1,163 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates the terms
and conditions of version 3 of the GNU General Public License, supplemented
by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser General
Public License, and the "GNU GPL" refers to version 3 of the GNU General Public
License.
"The Library" refers to a covered work governed by this License, other than
an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided by the
Library, but which is not otherwise based on the Library. Defining a subclass
of a class defined by the Library is deemed a mode of using an interface provided
by the Library.
A "Combined Work" is a work produced by combining or linking an Application
with the Library. The particular version of the Library with which the Combined
Work was made is also called the "Linked Version".
The "Minimal Corresponding Source" for a Combined Work means the Corresponding
Source for the Combined Work, excluding any source code for portions of the
Combined Work that, considered in isolation, are based on the Application,
and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the object
code and/or source code for the Application, including any data and utility
programs needed for reproducing the Combined Work from the Application, but
excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License without
being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a facility
refers to a function or data to be supplied by an Application that uses the
facility (other than as an argument passed when the facility is invoked),
then you may convey a copy of the modified version:
a) under this License, provided that you make a good faith effort to ensure
that, in the event an Application does not supply the function or data, the
facility still operates, and performs whatever part of its purpose remains
meaningful, or
b) under the GNU GPL, with none of the additional permissions of this License
applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from a header
file that is part of the Library. You may convey such object code under terms
of your choice, provided that, if the incorporated material is not limited
to numerical parameters, data structure layouts and accessors, or small macros,
inline functions and templates (ten or fewer lines in length), you do both
of the following:
a) Give prominent notice with each copy of the object code that the Library
is used in it and that the Library and its use are covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that, taken together,
effectively do not restrict modification of the portions of the Library contained
in the Combined Work and reverse engineering for debugging such modifications,
if you also do each of the following:
a) Give prominent notice with each copy of the Combined Work that the Library
is used in it and that the Library and its use are covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during execution, include
the copyright notice for the Library among these notices, as well as a reference
directing the user to the copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this License,
and the Corresponding Application Code in a form suitable for, and under terms
that permit, the user to recombine or relink the Application with a modified
version of the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
1) Use a suitable shared library mechanism for linking with the Library. A
suitable mechanism is one that (a) uses at run time a copy of the Library
already present on the user's computer system, and (b) will operate properly
with a modified version of the Library that is interface-compatible with the
Linked Version.
e) Provide Installation Information, but only if you would otherwise be required
to provide such information under section 6 of the GNU GPL, and only to the
extent that such information is necessary to install and execute a modified
version of the Combined Work produced by recombining or relinking the Application
with a modified version of the Linked Version. (If you use option 4d0, the
Installation Information must accompany the Minimal Corresponding Source and
Corresponding Application Code. If you use option 4d1, you must provide the
Installation Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the Library side
by side in a single library together with other library facilities that are
not Applications and are not covered by this License, and convey such a combined
library under terms of your choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based on the
Library, uncombined with any other library facilities, conveyed under the
terms of this License.
b) Give prominent notice with the combined library that part of it is a work
based on the Library, and explaining where to find the accompanying uncombined
form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions of the
GNU Lesser General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to address
new problems or concerns.
Each version is given a distinguishing version number. If the Library as you
received it specifies that a certain numbered version of the GNU Lesser General
Public License "or any later version" applies to it, you have the option of
following the terms and conditions either of that published version or of
any later version published by the Free Software Foundation. If the Library
as you received it does not specify a version number of the GNU Lesser General
Public License, you may choose any version of the GNU Lesser General Public
License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide whether
future versions of the GNU Lesser General Public License shall apply, that
proxy's public statement of acceptance of any version is permanent authorization
for you to choose that version for the Library.

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.

173
hub/MACOS_INSTALL.md Normal file
View File

@ -0,0 +1,173 @@
# MACOS_INSTALL
This is an installation guide for macOS, 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.
## Prepare your environment
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:
* Python Interpreter
* Python Editor
* @Chris: Miniconda??
You also need to register a user account with the CERC's code repository on Gitlab 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.
### Get a Python interpreter
1. Download the latest version of python for macOS: [Python environment](https://www.python.org/downloads/)
2. Open the downloaded .pkg file and follow the instructions to install.
### 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.
Whilst this is a personal choice we would like to recommend [PyCharm Community Edition](https://www.jetbrains.com/pycharm/download/#section=mac),
an excellent open-source python editor.
2. Open the downloaded .dmg file and then drag the PyCharm CE app into your Applications folder.
![pycharm ce](docs/img_macos_install/img_0.png)
## Get the CERC Hub source code
1. Run PyCharmCE
2. Click on the **Get from VCS** button in the top right of the window.
![get_vc_1](docs/img_macos_install/img_1.png)
3. Select **Git** as the **Version control**. For the URL use the link to the Hub repository, as seen below.
![get_vc_2](docs/img_macos_install/img_2.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**)
![git_lab](docs/img_macos_install/img_3.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.
Note: If you see a message saying Git is not installed, click on the **Download and install** and follow instructions.
4. Click the **Clone** button to download CERC Hub source code.
(If the macOS security asks if you trust executing this source code, you can click **Trust Project**.)
![trust_project](docs/img_macos_install/img_4.png)
You will then see the project directories and files below, with the hub readme document displayed.
You have successfully cloned a copy of the CERC hub onto your computer.
![hub_readme](docs/img_macos_install/img_5.png)
## Create Your Own Branch of the Hub
You now need to create a new **Branch** of the Hub source code. This is like your own special version of the Hub,
where you can safely integrate your model, without affecting the smooth running of the Main Branch Hub and the Platform.
To create your working branch you need rights to edit the project. Please, contact Guillermo (guillermo.gutierrezmorote@concordia.ca)
or Koa (kekoa.wells@concordia.ca) to get those rights and then follow the instructions.
1. Create your working branch by right-clicking on the project folder (hub) and then selecting **Git->New Branch...**
![new_branch](docs/img_macos_install/img_6.png)
2. Give a name to your branch that has some sense of what will be done with it, _updating_documentation_,
_lca_classes_,... And, click on the **Create** button.
![name_branch](docs/img_macos_install/img_7.png)
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_.
![branch_in_local](docs/img_macos_install/img_8.png)
4. Now we need to let the CERC Gitlab repository know about this new branch. You do this by right-clicking on
your branch and selecting **Push...** from the drop-down menu.
![push_branch_1](docs/img_macos_install/img_9.png)
5. Then click on the **Push** button at the bottom-right of the **Push Commits** window.
![push_branch_2](docs/img_macos_install/img_10.png)
Note - at this point you may need to login to Gitlab using you Concordia credentials.
![gitlab_credentials](docs/img_macos_install/img_11.png)
Check that your branch now appears in the _Remote_ branch hierarchy, in the bottom-left corner. If it does not appear,
contact Guille (guillermo.gutierrezmorote@concordia.ca) or Koa (kekoa.wells@concordia.ca), to find the reason of the error.
![branch_pushed](docs/img_macos_install/img_12.png)
## Configure Pycharm
When integrating your model with the Platform, it is important that you follow [CERCs coding style guidelines](PYGUIDE.md).
One of the rules is that we use two spaces as a tab instead of the standard [pep8](https://www.python.org/dev/peps/pep-0008/)
four spaces indentation. This option can be configured in PyCharm at the settings screen.
To access the settings screen, click on the gear icon in the top right corner, and select **Preferences** from
the drop-down menu. The Preferences window will appear. From the panel on the left, select
**Editor->Code Style->Python** and you will see where tab size can be changed. Change it to 2.
See the picture below.
![configure_pycharm](docs/img_macos_install/img_13.png)
## Set up a new project on Gitlab
1. Open a browser and to the [CERC Git](https://rs-loy-gitlab.concordia.ca/). Click on the blue **New project** button.
![git_new_project](docs/img_macos_install/img_14.png)
2. Choose the **Create blank project** option from the three options seen below.
![blank_project](docs/img_macos_install/img_15.png)
3. Type in a name (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**.
Then click on the **Create project** button.
![project_created](docs/img_macos_install/img_16.png)
You should then see a confirmation screen with all the information about your new project.
![confirmation_project](docs/img_macos_install/img_17.png)
## 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.
![copy_https](docs/img_macos_install/img_18.png)
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.
4. Select **Pycharm->Preferences** to open the **Preferences** window. From the panel on the left click on
**Project:<project name> -> Project Structure**.
![new_project_in_pychaem](docs/img_macos_install/img_19.png)
5. Add the Hub project to your own, by clicking on Add Content Root: (Pilar, this is where it all went wrong for me :-) )
## Create your first Python file
Add your first file to your project and click on install requirements to automatically download all the dependencies
(in blue at top-right corner).
When all the dependencies are satisfied, you are all set to start importing your first city model.
Add the following code to your main.py
from imports.geometry_factory import GeometryFactory
city = GeometryFactory('citygml', path='myfile.gml').city
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). Once the conflicts are solved and the merge in
local is done, push the changes by clicking on the green arrow.

175
hub/PYGUIDE.md Normal file
View File

@ -0,0 +1,175 @@
# Cerc Python Style Guide
## What's coding style and why it matters.
Coding style is just how the code looks, it's incredibly personal, and everyone has their style.
Your preferred architectures, variable and function naming style all of then impacts in your code style and how the others read and understand it, so it could become a significant burden if everyone is coding on his or her own.
At CERC, we are following the [PEP8](https://www.python.org/dev/peps/pep-0008/) with two spaces indentation instead of four.
## Tools.
We use [PyCharm](https://www.jetbrains.com/pycharm/) as an integrated development environment and follow the tool's overall advice but the space indentation, which we set to two spaces instead of default four spaces.
For code analysis, we enforce the usage of [pylint](https://www.pylint.org/) with our own [custom style definition](pylintrc). This file will be downloaded with the project the first time you clone it.
## Naming convention.
* Name your folders and files in lowercase and use _ (underscore) to separate words.
* Your class names must start in capital letters and follow the python CapWords pattern.
* Methods and properties that return lists must end in "s". Therefore, those that return single values, must be singular.
* Methods and variables should be lowercase and use _ (underscore) as a word separator.
* Constant names must be all capitals.
* Avoid the usage of "get_" and "set_" methods whenever it is possible, use @property and @variable.setter decorators instead.
* "Private" methods, variables and properties start with _ (underscore).
## Imports.
Place your imports at the top of the file, after the license and contact information
comment.
```python
"""
MyClass module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder name name@concordia.ca
"""
import sys
```
Ensure that your imports are used and remove any unused.
## Object attributes and methods.
Use properties whenever it is possible. Encapsulate the access to all the calculated object attributes to avoid recalculating each time the property is called.
```python
@property
def object_attribute(cls):
if cls._object_attribute is None:
cls._object_attribute = ...
...
return cls._object_attribute
```
And like in the following example for read and write properties:
```python
@property
def object_changeable_attribute(cls):
return cls._object_changeable_attribute
@object_changeable_attribute.setter
def object_changeable_attribute(cls, value):
cls._object_changeable_attribute = value
```
If your method or attribute returns a complex object, use type hints as in this example:
```python
@property
def complex_object(cls) -> ComplexObject:
return cls._object_changeable_attribute
def new_complex_object(cls, first_param, second_param) -> ComplexObject:
other_needed_property = cls.other_needed_property
return ComplexObject(first_param, second_param, other_needed_property)
```
Always access your variable through the method and avoid to access directly.
```python
@property
def object_attribute(cls):
return cls._object_attribute
def operation(cls, first_param, second_param):
return cls.object_attribute * 2
```
### Comments.
#### Code documentation.
All public classes, properties, and methods must have code comments. Code comments start with capital letters and end without period:
```python
class MyClass
"""
MyClass class perform models class operations
"""
def __init__(cls):
@property
def object_attribute(cls):
"""
Get my class object attribute
:return: int
"""
return cls._object_attribute
def operation(cls, first_param, second_param):
"""
Multiplies object_attribute by two
:return: int
"""
return cls.object_attribute * 2
```
Comments at getters and setters always start with Get and Set, and identity the type of variable at return (at getter) or the value (at setter):
```python
@property
def object_attribute(cls):
"""
Get object attribute
:return: int
"""
return cls._object_attribute
@object_attribute.setter
def object_attribute(cls, value):
"""
Set object attribute
:param value: int
"""
cls._object_attribute = value
```
Attributes with known units should be explicit in method's comment.
```python
@property
def distance(cls):
"""
My class distance in meters
:return: float
"""
return cls._distance
```
#### To do's.
Pending to implement operations should be indicated with todo comments to highlight the missing functionality.
```python
# todo: right now extracted at the city level, in the future should be extracted also at building level if exist
```

20
hub/README.md Normal file
View File

@ -0,0 +1,20 @@
# Hub
Hub is part of Insel4Cities architecture for urban simulations, created by the CERC group at Concordia University.
Hub repository contains:
* city_model_structure: a central data model specifically design to model urban environments. An instance of this is called City.
* catalog_factories: a set of classes to describe catalog structures used by the import and export factories.
* imports: factories to import data from different formats to feed the city model structure (and create a City) or the catalog structures depending on the purpose.
* exports: factories to export desired parts of the City to different formats depending on the purpose, or to export catalogs in a common format.
* data: contains offered data, either for geometry, weather or different types of catalogs.
* other folders to support manipulating data.
Released under [LGPL license](LICENSE.md), will provide an object-oriented, modular approach to urban simulations.
Our aims are:
* involve as many scientists and contributors as possible.
* provide a complete set of classes that help scientists and students to model urban environments.
Please check the [contributing information](CONTRIBUTING_EXTERNALS.md) and [code of conduct](CODE_OF_CONDUCT.md) if you want to contribute, and let us know any new feature you may be of interest for you or your team.

View File

@ -0,0 +1,60 @@
# Functions and usages internally recognized within the hub
The hub uses a list of building functions that are the only ones recognized. All new categories should be added to the dictionaries that translate from the input formats to the hub functions and from the hub functions to the output formats.
Output formats accepted:
* Function:
* nrel
* nrcan
* eilat
* Usage:
* nrcan
* comnet
* eilat
Libs_functions:
* single family house
* multi family house
* row hose
* mid rise apartment
* high rise apartment
* residential
* small office
* medium office
* large office
* primary school
* secondary school
* stand alone retail
* hospital
* out-patient health care
* strip mall
* supermarket
* ware house
* quick service restaurant
* full service restaurant
* small hotel
* large hotel
Libs_usage:
* residential
* single family house
* multi family house
* education
* school without shower
* school with shower
* retail shop without refrigerated food
* retail shop with refrigerated food
* hotel
* hotel medium class
* dormitory
* industry
* restaurant
* health care
* retirement home or orphanage
* office and administration
* event location
* hall
* sports location
* labor
* green-house
* non-heated

269
hub/WINDOWS_INSTALL.md Normal file
View File

@ -0,0 +1,269 @@
# WINDOWS_INSTALL
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 Gitea and created your first python file.
## Prepare your environment
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 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.
### Get Miniconda
1. Download [Miniconda for Windows](https://docs.conda.io/en/latest/miniconda.html) for Python version 3.9.
2. Run the installer and select in the corresponding window _Register Miniconda3 as my default Python 3.9_.
![conda](docs/img_windows_install/img_20.png)
If you already installed a Python interpreter, the following message will warn you:
![conda_2](docs/img_windows_install/img_21.png)
Click **OK** and then, click **Install**.
Once you are done, add Miniconda to the Path by clicking on **New** and writing the path where _conda.exe_ was installed.:
![path_1](docs/img_windows_install/img_27.png)
![path_2](docs/img_windows_install/img_28.png)
Now, restart the computer to update the Path.
Finally, open a terminal and type 'conda init powershell'.
![terminal_conda](docs/img_windows_install/img_30.png)
**NOTE:** This final step could produce the following error:
_The term '...' is not recognized as the name of a cmdlet, function,..._
![error_conda](docs/img_windows_install/img_32.png)
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.
Whilst this is a personal choice we would like to recommend [PyCharm Community Edition](https://www.jetbrains.com/pycharm/download/#section=windows),
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.
![creating_virtual_environment](docs/img_windows_install/img_31.png)
## Get the CERC Hub source code
1. Run PyCharmCE
2. Click on the **Get from VCS** button in the top right of the window.
![pycharm welcome screen](docs/img_windows_install/img_0.png)
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**. 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)
![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.
Note: If you see a message saying Git is not installed, click on the **Download and install** and follow instructions.
4. Click the **Clone** button to download CERC hub source code. You will then see the project directories and files below,
with the hub readme document displayed. You have successfully cloned a copy of the CERC hub onto your computer.
![pycharm_project_screen](docs/img_windows_install/img_2.png)
## Configure Python Interpreter
1. Click on **File**, and select **Settings...** from the drop-down menu. The Settings window will appear.
From the panel on the left, select **Project: hub->Python Interpreter** and you will see the following window:
![configure_interpreter_1](docs/img_windows_install/img_22.png)
2. Click on the little gear on the right and click **Add...**. In the new window, click on **Conda Environment**,
select **New environment** and the Python version 3.9.
![configure_interpreter_2](docs/img_windows_install/img_25.png)
You should end with the following window:
![configure_interpreter_3](docs/img_windows_install/img_26.png)
Click **OK** to close it.
**NOTE:** This final step could produce the following error:
![conda_environment_error](docs/img_windows_install/img_33.png)
To solve it, add to the Path (as explained in [previous chapter Get Miniconda](WINDOWS_INSTALL.md#Get Miniconda))
the following paths:
* C:\Users\User\miniconda3\Library\bin
* C:\Users\User\miniconda3
* C:\Users\User\miniconda3\Scripts
Restart the computer and come back to step 1 of this section.
5. Go to the Terminal (a tab in the bottom) to finish this process. You should see
_PS C:\Users\Pilar\PycharmProjects\hub>_ preceded either by _(base)_ or _(hub)_. If non of those are there,
be sure that you have Miniconda installed and included in the path as explained in
[previous chapter Get Miniconda](WINDOWS_INSTALL.md#Get Miniconda). If not, do it, restart Pycharm, and come back to this point.
If the environment you are working in is _(base)_, type 'conda activate ' and the project's name, in this case 'conda activate hub'.
Click enter, and the environment should change to _(hub)_. If this didn't work, contact Guillermo
(guillermo.gutierrezmorote@concordia.ca) or Koa (kekoa.wells@concordia.ca) for some help.
![configure interpreter 4](docs/img_windows_install/img_29.png)
6. Once you are in _(hub)_, type 'pip install -r .\requirements.txt' and wait until all requirements are installed. In the
bottom-right corner you should be able to see a bar showing the progress.
## Create Your Own Branch of the Hub
You now need to create a new **Branch** of the Hub source code. This is like your own special version of the Hub,
where you can safely integrate your model, without affecting the smooth running of the Main Branch Hub and the Platform.
To create your working branch you need rights to edit the project. Please, contact Guillermo (guillermo.gutierrezmorote@concordia.ca)
or Koa (kekoa.wells@concordia.ca) to get those rights and then follow the instructions.
1. Create your working branch by right-clicking on the project folder (hub) and then selecting **Git->New Branch...**
![create new branch 1](docs/img_windows_install/img_9.png)
2. Give a name to your branch that has some sense of what will be done with it, _updating_documentation_,
_lca_classes_,... And, click on the **Create** button.
![push new branch 1](docs/img_windows_install/img_11.png)
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 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.
![push new branch 2](docs/img_windows_install/img_12.png)
Check that your branch now appears in the _Remote_ branch hierarchy, in the bottom-left corner. If it does not appear,
contact Guillermo (guillermo.gutierrezmorote@concordia.ca) or Koa (kekoa.wells@concordia.ca), to find the reason of the error.
![check all set](docs/img_windows_install/img_13.png)
If your branch is there, you are done with this part.
Now you are all set to contribute to Hub or to use it for your own projects!
## Configure Pycharm
When integrating your model with the Platform, it is important that you follow [CERCs coding style guidelines](PYGUIDE.md).
One of the rules is that we use two spaces as a tab instead of the standard [pep8](https://www.python.org/dev/peps/pep-0008/)
four spaces indentation. This option can be configured in PyCharm at the settings screen.
To access the settings screen, click on **File**, and select **Settings...** from
the drop-down menu. The Settings window will appear. From the panel on the left, select
**Editor->Code Style->Python** and you will see where tab size can be changed. Change it to 2.
See the picture below.
![pycharm configuration screen](docs/img_windows_install/img_5.png)
## 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 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_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)).
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_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, 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 Gitea project.
4. Select **File->Settings** to open the **Settings** window. From the panel on the left click on
**Project:<project name> -> Project Structure**.
5. Add the Hub project to your own, by clicking on Add Content Root:
![pycharm new project screen](docs/img_windows_install/img_4.png)
![pycharm add hub](docs/img_windows_install/img_7.png)
6. Still in the **Settings** window, configure your Python interpreter (yes, again, you have to do this step for each new project)
by following steps 1 to 5 explained in [chapter Configure Python Interpreter](WINDOWS_INSTALL.md#Configure Python Interpreter).
Then, to install the requirements, type 'cd..' and enter. Then 'pip install -r .\hub\requirements.txt' and wait until all requirements are installed. In the
bottom-right corner you should be able to see a bar showing the progress. Finally, type 'cd ' and your project's name.
7. Now, add your first file to your project and click on install requirements to automatically download all the dependencies (in blue at top-right corner).
![pycharm add dependencies](docs/img_windows_install/img_8.png)
If the blue message doesn't appear, click on the **Terminal** tab (bottom-left), type _pip install -r .\requirements.txt_
and press enter.
8. When all the dependencies are satisfied, you are all set to start importing your first city model.
Add the following code to your main.py
```python
from hub.imports import GeometryFactory
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 (Gitea).
Once the conflicts are solved and the merge in local is done, push the changes by clicking on the green arrow.

0
hub/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,34 @@
"""
Catalog base class
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
class Catalog:
"""
Catalogs base class
catalog_factories will inherit from this class.
"""
def names(self, category=None):
"""
Base property to return the catalog entries names.
:return: Catalog names filter by category if provided
"""
raise NotImplementedError
def entries(self, category=None):
"""
Base property to return the catalog entries
:return: Catalog content filter by category if provided
"""
raise NotImplementedError
def get_entry(self, name):
"""
Base property to return the catalog entry matching the given name
:return: Catalog entry with the matching name
"""
raise NotImplementedError

View File

@ -0,0 +1,63 @@
"""
Construction helper module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
"""
from hub.helpers import constants as cte
class ConstructionHelper:
"""
Construction helper class
"""
_reference_standard_to_construction_period = {
'non_standard_dompark': '1900 - 2004',
'ASHRAE 90.1_2004': '2004 - 2009',
'ASHRAE 189.1_2009': '2009 - PRESENT'
}
_nrel_surfaces_types_to_hub_types = {
'exterior wall': cte.WALL,
'interior wall': cte.INTERIOR_WALL,
'ground wall': cte.GROUND_WALL,
'exterior slab': cte.GROUND,
'attic floor': cte.ATTIC_FLOOR,
'interior slab': cte.INTERIOR_SLAB,
'roof': cte.ROOF
}
_nrcan_surfaces_types_to_hub_types = {
'OutdoorsWall': cte.WALL,
'OutdoorsRoofCeiling': cte.ROOF,
'OutdoorsFloor': cte.ATTIC_FLOOR,
'Window': cte.WINDOW,
'Skylight': cte.SKYLIGHT,
'GroundWall': cte.GROUND_WALL,
'GroundRoofCeiling': cte.GROUND_WALL,
'GroundFloor': cte.GROUND
}
@property
def reference_standard_to_construction_period(self):
"""
Get reference standard to construction period dictionary
:return: {}
"""
return self._reference_standard_to_construction_period
@property
def nrel_surfaces_types_to_hub_types(self):
"""
Get reference nrel surface type to hub type dictionary
:return: {}
"""
return self._nrel_surfaces_types_to_hub_types
@property
def nrcan_surfaces_types_to_hub_types(self):
"""
Get reference nrcan surface type to hub type dictionary
:return: {}
"""
return self._nrcan_surfaces_types_to_hub_types

View File

@ -0,0 +1,233 @@
"""
Eilat construction catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import json
from pathlib import Path
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.construction.content import Content
from hub.catalog_factories.construction.construction_helper import ConstructionHelper
from hub.catalog_factories.data_models.construction.construction import Construction
from hub.catalog_factories.data_models.construction.archetype import Archetype
from hub.catalog_factories.data_models.construction.window import Window
from hub.catalog_factories.data_models.construction.material import Material
from hub.catalog_factories.data_models.construction.layer import Layer
import hub.helpers.constants as cte
class EilatCatalog(Catalog):
"""
Eilat catalog class
"""
def __init__(self, path):
_path_archetypes = Path(path / 'eilat_archetypes.json').resolve()
_path_constructions = (path / 'eilat_constructions.json').resolve()
with open(_path_archetypes, 'r', encoding='utf-8') as file:
self._archetypes = json.load(file)
with open(_path_constructions, 'r', encoding='utf-8') as file:
self._constructions = json.load(file)
self._catalog_windows = self._load_windows()
self._catalog_materials = self._load_materials()
self._catalog_constructions = self._load_constructions()
self._catalog_archetypes = self._load_archetypes()
# store the full catalog data model in self._content
self._content = Content(self._catalog_archetypes,
self._catalog_constructions,
self._catalog_materials,
self._catalog_windows)
def _load_windows(self):
_catalog_windows = []
windows = self._constructions['transparent_surfaces']
for window in windows:
name = list(window.keys())[0]
window_id = name
g_value = window[name]['shgc']
window_type = window[name]['type']
frame_ratio = window[name]['frame_ratio']
overall_u_value = window[name]['u_value']
_catalog_windows.append(Window(window_id, frame_ratio, g_value, overall_u_value, name, window_type))
return _catalog_windows
def _load_materials(self):
_catalog_materials = []
materials = self._constructions['materials']
for material in materials:
name = list(material.keys())[0]
material_id = name
no_mass = material[name]['no_mass']
thermal_resistance = None
conductivity = None
density = None
specific_heat = None
solar_absorptance = None
thermal_absorptance = None
visible_absorptance = None
if no_mass:
thermal_resistance = material[name]['thermal_resistance']
else:
solar_absorptance = material[name]['solar_absorptance']
thermal_absorptance = str(1 - float(material[name]['thermal_emittance']))
visible_absorptance = material[name]['visible_absorptance']
conductivity = material[name]['conductivity']
density = material[name]['density']
specific_heat = material[name]['specific_heat']
_material = Material(material_id,
name,
solar_absorptance,
thermal_absorptance,
visible_absorptance,
no_mass,
thermal_resistance,
conductivity,
density,
specific_heat)
_catalog_materials.append(_material)
return _catalog_materials
def _load_constructions(self):
_catalog_constructions = []
constructions = self._constructions['opaque_surfaces']
for construction in constructions:
name = list(construction.keys())[0]
construction_id = name
construction_type = ConstructionHelper().nrcan_surfaces_types_to_hub_types[construction[name]['type']]
layers = []
for layer in construction[name]['layers']:
layer_id = layer
layer_name = layer
material_id = layer
thickness = construction[name]['layers'][layer]
for material in self._catalog_materials:
if str(material_id) == str(material.id):
layers.append(Layer(layer_id, layer_name, material, thickness))
break
_catalog_constructions.append(Construction(construction_id, construction_type, name, layers))
return _catalog_constructions
def _load_archetypes(self):
_catalog_archetypes = []
archetypes = self._archetypes['archetypes']
for archetype in archetypes:
archetype_id = f'{archetype["function"]}_{archetype["period_of_construction"]}_{archetype["climate_zone"]}'
function = archetype['function']
name = archetype_id
climate_zone = archetype['climate_zone']
construction_period = archetype['period_of_construction']
average_storey_height = archetype['average_storey_height']
extra_loses_due_to_thermal_bridges = archetype['extra_loses_due_thermal_bridges']
infiltration_rate_for_ventilation_system_off = archetype['infiltration_rate_for_ventilation_system_off'] / cte.HOUR_TO_SECONDS
infiltration_rate_for_ventilation_system_on = archetype['infiltration_rate_for_ventilation_system_on'] / cte.HOUR_TO_SECONDS
archetype_constructions = []
for archetype_construction in archetype['constructions']:
archetype_construction_type = ConstructionHelper().nrcan_surfaces_types_to_hub_types[archetype_construction]
archetype_construction_name = archetype['constructions'][archetype_construction]['opaque_surface_name']
for construction in self._catalog_constructions:
if archetype_construction_type == construction.type and construction.name == archetype_construction_name:
_construction = None
_window = None
_window_ratio = None
if 'transparent_surface_name' in archetype['constructions'][archetype_construction].keys():
_window_ratio = archetype['constructions'][archetype_construction]['transparent_ratio']
_window_id = archetype['constructions'][archetype_construction]['transparent_surface_name']
for window in self._catalog_windows:
if _window_id == window.id:
_window = window
break
_construction = Construction(construction.id,
construction.type,
construction.name,
construction.layers,
_window_ratio,
_window)
archetype_constructions.append(_construction)
break
_catalog_archetypes.append(Archetype(archetype_id,
name,
function,
climate_zone,
construction_period,
archetype_constructions,
average_storey_height,
None,
extra_loses_due_to_thermal_bridges,
None,
infiltration_rate_for_ventilation_system_off,
infiltration_rate_for_ventilation_system_on))
return _catalog_archetypes
def names(self, category=None):
"""
Get the catalog elements names
:parm: optional category filter
"""
if category is None:
_names = {'archetypes': [], 'constructions': [], 'materials': [], 'windows': []}
for archetype in self._content.archetypes:
_names['archetypes'].append(archetype.name)
for construction in self._content.constructions:
_names['constructions'].append(construction.name)
for material in self._content.materials:
_names['materials'].append(material.name)
for window in self._content.windows:
_names['windows'].append(window.name)
else:
_names = {category: []}
if category.lower() == 'archetypes':
for archetype in self._content.archetypes:
_names[category].append(archetype.name)
elif category.lower() == 'constructions':
for construction in self._content.constructions:
_names[category].append(construction.name)
elif category.lower() == 'materials':
for material in self._content.materials:
_names[category].append(material.name)
elif category.lower() == 'windows':
for window in self._content.windows:
_names[category].append(window.name)
else:
raise ValueError(f'Unknown category [{category}]')
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: optional category filter
"""
if category is None:
return self._content
if category.lower() == 'archetypes':
return self._content.archetypes
if category.lower() == 'constructions':
return self._content.constructions
if category.lower() == 'materials':
return self._content.materials
if category.lower() == 'windows':
return self._content.windows
raise ValueError(f'Unknown category [{category}]')
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for entry in self._content.archetypes:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.constructions:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.materials:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.windows:
if entry.name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,238 @@
"""
NRCAN construction catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import json
from pathlib import Path
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.construction.content import Content
from hub.catalog_factories.construction.construction_helper import ConstructionHelper
from hub.catalog_factories.data_models.construction.construction import Construction
from hub.catalog_factories.data_models.construction.archetype import Archetype
from hub.catalog_factories.data_models.construction.window import Window
from hub.catalog_factories.data_models.construction.material import Material
from hub.catalog_factories.data_models.construction.layer import Layer
import hub.helpers.constants as cte
class NrcanCatalog(Catalog):
"""
Nrcan catalog class
"""
def __init__(self, path):
_path_archetypes = Path(path / 'nrcan_archetypes.json').resolve()
_path_constructions = (path / 'nrcan_constructions.json').resolve()
with open(_path_archetypes, 'r', encoding='utf-8') as file:
self._archetypes = json.load(file)
with open(_path_constructions, 'r', encoding='utf-8') as file:
self._constructions = json.load(file)
self._catalog_windows = self._load_windows()
self._catalog_materials = self._load_materials()
self._catalog_constructions = self._load_constructions()
self._catalog_archetypes = self._load_archetypes()
# store the full catalog data model in self._content
self._content = Content(self._catalog_archetypes,
self._catalog_constructions,
self._catalog_materials,
self._catalog_windows)
def _load_windows(self):
_catalog_windows = []
windows = self._constructions['transparent_surfaces']
for window in windows:
name = list(window.keys())[0]
window_id = name
g_value = window[name]['shgc']
window_type = window[name]['type']
frame_ratio = window[name]['frame_ratio']
overall_u_value = window[name]['u_value']
_catalog_windows.append(Window(window_id, frame_ratio, g_value, overall_u_value, name, window_type))
return _catalog_windows
def _load_materials(self):
_catalog_materials = []
materials = self._constructions['materials']
for material in materials:
name = list(material.keys())[0]
material_id = name
no_mass = material[name]['no_mass']
thermal_resistance = None
conductivity = None
density = None
specific_heat = None
solar_absorptance = None
thermal_absorptance = None
visible_absorptance = None
if no_mass:
thermal_resistance = material[name]['thermal_resistance']
else:
solar_absorptance = material[name]['solar_absorptance']
thermal_absorptance = str(1 - float(material[name]['thermal_emittance']))
visible_absorptance = material[name]['visible_absorptance']
conductivity = material[name]['conductivity']
density = material[name]['density']
specific_heat = material[name]['specific_heat']
_material = Material(material_id,
name,
solar_absorptance,
thermal_absorptance,
visible_absorptance,
no_mass,
thermal_resistance,
conductivity,
density,
specific_heat)
_catalog_materials.append(_material)
return _catalog_materials
def _load_constructions(self):
_catalog_constructions = []
constructions = self._constructions['opaque_surfaces']
for construction in constructions:
name = list(construction.keys())[0]
construction_id = name
construction_type = ConstructionHelper().nrcan_surfaces_types_to_hub_types[construction[name]['type']]
layers = []
for layer in construction[name]['layers']:
layer_id = layer
layer_name = layer
material_id = layer
thickness = construction[name]['layers'][layer]
for material in self._catalog_materials:
if str(material_id) == str(material.id):
layers.append(Layer(layer_id, layer_name, material, thickness))
break
_catalog_constructions.append(Construction(construction_id, construction_type, name, layers))
return _catalog_constructions
def _load_archetypes(self):
_catalog_archetypes = []
archetypes = self._archetypes['archetypes']
for archetype in archetypes:
archetype_id = f'{archetype["function"]}_{archetype["period_of_construction"]}_{archetype["climate_zone"]}'
function = archetype['function']
name = archetype_id
climate_zone = archetype['climate_zone']
construction_period = archetype['period_of_construction']
average_storey_height = archetype['average_storey_height']
thermal_capacity = float(archetype['thermal_capacity']) * 1000
extra_loses_due_to_thermal_bridges = archetype['extra_loses_due_thermal_bridges']
infiltration_rate_for_ventilation_system_off = (
archetype['infiltration_rate_for_ventilation_system_off'] / cte.HOUR_TO_SECONDS
)
infiltration_rate_for_ventilation_system_on = (
archetype['infiltration_rate_for_ventilation_system_on'] / cte.HOUR_TO_SECONDS
)
archetype_constructions = []
for archetype_construction in archetype['constructions']:
archetype_construction_type = ConstructionHelper().nrcan_surfaces_types_to_hub_types[archetype_construction]
archetype_construction_name = archetype['constructions'][archetype_construction]['opaque_surface_name']
for construction in self._catalog_constructions:
if archetype_construction_type == construction.type and construction.name == archetype_construction_name:
_construction = None
_window = None
_window_ratio = None
if 'transparent_surface_name' in archetype['constructions'][archetype_construction].keys():
_window_ratio = archetype['constructions'][archetype_construction]['transparent_ratio']
_window_id = archetype['constructions'][archetype_construction]['transparent_surface_name']
for window in self._catalog_windows:
if _window_id == window.id:
_window = window
break
_construction = Construction(construction.id,
construction.type,
construction.name,
construction.layers,
_window_ratio,
_window)
archetype_constructions.append(_construction)
break
_catalog_archetypes.append(Archetype(archetype_id,
name,
function,
climate_zone,
construction_period,
archetype_constructions,
average_storey_height,
thermal_capacity,
extra_loses_due_to_thermal_bridges,
None,
infiltration_rate_for_ventilation_system_off,
infiltration_rate_for_ventilation_system_on))
return _catalog_archetypes
def names(self, category=None):
"""
Get the catalog elements names
:parm: optional category filter
"""
if category is None:
_names = {'archetypes': [], 'constructions': [], 'materials': [], 'windows': []}
for archetype in self._content.archetypes:
_names['archetypes'].append(archetype.name)
for construction in self._content.constructions:
_names['constructions'].append(construction.name)
for material in self._content.materials:
_names['materials'].append(material.name)
for window in self._content.windows:
_names['windows'].append(window.name)
else:
_names = {category: []}
if category.lower() == 'archetypes':
for archetype in self._content.archetypes:
_names[category].append(archetype.name)
elif category.lower() == 'constructions':
for construction in self._content.constructions:
_names[category].append(construction.name)
elif category.lower() == 'materials':
for material in self._content.materials:
_names[category].append(material.name)
elif category.lower() == 'windows':
for window in self._content.windows:
_names[category].append(window.name)
else:
raise ValueError(f'Unknown category [{category}]')
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: optional category filter
"""
if category is None:
return self._content
if category.lower() == 'archetypes':
return self._content.archetypes
if category.lower() == 'constructions':
return self._content.constructions
if category.lower() == 'materials':
return self._content.materials
if category.lower() == 'windows':
return self._content.windows
raise ValueError(f'Unknown category [{category}]')
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for entry in self._content.archetypes:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.constructions:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.materials:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.windows:
if entry.name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,235 @@
"""
Nrel construction catalog
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 xmltodict
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.construction.window import Window
from hub.catalog_factories.data_models.construction.material import Material
from hub.catalog_factories.data_models.construction.layer import Layer
from hub.catalog_factories.data_models.construction.construction import Construction
from hub.catalog_factories.data_models.construction.content import Content
from hub.catalog_factories.data_models.construction.archetype import Archetype
from hub.catalog_factories.construction.construction_helper import ConstructionHelper
import hub.helpers.constants as cte
class NrelCatalog(Catalog):
"""
Nrel catalog class
"""
def __init__(self, path):
archetypes_path = str(Path(path / 'us_archetypes.xml').resolve())
constructions_path = str(Path(path / 'us_constructions.xml').resolve())
with open(constructions_path, 'r', encoding='utf-8') as xml:
self._constructions = xmltodict.parse(xml.read(), force_list=('material', 'window', 'construction', 'layer'))
with open(archetypes_path, 'r', encoding='utf-8') as xml:
self._archetypes = xmltodict.parse(xml.read(), force_list=('archetype', 'construction'))
self._catalog_windows = self._load_windows()
self._catalog_materials = self._load_materials()
self._catalog_constructions = self._load_constructions()
self._catalog_archetypes = self._load_archetypes()
# store the full catalog data model in self._content
self._content = Content(self._catalog_archetypes,
self._catalog_constructions,
self._catalog_materials,
self._catalog_windows)
def _load_windows(self):
_catalog_windows = []
windows = self._constructions['library']['windows']['window']
for window in windows:
frame_ratio = float(window['frame_ratio']['#text'])
g_value = window['shgc']
overall_u_value = float(window['conductivity']['#text']) / float(window['thickness']['#text'])
name = window['@name']
window_id = window['@id']
_catalog_windows.append(Window(window_id, frame_ratio, g_value, overall_u_value, name))
return _catalog_windows
def _load_materials(self):
_catalog_materials = []
materials = self._constructions['library']['materials']['material']
for material in materials:
material_id = material['@id']
name = material['@name']
solar_absorptance = float(material['solar_absorptance']['#text'])
thermal_absorptance = float(material['thermal_absorptance']['#text'])
visible_absorptance = float(material['visible_absorptance']['#text'])
no_mass = False
thermal_resistance = None
conductivity = None
density = None
specific_heat = None
if 'no_mass' in material and material['no_mass'] == 'true':
no_mass = True
thermal_resistance = float(material['thermal_resistance']['#text'])
else:
conductivity = float(material['conductivity']['#text'])
density = float(material['density']['#text'])
specific_heat = float(material['specific_heat']['#text'])
_material = Material(material_id,
name,
solar_absorptance,
thermal_absorptance,
visible_absorptance,
no_mass,
thermal_resistance,
conductivity,
density,
specific_heat)
_catalog_materials.append(_material)
return _catalog_materials
def _load_constructions(self):
_catalog_constructions = []
constructions = self._constructions['library']['constructions']['construction']
for construction in constructions:
construction_id = construction['@id']
construction_type = ConstructionHelper().nrel_surfaces_types_to_hub_types[construction['@type']]
name = construction['@name']
layers = []
for layer in construction['layers']['layer']:
layer_id = layer['@id']
layer_name = layer['@name']
material_id = layer['material'][0]
thickness = 0
if 'thickness' in layer:
thickness = float(layer['thickness']['#text'])
for material in self._catalog_materials:
if str(material_id) == str(material.id):
layers.append(Layer(layer_id, layer_name, material, thickness))
break
_catalog_constructions.append(Construction(construction_id, construction_type, name, layers))
return _catalog_constructions
def _load_archetypes(self):
_catalog_archetypes = []
archetypes = self._archetypes['archetypes']['archetype']
for archetype in archetypes:
archetype_id = archetype['@id']
function = archetype['@building_type']
name = f"{function} {archetype['@climate_zone']} {archetype['@reference_standard']}"
climate_zone = archetype['@climate_zone']
construction_period = ConstructionHelper().reference_standard_to_construction_period[
archetype['@reference_standard']
]
average_storey_height = float(archetype['average_storey_height']['#text'])
thermal_capacity = float(archetype['thermal_capacity']['#text']) * 1000
extra_loses_due_to_thermal_bridges = float(archetype['extra_loses_due_to_thermal_bridges']['#text'])
indirect_heated_ratio = float(archetype['indirect_heated_ratio']['#text'])
infiltration_rate_for_ventilation_system_off = float(
archetype['infiltration_rate_for_ventilation_system_off']['#text']
) / cte.HOUR_TO_SECONDS
infiltration_rate_for_ventilation_system_on = float(
archetype['infiltration_rate_for_ventilation_system_on']['#text']
) / cte.HOUR_TO_SECONDS
archetype_constructions = []
for archetype_construction in archetype['constructions']['construction']:
for construction in self._catalog_constructions:
if construction.id == archetype_construction['@id']:
window_ratio = float(archetype_construction['window_ratio']['#text'])
window_id = archetype_construction['window']
_construction = None
_window = None
for window in self._catalog_windows:
if window_id == window.id:
_window = window
break
_construction = Construction(construction.id,
construction.type,
construction.name,
construction.layers,
window_ratio,
_window)
archetype_constructions.append(_construction)
break
_catalog_archetypes.append(Archetype(archetype_id,
name,
function,
climate_zone,
construction_period,
archetype_constructions,
average_storey_height,
thermal_capacity,
extra_loses_due_to_thermal_bridges,
indirect_heated_ratio,
infiltration_rate_for_ventilation_system_off,
infiltration_rate_for_ventilation_system_on))
return _catalog_archetypes
def names(self, category=None):
"""
Get the catalog elements names
:parm: optional category filter
"""
if category is None:
_names = {'archetypes': [], 'constructions': [], 'materials': [], 'windows': []}
for archetype in self._content.archetypes:
_names['archetypes'].append(archetype.name)
for construction in self._content.constructions:
_names['constructions'].append(construction.name)
for material in self._content.materials:
_names['materials'].append(material.name)
for window in self._content.windows:
_names['windows'].append(window.name)
else:
_names = {category: []}
if category.lower() == 'archetypes':
for archetype in self._content.archetypes:
_names[category].append(archetype.name)
elif category.lower() == 'constructions':
for construction in self._content.constructions:
_names[category].append(construction.name)
elif category.lower() == 'materials':
for material in self._content.materials:
_names[category].append(material.name)
elif category.lower() == 'windows':
for window in self._content.windows:
_names[category].append(window.name)
else:
raise ValueError(f'Unknown category [{category}]')
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: optional category filter
"""
if category is None:
return self._content
if category.lower() == 'archetypes':
return self._content.archetypes
if category.lower() == 'constructions':
return self._content.constructions
if category.lower() == 'materials':
return self._content.materials
if category.lower() == 'windows':
return self._content.windows
raise ValueError(f'Unknown category [{category}]')
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for entry in self._content.archetypes:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.constructions:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.materials:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.windows:
if entry.name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,57 @@
"""
Construction catalog factory, publish the construction information
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
from typing import TypeVar
from hub.catalog_factories.construction.nrcan_catalog import NrcanCatalog
from hub.catalog_factories.construction.nrel_catalog import NrelCatalog
from hub.catalog_factories.construction.eilat_catalog import EilatCatalog
from hub.helpers.utils import validate_import_export_type
Catalog = TypeVar('Catalog')
class ConstructionCatalogFactory:
"""
Construction catalog factory class
"""
def __init__(self, handler, base_path=None):
if base_path is None:
base_path = Path(Path(__file__).parent.parent / 'data/construction')
self._handler = '_' + handler.lower()
validate_import_export_type(ConstructionCatalogFactory, handler)
self._path = base_path
@property
def _nrel(self):
"""
Retrieve NREL catalog
"""
return NrelCatalog(self._path)
@property
def _nrcan(self):
"""
Retrieve NRCAN catalog
"""
return NrcanCatalog(self._path)
@property
def _eilat(self):
"""
Retrieve Eilat catalog
"""
return EilatCatalog(self._path)
@property
def catalog(self) -> Catalog:
"""
Enrich the city given to the class using the class given handler
:return: Catalog
"""
return getattr(self, self._handler, lambda: None)

View File

View File

@ -0,0 +1,211 @@
"""
Cost catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import xmltodict
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.cost.archetype import Archetype
from hub.catalog_factories.data_models.cost.content import Content
from hub.catalog_factories.data_models.cost.capital_cost import CapitalCost
from hub.catalog_factories.data_models.cost.chapter import Chapter
from hub.catalog_factories.data_models.cost.item_description import ItemDescription
from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost
from hub.catalog_factories.data_models.cost.fuel import Fuel
from hub.catalog_factories.data_models.cost.income import Income
class MontrealNewCatalog(Catalog):
"""
Montreal custom catalog class
"""
def __init__(self, path):
path = (path / 'montreal_costs_completed.xml').resolve()
with open(path, 'r', encoding='utf-8') as xml:
self._archetypes = xmltodict.parse(xml.read(), force_list='archetype')
# store the full catalog data model in self._content
self._content = Content(self._load_archetypes())
def _load_archetypes(self):
_catalog_archetypes = []
archetypes = self._archetypes['archetypes']['archetype']
for archetype in archetypes:
lod = float(archetype['@lod'])
function = archetype['@function']
municipality = archetype['@municipality']
country = archetype['@country']
currency = archetype['currency']
capital_cost = self.load_capital_costs(archetype)
operational_cost = self._get_operational_costs(archetype['operational_cost'])
end_of_life_cost = float(archetype['end_of_life_cost']['#text'])
construction = float(archetype['incomes']['subsidies']['construction']['#text'])
hvac = float(archetype['incomes']['subsidies']['hvac']['#text'])
photovoltaic_system = float(archetype['incomes']['subsidies']['photovoltaic']['#text'])
electricity_exports = float(archetype['incomes']['electricity_export']['#text']) / 1000 / 3600
reduction_tax = float(archetype['incomes']['tax_reduction']['#text']) / 100
income = Income(construction_subsidy=construction,
hvac_subsidy=hvac,
photovoltaic_subsidy=photovoltaic_system,
electricity_export=electricity_exports,
reductions_tax=reduction_tax)
_catalog_archetypes.append(Archetype(lod,
function,
municipality,
country,
currency,
capital_cost,
operational_cost,
end_of_life_cost,
income))
return _catalog_archetypes
@staticmethod
def item_description(item_type, item):
if 'refurbishment_cost' in item.keys():
_refurbishment = float(item['refurbishment_cost']['#text'])
_refurbishment_unit = item['refurbishment_cost']['@cost_unit']
_item_description = ItemDescription(item_type,
initial_investment=None,
initial_investment_unit=None,
refurbishment=_refurbishment,
refurbishment_unit=_refurbishment_unit,
reposition=None,
reposition_unit=None,
lifetime=None)
else:
_reposition = float(item['reposition']['#text'])
_reposition_unit = item['reposition']['@cost_unit']
_investment = float(item['investment_cost']['#text'])
_investment_unit = item['investment_cost']['@cost_unit']
_lifetime = float(item['lifetime_equipment']['#text'])
_item_description = ItemDescription(item_type,
initial_investment=_investment,
initial_investment_unit=_investment_unit,
refurbishment=None,
refurbishment_unit=None,
reposition=_reposition,
reposition_unit=_reposition_unit,
lifetime=_lifetime)
return _item_description
def load_capital_costs(self, archetype):
archetype_capital_costs = archetype['capital_cost']
design_allowance = float(
archetype_capital_costs['Z_allowances_overhead_profit']['Z10_design_allowance']['#text']) / 100
overhead_and_profit = float(
archetype_capital_costs['Z_allowances_overhead_profit']['Z20_overhead_profit']['#text']) / 100
general_chapters = []
shell_items = []
service_items = []
for category in archetype_capital_costs:
if category == 'B_shell':
items = archetype_capital_costs[category]
for item in items:
components = items[item]
for component in components:
building_item = components[component]
shell_items.append(self.item_description(component, building_item))
general_chapters.append(Chapter(chapter_type=category, items=shell_items))
elif category == 'D_services':
services = archetype_capital_costs[category]
for service in services:
components = services[service]
if len(components.keys()) == 1:
for component in components:
service_item = components[component]
service_items.append(self.item_description(component, service_item))
else:
for component in components:
items = components[component]
if 'investment_cost' in items.keys():
service_item = components[component]
service_items.append(self.item_description(component, service_item))
else:
for item in items:
service_item = items[item]
service_items.append(self.item_description(item, service_item))
general_chapters.append(Chapter(chapter_type=category, items=service_items))
capital_costs = CapitalCost(general_chapters=general_chapters,
design_allowance=design_allowance,
overhead_and_profit=overhead_and_profit)
return capital_costs
@staticmethod
def _get_operational_costs(entry):
fuels = []
for item in entry['fuels']['fuel']:
fuel_type = item['@fuel_type']
fuel_variable = float(item['variable']['#text'])
fuel_variable_units = item['variable']['@cost_unit']
fuel_fixed_monthly = None
fuel_fixed_peak = None
density = None
density_unit = None
lower_heating_value = None
lower_heating_value_unit = None
if fuel_type == 'Electricity':
fuel_fixed_monthly = float(item['fixed_monthly']['#text'])
fuel_fixed_peak = float(item['fixed_power']['#text']) / 1000
else:
if item['fixed_monthly']:
fuel_fixed_monthly = float(item['fixed_monthly']['#text'])
if item['density']:
density = float(item['density']['#text'])
density_unit = item['density']['@density_unit']
if item['lower_heating_value']:
lower_heating_value = float(item['lower_heating_value']['#text'])
lower_heating_value_unit = item['lower_heating_value']['@lhv_unit']
fuel = Fuel(fuel_type,
fixed_monthly=fuel_fixed_monthly,
fixed_power=fuel_fixed_peak,
variable=fuel_variable,
variable_units=fuel_variable_units,
density=density,
density_unit=density_unit,
lower_heating_value=lower_heating_value,
lower_heating_value_unit=lower_heating_value_unit)
fuels.append(fuel)
heating_equipment_maintenance = float(entry['maintenance']['heating_equipment']['#text'])
cooling_equipment_maintenance = float(entry['maintenance']['cooling_equipment']['#text'])
photovoltaic_system_maintenance = float(entry['maintenance']['photovoltaic_system']['#text'])
co2_emissions = float(entry['co2_cost']['#text'])
_operational_cost = OperationalCost(fuels,
heating_equipment_maintenance,
cooling_equipment_maintenance,
photovoltaic_system_maintenance,
co2_emissions)
return _operational_cost
def names(self, category=None):
"""
Get the catalog elements names
:parm: for costs catalog category filter does nothing as there is only one category (archetypes)
"""
_names = {'archetypes': []}
for archetype in self._content.archetypes:
_names['archetypes'].append(archetype.name)
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: for costs catalog category filter does nothing as there is only one category (archetypes)
"""
return self._content
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for entry in self._content.archetypes:
if entry.name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,181 @@
"""
Cost catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import xmltodict
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.cost.archetype import Archetype
from hub.catalog_factories.data_models.cost.content import Content
from hub.catalog_factories.data_models.cost.capital_cost import CapitalCost
from hub.catalog_factories.data_models.cost.chapter import Chapter
from hub.catalog_factories.data_models.cost.item_description import ItemDescription
from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost
from hub.catalog_factories.data_models.cost.fuel import Fuel
from hub.catalog_factories.data_models.cost.income import Income
class MontrealCustomCatalog(Catalog):
"""
Montreal custom catalog class
"""
def __init__(self, path):
path = (path / 'montreal_costs.xml').resolve()
with open(path, 'r', encoding='utf-8') as xml:
self._archetypes = xmltodict.parse(xml.read(), force_list='archetype')
# store the full catalog data model in self._content
self._content = Content(self._load_archetypes())
@staticmethod
def _item_with_threesome(entry, item_type):
_reposition = float(entry[item_type]['reposition']['#text'])
_reposition_unit = entry[item_type]['reposition']['@cost_unit']
_investment = float(entry[item_type]['investment_cost']['#text'])
_investment_unit = entry[item_type]['investment_cost']['@cost_unit']
_lifetime = float(entry[item_type]['lifetime_equipment']['#text'])
_item_description = ItemDescription(item_type,
initial_investment=_investment,
initial_investment_unit=_investment_unit,
reposition=_reposition,
reposition_unit=_reposition_unit,
lifetime=_lifetime)
return _item_description
@staticmethod
def _item_with_refurbishment_values(entry, item_type):
_refurbishment = float(entry[item_type]['refurbishment_cost']['#text'])
_refurbishment_unit = entry[item_type]['refurbishment_cost']['@cost_unit']
_item_description = ItemDescription(item_type,
refurbishment=_refurbishment,
refurbishment_unit=_refurbishment_unit)
return _item_description
def _get_capital_costs(self, entry):
general_chapters = []
shell = entry['B_shell']
items_list = []
item_type = 'B10_superstructure'
item_description = self._item_with_refurbishment_values(shell, item_type)
items_list.append(item_description)
for item in shell['B20_envelope']:
item_type = item
item_description = self._item_with_refurbishment_values(shell['B20_envelope'], item_type)
items_list.append(item_description)
item_type = 'B3010_opaque_roof'
item_description = self._item_with_refurbishment_values(shell['B30_roofing'], item_type)
items_list.append(item_description)
general_chapters.append(Chapter('B_shell', items_list))
items_list = []
item_type = 'D301010_photovoltaic_system'
services = entry['D_services']
item_description = self._item_with_threesome(services['D30_hvac']['D3010_energy_supply'], item_type)
items_list.append(item_description)
item_type_list = ['D3020_heat_generating_systems', 'D3030_cooling_generation_systems', 'D3040_distribution_systems',
'D3080_other_hvac_ahu']
for item_type in item_type_list:
item_description = self._item_with_threesome(services['D30_hvac'], item_type)
items_list.append(item_description)
item_type = 'D5020_lighting_and_branch_wiring'
item_description = self._item_with_threesome(services['D50_electrical'], item_type)
items_list.append(item_description)
general_chapters.append(Chapter('D_services', items_list))
allowances = entry['Z_allowances_overhead_profit']
design_allowance = float(allowances['Z10_design_allowance']['#text']) / 100
overhead_and_profit = float(allowances['Z20_overhead_profit']['#text']) / 100
_capital_cost = CapitalCost(general_chapters, design_allowance, overhead_and_profit)
return _capital_cost
@staticmethod
def _get_operational_costs(entry):
fuels = []
for item in entry['fuels']['fuel']:
fuel_type = item['@fuel_type']
fuel_variable = float(item['variable']['#text'])
fuel_variable_units = item['variable']['@cost_unit']
fuel_fixed_monthly = None
fuel_fixed_peak = None
if fuel_type == 'electricity':
fuel_fixed_monthly = float(item['fixed_monthly']['#text'])
fuel_fixed_peak = float(item['fixed_power']['#text']) / 1000
elif fuel_type == 'gas':
fuel_fixed_monthly = float(item['fixed_monthly']['#text'])
fuel = Fuel(fuel_type,
fixed_monthly=fuel_fixed_monthly,
fixed_power=fuel_fixed_peak,
variable=fuel_variable,
variable_units=fuel_variable_units)
fuels.append(fuel)
heating_equipment_maintenance = float(entry['maintenance']['heating_equipment']['#text']) / 1000
cooling_equipment_maintenance = float(entry['maintenance']['cooling_equipment']['#text']) / 1000
photovoltaic_system_maintenance = float(entry['maintenance']['photovoltaic_system']['#text'])
co2_emissions = float(entry['co2_cost']['#text'])
_operational_cost = OperationalCost(fuels,
heating_equipment_maintenance,
cooling_equipment_maintenance,
photovoltaic_system_maintenance,
co2_emissions)
return _operational_cost
def _load_archetypes(self):
_catalog_archetypes = []
archetypes = self._archetypes['archetypes']['archetype']
for archetype in archetypes:
function = archetype['@function']
municipality = archetype['@municipality']
country = archetype['@country']
lod = float(archetype['@lod'])
currency = archetype['currency']
capital_cost = self._get_capital_costs(archetype['capital_cost'])
operational_cost = self._get_operational_costs(archetype['operational_cost'])
end_of_life_cost = float(archetype['end_of_life_cost']['#text'])
construction = float(archetype['incomes']['subsidies']['construction']['#text'])
hvac = float(archetype['incomes']['subsidies']['hvac']['#text'])
photovoltaic_system = float(archetype['incomes']['subsidies']['photovoltaic']['#text'])
electricity_exports = float(archetype['incomes']['electricity_export']['#text']) / 1000 / 3600
reduction_tax = float(archetype['incomes']['tax_reduction']['#text']) / 100
income = Income(construction_subsidy=construction,
hvac_subsidy=hvac,
photovoltaic_subsidy=photovoltaic_system,
electricity_export=electricity_exports,
reductions_tax=reduction_tax)
_catalog_archetypes.append(Archetype(lod,
function,
municipality,
country,
currency,
capital_cost,
operational_cost,
end_of_life_cost,
income))
return _catalog_archetypes
def names(self, category=None):
"""
Get the catalog elements names
:parm: for costs catalog category filter does nothing as there is only one category (archetypes)
"""
_names = {'archetypes': []}
for archetype in self._content.archetypes:
_names['archetypes'].append(archetype.name)
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: for costs catalog category filter does nothing as there is only one category (archetypes)
"""
return self._content
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for entry in self._content.archetypes:
if entry.name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,51 @@
"""
Cost catalog publish the life cycle cost
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Atiya atiya.atiya@mail.concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from pathlib import Path
from typing import TypeVar
from hub.catalog_factories.cost.montreal_custom_catalog import MontrealCustomCatalog
from hub.catalog_factories.cost.montreal_complete_cost_catalog import MontrealNewCatalog
Catalog = TypeVar('Catalog')
class CostsCatalogFactory:
"""
CostsCatalogFactory class
"""
def __init__(self, file_type, base_path=None):
if base_path is None:
base_path = Path(Path(__file__).parent.parent / 'data/costs')
self._catalog_type = '_' + file_type.lower()
self._path = base_path
@property
def _montreal_custom(self):
"""
Retrieve Montreal Custom catalog
"""
return MontrealCustomCatalog(self._path)
@property
def _montreal_new(self):
"""
Retrieve Montreal Custom catalog
"""
return MontrealNewCatalog(self._path)
@property
def catalog(self) -> Catalog:
"""
Return a cost catalog
:return: CostCatalog
"""
return getattr(self, self._catalog_type, lambda: None)
@property
def catalog_debug(self):
return MontrealNewCatalog(self._path)

View File

@ -0,0 +1,155 @@
"""
Construction catalog Archetype
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from hub.catalog_factories.data_models.construction.construction import Construction
class Archetype:
"""
Archetype class
"""
def __init__(self, archetype_id,
name,
function,
climate_zone,
construction_period,
constructions,
average_storey_height,
thermal_capacity,
extra_loses_due_to_thermal_bridges,
indirect_heated_ratio,
infiltration_rate_for_ventilation_system_off,
infiltration_rate_for_ventilation_system_on):
self._id = archetype_id
self._name = name
self._function = function
self._climate_zone = climate_zone
self._construction_period = construction_period
self._constructions = constructions
self._average_storey_height = average_storey_height
self._thermal_capacity = thermal_capacity
self._extra_loses_due_to_thermal_bridges = extra_loses_due_to_thermal_bridges
self._indirect_heated_ratio = indirect_heated_ratio
self._infiltration_rate_for_ventilation_system_off = infiltration_rate_for_ventilation_system_off
self._infiltration_rate_for_ventilation_system_on = infiltration_rate_for_ventilation_system_on
@property
def id(self):
"""
Get archetype id
:return: str
"""
return self._id
@property
def name(self):
"""
Get archetype name
:return: str
"""
return self._name
@property
def function(self):
"""
Get archetype function
:return: str
"""
return self._function
@property
def climate_zone(self):
"""
Get archetype climate zone
:return: str
"""
return self._climate_zone
@property
def constructions(self) -> [Construction]:
"""
Get archetype constructions
:return: [Construction]
"""
return self._constructions
@property
def construction_period(self):
"""
Get archetype construction period
:return: str
"""
return self._construction_period
@property
def average_storey_height(self):
"""
Get archetype average storey height in m
:return: float
"""
return self._average_storey_height
@property
def thermal_capacity(self):
"""
Get archetype thermal capacity in J/m3K
:return: float
"""
return self._thermal_capacity
@property
def extra_loses_due_to_thermal_bridges(self):
"""
Get archetype extra loses due to thermal bridges in W/m2K
:return: float
"""
return self._extra_loses_due_to_thermal_bridges
@property
def indirect_heated_ratio(self):
"""
Get archetype indirect heated area ratio
:return: float
"""
return self._indirect_heated_ratio
@property
def infiltration_rate_for_ventilation_system_off(self):
"""
Get archetype infiltration rate for ventilation system off in 1/s
:return: float
"""
return self._infiltration_rate_for_ventilation_system_off
@property
def infiltration_rate_for_ventilation_system_on(self):
"""
Get archetype infiltration rate for ventilation system on in 1/s
:return: float
"""
return self._infiltration_rate_for_ventilation_system_on
def to_dictionary(self):
"""Class content to dictionary"""
_constructions = []
for _construction in self.constructions:
_constructions.append(_construction.to_dictionary())
content = {'Archetype': {'id': self.id,
'name': self.name,
'function': self.function,
'climate zone': self.climate_zone,
'period of construction': self.construction_period,
'average storey height [m]': self.average_storey_height,
'thermal capacity [J/m3K]': self.thermal_capacity,
'extra loses due to thermal bridges [W/m2K]': self.extra_loses_due_to_thermal_bridges,
'indirect heated ratio': self.indirect_heated_ratio,
'infiltration rate for ventilation off [1/s]': self.infiltration_rate_for_ventilation_system_off,
'infiltration rate for ventilation on [1/s]': self.infiltration_rate_for_ventilation_system_on,
'constructions': _constructions
}
}
return content

View File

@ -0,0 +1,88 @@
"""
Construction catalog construction
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from hub.catalog_factories.data_models.construction.layer import Layer
from hub.catalog_factories.data_models.construction.window import Window
class Construction:
"""
Construction class
"""
def __init__(self, construction_id, construction_type, name, layers, window_ratio=None, window=None):
self._id = construction_id
self._type = construction_type
self._name = name
self._layers = layers
self._window_ratio = window_ratio
self._window = window
@property
def id(self):
"""
Get construction id
:return: str
"""
return self._id
@property
def type(self):
"""
Get construction type
:return: str
"""
return self._type
@property
def name(self):
"""
Get construction name
:return: str
"""
return self._name
@property
def layers(self) -> [Layer]:
"""
Get construction layers
:return: [layer]
"""
return self._layers
@property
def window_ratio(self):
"""
Get construction window ratio
:return: dict
"""
return self._window_ratio
@property
def window(self) -> Window:
"""
Get construction window
:return: Window
"""
return self._window
def to_dictionary(self):
"""Class content to dictionary"""
_layers = []
for _layer in self.layers:
_layers.append(_layer.to_dictionary())
_window = None
if self.window is not None:
_window = self.window.to_dictionary()
content = {'Construction': {'id': self.id,
'name': self.name,
'type': self.type,
'window ratio': self.window_ratio,
'window': _window,
'layers': _layers
}
}
return content

View File

@ -0,0 +1,63 @@
"""
Construction catalog content
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
class Content:
"""
Content class
"""
def __init__(self, archetypes, constructions, materials, windows):
self._archetypes = archetypes
self._constructions = constructions
self._materials = materials
self._windows = windows
@property
def archetypes(self):
"""
All archetypes in the catalog
"""
return self._archetypes
@property
def constructions(self):
"""
All constructions in the catalog
"""
return self._constructions
@property
def materials(self):
"""
All materials in the catalog
"""
return self._materials
@property
def windows(self):
"""
All windows in the catalog
"""
return self._windows
def to_dictionary(self):
"""Class content to dictionary"""
_archetypes = []
for _archetype in self.archetypes:
_archetypes.append(_archetype.to_dictionary())
content = {'Archetypes': _archetypes}
return content
def __str__(self):
"""Print content"""
_archetypes = []
for _archetype in self.archetypes:
_archetypes.append(_archetype.to_dictionary())
content = {'Archetypes': _archetypes}
return str(content)

View File

@ -0,0 +1,61 @@
"""
Construction catalog layer
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from hub.catalog_factories.data_models.construction.material import Material
class Layer:
"""
Layer class
"""
def __init__(self, layer_id, name, material, thickness):
self._id = layer_id
self._name = name
self._material = material
self._thickness = thickness
@property
def id(self):
"""
Get layer id
:return: str
"""
return self._id
@property
def name(self):
"""
Get layer name
:return: str
"""
return self._name
@property
def material(self) -> Material:
"""
Get layer material
:return: Material
"""
return self._material
@property
def thickness(self):
"""
Get layer thickness in meters
:return: None or float
"""
return self._thickness
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Layer': {'id': self.id,
'name': self.name,
'thickness [m]': self.thickness,
'material': self.material.to_dictionary()
}
}
return content

View File

@ -0,0 +1,128 @@
"""
Construction catalog material
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
class Material:
"""
Material class
"""
def __init__(self, material_id,
name,
solar_absorptance,
thermal_absorptance,
visible_absorptance,
no_mass=False,
thermal_resistance=None,
conductivity=None,
density=None,
specific_heat=None):
self._id = material_id
self._name = name
self._solar_absorptance = solar_absorptance
self._thermal_absorptance = thermal_absorptance
self._visible_absorptance = visible_absorptance
self._no_mass = no_mass
self._thermal_resistance = thermal_resistance
self._conductivity = conductivity
self._density = density
self._specific_heat = specific_heat
@property
def id(self):
"""
Get material id
:return: str
"""
return self._id
@property
def name(self):
"""
Get material name
:return: str
"""
return self._name
@property
def conductivity(self):
"""
Get material conductivity in W/mK
:return: None or float
"""
return self._conductivity
@property
def specific_heat(self):
"""
Get material conductivity in J/kgK
:return: None or float
"""
return self._specific_heat
@property
def density(self):
"""
Get material density in kg/m3
:return: None or float
"""
return self._density
@property
def solar_absorptance(self):
"""
Get material solar absorptance
:return: None or float
"""
return self._solar_absorptance
@property
def thermal_absorptance(self):
"""
Get material thermal absorptance
:return: None or float
"""
return self._thermal_absorptance
@property
def visible_absorptance(self):
"""
Get material visible absorptance
:return: None or float
"""
return self._visible_absorptance
@property
def no_mass(self):
"""
Get material no mass flag
:return: None or Boolean
"""
return self._no_mass
@property
def thermal_resistance(self):
"""
Get material thermal resistance in m2K/W
:return: None or float
"""
return self._thermal_resistance
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Material': {'id': self.id,
'name': self.name,
'is no-mass': self.no_mass,
'density [kg/m3]': self.density,
'specific heat [J/kgK]': self.specific_heat,
'conductivity [W/mK]': self.conductivity,
'thermal resistance [m2K/W]': self.thermal_resistance,
'solar absorptance': self.solar_absorptance,
'thermal absorptance': self.thermal_absorptance,
'visible absorptance': self.visible_absorptance
}
}
return content

View File

@ -0,0 +1,79 @@
"""
Construction catalog window
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
class Window:
"""
Window class
"""
def __init__(self, window_id, frame_ratio, g_value, overall_u_value, name, window_type=None):
self._id = window_id
self._frame_ratio = frame_ratio
self._g_value = g_value
self._overall_u_value = overall_u_value
self._name = name
self._type = window_type
@property
def id(self):
"""
Get window id
:return: str
"""
return self._id
@property
def name(self):
"""
Get window name
:return: str
"""
return self._name
@property
def frame_ratio(self):
"""
Get window frame ratio
:return: float
"""
return self._frame_ratio
@property
def g_value(self):
"""
Get thermal opening g-value
:return: float
"""
return self._g_value
@property
def overall_u_value(self):
"""
Get thermal opening overall U-value in W/m2K
:return: float
"""
return self._overall_u_value
@property
def type(self):
"""
Get transparent surface type, 'window' or 'skylight'
:return: str
"""
return self._type
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Window': {'id': self.id,
'name': self.name,
'type': self.type,
'frame ratio': self.frame_ratio,
'g-value': self.g_value,
'overall U value [W/m2K]': self.overall_u_value
}
}
return content

View File

@ -0,0 +1,132 @@
"""
Archetype catalog Cost
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from hub.catalog_factories.data_models.cost.capital_cost import CapitalCost
from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost
from hub.catalog_factories.data_models.cost.income import Income
class Archetype:
"""
Archetype class
"""
def __init__(self,
lod,
function,
municipality,
country,
currency,
capital_cost,
operational_cost,
end_of_life_cost,
income):
self._lod = lod
self._function = function
self._municipality = municipality
self._country = country
self._currency = currency
self._capital_cost = capital_cost
self._operational_cost = operational_cost
self._end_of_life_cost = end_of_life_cost
self._income = income
@property
def name(self):
"""
Get name
:return: string
"""
return f'{self._country}_{self._municipality}_{self._function}_lod{self._lod}'
@property
def lod(self):
"""
Get level of detail of the catalog
:return: string
"""
return self._lod
@property
def function(self):
"""
Get function
:return: string
"""
return self._function
@property
def municipality(self):
"""
Get municipality
:return: string
"""
return self._municipality
@property
def country(self):
"""
Get country
:return: string
"""
return self._country
@property
def currency(self):
"""
Get currency
:return: string
"""
return self._currency
@property
def capital_cost(self) -> CapitalCost:
"""
Get capital cost
:return: CapitalCost
"""
return self._capital_cost
@property
def operational_cost(self) -> OperationalCost:
"""
Get operational cost
:return: OperationalCost
"""
return self._operational_cost
@property
def end_of_life_cost(self):
"""
Get end of life cost in given currency per m2
:return: float
"""
return self._end_of_life_cost
@property
def income(self) -> Income:
"""
Get income
:return: Income
"""
return self._income
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Archetype': {'name': self.name,
'level of detail': self.lod,
'municipality': self.municipality,
'country': self.country,
'currency': self.currency,
'function': self.function,
'capital cost': self.capital_cost.to_dictionary(),
'operational cost': self.operational_cost.to_dictionary(),
'end of life cost [currency/m2]': self.end_of_life_cost,
'income': self.income.to_dictionary()
}
}
return content

View File

@ -0,0 +1,66 @@
"""
Capital costs included in the catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import List
from hub.catalog_factories.data_models.cost.chapter import Chapter
class CapitalCost:
"""
Capital cost class
"""
def __init__(self, general_chapters, design_allowance, overhead_and_profit):
self._general_chapters = general_chapters
self._design_allowance = design_allowance
self._overhead_and_profit = overhead_and_profit
@property
def general_chapters(self) -> List[Chapter]:
"""
Get general chapters in capital costs
:return: [Chapter]
"""
return self._general_chapters
@property
def design_allowance(self):
"""
Get design allowance in percentage (-)
:return: float
"""
return self._design_allowance
@property
def overhead_and_profit(self):
"""
Get overhead profit in percentage (-)
:return: float
"""
return self._overhead_and_profit
def chapter(self, name) -> Chapter:
"""
Get specific chapter by name
:return: Chapter
"""
for chapter in self.general_chapters:
if chapter.chapter_type == name:
return chapter
raise KeyError(f'Chapter name {name} not found')
def to_dictionary(self):
"""Class content to dictionary"""
_chapters = []
for _chapter in self.general_chapters:
_chapters.append(_chapter.to_dictionary())
content = {'Capital cost': {'design allowance': self.design_allowance,
'overhead and profit': self.overhead_and_profit,
'chapters': _chapters
}
}
return content

View File

@ -0,0 +1,57 @@
"""
Cost chapter description
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import List
from hub.catalog_factories.data_models.cost.item_description import ItemDescription
class Chapter:
"""
Chapter class
"""
def __init__(self, chapter_type, items):
self._chapter_type = chapter_type
self._items = items
@property
def chapter_type(self):
"""
Get chapter type
:return: str
"""
return self._chapter_type
@property
def items(self) -> List[ItemDescription]:
"""
Get list of items contained in the chapter
:return: [str]
"""
return self._items
def item(self, name) -> ItemDescription:
"""
Get specific item by name
:return: ItemDescription
"""
for item in self.items:
if item.type == name:
return item
raise KeyError(f'Item name {name} not found')
def to_dictionary(self):
"""Class content to dictionary"""
_items = []
for _item in self.items:
_items.append(_item.to_dictionary())
content = {'Chapter': {'chapter type': self.chapter_type,
'items': _items
}
}
return content

View File

@ -0,0 +1,40 @@
"""
Cost catalog content
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Atiya atiya.atiya@mail.concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
class Content:
"""
Content class
"""
def __init__(self, archetypes):
self._archetypes = archetypes
@property
def archetypes(self):
"""
All archetypes in the catalog
"""
return self._archetypes
def to_dictionary(self):
"""Class content to dictionary"""
_archetypes = []
for _archetype in self.archetypes:
_archetypes.append(_archetype.to_dictionary())
content = {'Archetypes': _archetypes}
return content
def __str__(self):
"""Print content"""
_archetypes = []
for _archetype in self.archetypes:
_archetypes.append(_archetype.to_dictionary())
content = {'Archetypes': _archetypes}
return str(content)

View File

@ -0,0 +1,99 @@
"""
Cost fuel
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import Union
class Fuel:
"""
Fuel class
"""
def __init__(self, fuel_type,
fixed_monthly=None,
fixed_power=None,
variable=None,
variable_units=None,
density=None,
density_unit=None,
lower_heating_value=None,
lower_heating_value_unit=None):
self._fuel_type = fuel_type
self._fixed_monthly = fixed_monthly
self._fixed_power = fixed_power
self._variable = variable
self._variable_units = variable_units
self._density = density
self._density_unit = density_unit
self._lower_heating_value = lower_heating_value
self._lower_heating_value_unit = lower_heating_value_unit
@property
def type(self):
"""
Get fuel type
:return: str
"""
return self._fuel_type
@property
def fixed_monthly(self) -> Union[None, float]:
"""
Get fixed operational costs in currency per month
:return: None or float
"""
return self._fixed_monthly
@property
def fixed_power(self) -> Union[None, float]:
"""
Get fixed operational costs depending on the peak power consumed in currency per month per W
:return: None or float
"""
if self._fixed_power is not None:
return self._fixed_power/1000
return None
@property
def variable(self) -> Union[tuple[None, None], tuple[float, str]]:
"""
Get variable costs in given units
:return: None, None or float, str
"""
return self._variable, self._variable_units
@property
def density(self) -> Union[tuple[None, None], tuple[float, str]]:
"""
Get fuel density in given units
:return: None, None or float, str
"""
return self._density, self._density_unit
@property
def lower_heating_value(self) -> Union[tuple[None, None], tuple[float, str]]:
"""
Get lower heating value in given units
:return: None, None or float, str
"""
return self._lower_heating_value, self._lower_heating_value_unit
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Fuel': {'fuel type': self.type,
'fixed operational costs [currency/month]': self.fixed_monthly,
'fixed operational costs depending on the peak power consumed [currency/month W]': self.fixed_power,
'variable operational costs': self.variable[0],
'units': self.variable[1],
'density': self.density[0],
'density unit': self.density[1],
'lower heating value': self.lower_heating_value[0],
'lower heating value unit': self.lower_heating_value[1]
}
}
return content

View File

@ -0,0 +1,77 @@
"""
Incomes included in the costs catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import Union
class Income:
"""
Income class
"""
def __init__(self, construction_subsidy=None,
hvac_subsidy=None,
photovoltaic_subsidy=None,
electricity_export=None,
reductions_tax=None):
self._construction_subsidy = construction_subsidy
self._hvac_subsidy = hvac_subsidy
self._photovoltaic_subsidy = photovoltaic_subsidy
self._electricity_export = electricity_export
self._reductions_tax = reductions_tax
@property
def construction_subsidy(self) -> Union[None, float]:
"""
Get subsidy for construction in percentage %
:return: None or float
"""
return self._construction_subsidy
@property
def hvac_subsidy(self) -> Union[None, float]:
"""
Get subsidy for HVAC system in percentage %
:return: None or float
"""
return self._hvac_subsidy
@property
def photovoltaic_subsidy(self) -> Union[None, float]:
"""
Get subsidy PV systems in percentage
:return: None or float
"""
return self._photovoltaic_subsidy
@property
def electricity_export(self) -> Union[None, float]:
"""
Get electricity export incomes in currency per J
:return: None or float
"""
return self._electricity_export
@property
def reductions_tax(self) -> Union[None, float]:
"""
Get reduction in taxes in percentage (-)
:return: None or float
"""
return self._reductions_tax
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Income': {'construction subsidy [%]': self.construction_subsidy,
'hvac subsidy [%]': self.hvac_subsidy,
'photovoltaic subsidy [%]': self.photovoltaic_subsidy,
'electricity export [currency/J]': self.electricity_export,
'reductions tax': self.reductions_tax
}
}
return content

View File

@ -0,0 +1,86 @@
"""
Cost item properties
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import Union
class ItemDescription:
"""
Item description class
"""
def __init__(self, item_type,
initial_investment=None,
initial_investment_unit=None,
refurbishment=None,
refurbishment_unit=None,
reposition=None,
reposition_unit=None,
lifetime=None):
self._item_type = item_type
self._initial_investment = initial_investment
self._initial_investment_unit = initial_investment_unit
self._refurbishment = refurbishment
self._refurbishment_unit = refurbishment_unit
self._reposition = reposition
self._reposition_unit = reposition_unit
self._lifetime = lifetime
@property
def type(self):
"""
Get item type
:return: str
"""
return self._item_type
@property
def initial_investment(self) -> Union[tuple[None, None], tuple[float, str]]:
"""
Get initial investment of the specific item in given units
:return: None, None or float, str
"""
return self._initial_investment, self._initial_investment_unit
@property
def refurbishment(self) -> Union[tuple[None, None], tuple[float, str]]:
"""
Get refurbishment costs of the specific item in given units
:return: None, None or float, str
"""
return self._refurbishment, self._refurbishment_unit
@property
def reposition(self) -> Union[tuple[None, None], tuple[float, str]]:
"""
Get reposition costs of the specific item in given units
:return: None, None or float, str
"""
return self._reposition, self._reposition_unit
@property
def lifetime(self) -> Union[None, float]:
"""
Get lifetime in years
:return: None or float
"""
return self._lifetime
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Item': {'type': self.type,
'initial investment': self.initial_investment[0],
'initial investment units': self.initial_investment[1],
'refurbishment': self.refurbishment[0],
'refurbishment units': self.refurbishment[1],
'reposition': self.reposition[0],
'reposition units': self.reposition[1],
'life time [years]': self.lifetime
}
}
return content

View File

@ -0,0 +1,77 @@
"""
Operational costs included in the catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import List
from hub.catalog_factories.data_models.cost.fuel import Fuel
class OperationalCost:
"""
Operational cost class
"""
def __init__(self, fuels, maintenance_heating, maintenance_cooling, maintenance_pv, co2):
self._fuels = fuels
self._maintenance_heating = maintenance_heating
self._maintenance_cooling = maintenance_cooling
self._maintenance_pv = maintenance_pv
self._co2 = co2
@property
def fuels(self) -> List[Fuel]:
"""
Get fuels listed in capital costs
:return: [Fuel]
"""
return self._fuels
@property
def maintenance_heating(self):
"""
Get cost of maintaining the heating system in currency/W
:return: float
"""
return self._maintenance_heating
@property
def maintenance_cooling(self):
"""
Get cost of maintaining the cooling system in currency/W
:return: float
"""
return self._maintenance_cooling
@property
def maintenance_pv(self):
"""
Get cost of maintaining the PV system in currency/m2
:return: float
"""
return self._maintenance_pv
@property
def co2(self):
"""
Get cost of CO2 emissions in currency/kgCO2
:return: float
"""
return self._co2
def to_dictionary(self):
"""Class content to dictionary"""
_fuels = []
for _fuel in self.fuels:
_fuels.append(_fuel.to_dictionary())
content = {'Maintenance': {'fuels': _fuels,
'cost of maintaining the heating system [currency/W]': self.maintenance_heating,
'cost of maintaining the cooling system [currency/W]': self.maintenance_cooling,
'cost of maintaining the PV system [currency/W]': self.maintenance_pv,
'cost of CO2 emissions [currency/kgCO2]': self.co2
}
}
return content

View File

@ -0,0 +1,50 @@
"""
Energy System catalog archetype, understood as a cluster of energy systems
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
from typing import List
from hub.catalog_factories.data_models.energy_systems.system import System
class Archetype:
"""
Archetype class
"""
def __init__(self, name, systems):
self._name = name
self._systems = systems
@property
def name(self):
"""
Get name
:return: string
"""
return self._name
@property
def systems(self) -> List[System]:
"""
Get list of equipments that compose the total energy system
:return: [Equipment]
"""
return self._systems
def to_dictionary(self):
"""Class content to dictionary"""
_systems = []
for _system in self.systems:
_systems.append(_system.to_dictionary())
content = {
'Archetype': {
'name': self.name,
'systems': _systems
}
}
return content

View File

@ -0,0 +1,63 @@
"""
Energy System catalog content
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
class Content:
"""
Content class
"""
def __init__(self, archetypes, systems, generations=None, distributions=None):
self._archetypes = archetypes
self._systems = systems
self._generations = generations
self._distributions = distributions
@property
def archetypes(self):
"""
All archetype system clusters in the catalog
"""
return self._archetypes
@property
def systems(self):
"""
All systems in the catalog
"""
return self._systems
@property
def generation_equipments(self):
"""
All generation equipments in the catalog
"""
return self._generations
@property
def distribution_equipments(self):
"""
All distribution equipments in the catalog
"""
return self._distributions
def to_dictionary(self):
"""Class content to dictionary"""
_archetypes = []
for _archetype in self.archetypes:
_archetypes.append(_archetype.to_dictionary())
content = {'Archetypes': _archetypes}
return content
def __str__(self):
"""Print content"""
_archetypes = []
for _archetype in self.archetypes:
_archetypes.append(_archetype.to_dictionary())
content = {'Archetypes': _archetypes}
return str(content)

View File

@ -0,0 +1,140 @@
"""
Energy System catalog distribution system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
from typing import Union, List, TypeVar
from hub.catalog_factories.data_models.energy_systems.energy_storage_system import EnergyStorageSystem
from hub.catalog_factories.data_models.energy_systems.emission_system import EmissionSystem
GenerationSystem = TypeVar('GenerationSystem')
class DistributionSystem:
"""
Distribution system class
"""
def __init__(self, system_id, model_name=None, system_type=None, supply_temperature=None,
distribution_consumption_fix_flow=None, distribution_consumption_variable_flow=None, heat_losses=None,
generation_systems=None, energy_storage_systems=None, emission_systems=None):
self._system_id = system_id
self._model_name = model_name
self._type = system_type
self._supply_temperature = supply_temperature
self._distribution_consumption_fix_flow = distribution_consumption_fix_flow
self._distribution_consumption_variable_flow = distribution_consumption_variable_flow
self._heat_losses = heat_losses
self._generation_systems = generation_systems
self._energy_storage_systems = energy_storage_systems
self._emission_systems = emission_systems
@property
def id(self):
"""
Get system id
:return: float
"""
return self._system_id
@property
def model_name(self):
"""
Get model name
:return: string
"""
return self._model_name
@property
def type(self):
"""
Get type from [air, water, refrigerant]
:return: string
"""
return self._type
@property
def supply_temperature(self):
"""
Get supply_temperature in degree Celsius
:return: float
"""
return self._supply_temperature
@property
def distribution_consumption_fix_flow(self):
"""
Get distribution_consumption if the pump or fan work at fix mass or volume flow in ratio over peak power (W/W)
:return: float
"""
return self._distribution_consumption_fix_flow
@property
def distribution_consumption_variable_flow(self):
"""
Get distribution_consumption if the pump or fan work at variable mass or volume flow in ratio
over energy produced (J/J)
:return: float
"""
return self._distribution_consumption_variable_flow
@property
def heat_losses(self):
"""
Get heat_losses in ratio over energy produced in J/J
:return: float
"""
return self._heat_losses
@property
def generation_systems(self) -> Union[None, List[GenerationSystem]]:
"""
Get generation systems connected to the distribution system
:return: [GenerationSystem]
"""
return self._generation_systems
@property
def energy_storage_systems(self) -> Union[None, List[EnergyStorageSystem]]:
"""
Get energy storage systems connected to this distribution system
:return: [EnergyStorageSystem]
"""
return self._energy_storage_systems
@property
def emission_systems(self) -> Union[None, List[EmissionSystem]]:
"""
Get energy emission systems connected to this distribution system
:return: [EmissionSystem]
"""
return self._emission_systems
def to_dictionary(self):
"""Class content to dictionary"""
_generation_systems = [_generation_system.to_dictionary() for _generation_system in
self.generation_systems] if self.generation_systems is not None else None
_energy_storage_systems = [_energy_storage_system.to_dictionary() for _energy_storage_system in
self.energy_storage_systems] if self.energy_storage_systems is not None else None
_emission_systems = [_emission_system.to_dictionary() for _emission_system in
self.emission_systems] if self.emission_systems is not None else None
content = {
'Layer': {
'id': self.id,
'model name': self.model_name,
'type': self.type,
'supply temperature [Celsius]': self.supply_temperature,
'distribution consumption if fix flow over peak power [W/W]': self.distribution_consumption_fix_flow,
'distribution consumption if variable flow over peak power [J/J]': self.distribution_consumption_variable_flow,
'heat losses per energy produced [J/J]': self.heat_losses,
'generation systems connected': _generation_systems,
'energy storage systems connected': _energy_storage_systems,
'emission systems connected': _emission_systems
}
}
return content

View File

@ -0,0 +1,103 @@
"""
Energy System catalog electrical storage system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
from hub.catalog_factories.data_models.energy_systems.energy_storage_system import EnergyStorageSystem
class ElectricalStorageSystem(EnergyStorageSystem):
""""
Energy Storage System Class
"""
def __init__(self, storage_id, type_energy_stored=None, model_name=None, manufacturer=None, storage_type=None,
nominal_capacity=None, losses_ratio=None, rated_output_power=None, nominal_efficiency=None,
battery_voltage=None, depth_of_discharge=None, self_discharge_rate=None):
super().__init__(storage_id, model_name, manufacturer, nominal_capacity, losses_ratio)
self._type_energy_stored = type_energy_stored
self._storage_type = storage_type
self._rated_output_power = rated_output_power
self._nominal_efficiency = nominal_efficiency
self._battery_voltage = battery_voltage
self._depth_of_discharge = depth_of_discharge
self._self_discharge_rate = self_discharge_rate
@property
def type_energy_stored(self):
"""
Get type of energy stored from ['electrical', 'thermal']
:return: string
"""
return self._type_energy_stored
@property
def storage_type(self):
"""
Get storage type from ['lithium_ion', 'lead_acid', 'NiCd']
:return: string
"""
return self._storage_type
@property
def rated_output_power(self):
"""
Get the rated output power of storage system in Watts
:return: float
"""
return self._rated_output_power
@property
def nominal_efficiency(self):
"""
Get the nominal efficiency of the storage system
:return: float
"""
return self._nominal_efficiency
@property
def battery_voltage(self):
"""
Get the battery voltage in Volts
:return: float
"""
return self._battery_voltage
@property
def depth_of_discharge(self):
"""
Get the depth of discharge as a percentage
:return: float
"""
return self._depth_of_discharge
@property
def self_discharge_rate(self):
"""
Get the self discharge rate of battery as a percentage
:return: float
"""
return self._self_discharge_rate
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Storage component': {
'storage id': self.id,
'type of energy stored': self.type_energy_stored,
'model name': self.model_name,
'manufacturer': self.manufacturer,
'storage type': self.storage_type,
'nominal capacity [J]': self.nominal_capacity,
'losses-ratio [J/J]': self.losses_ratio,
'rated power [W]': self.rated_output_power,
'nominal efficiency': self.nominal_efficiency,
'battery voltage [V]': self.battery_voltage,
'depth of discharge [%]': self.depth_of_discharge,
'self discharge rate': self.self_discharge_rate
}
}
return content

View File

@ -0,0 +1,60 @@
"""
Energy System catalog emission system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
class EmissionSystem:
"""
Emission system class
"""
def __init__(self, system_id, model_name=None, system_type=None, parasitic_energy_consumption=None):
self._system_id = system_id
self._model_name = model_name
self._type = system_type
self._parasitic_energy_consumption = parasitic_energy_consumption
@property
def id(self):
"""
Get system id
:return: float
"""
return self._system_id
@property
def model_name(self):
"""
Get model name
:return: string
"""
return self._model_name
@property
def type(self):
"""
Get type
:return: string
"""
return self._type
@property
def parasitic_energy_consumption(self):
"""
Get parasitic_energy_consumption in ratio (J/J)
:return: float
"""
return self._parasitic_energy_consumption
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Layer': {'id': self.id,
'model name': self.model_name,
'type': self.type,
'parasitic energy consumption per energy produced [J/J]': self.parasitic_energy_consumption
}
}
return content

View File

@ -0,0 +1,75 @@
"""
Energy System catalog heat generation system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from abc import ABC
class EnergyStorageSystem(ABC):
""""
Energy Storage System Abstract Class
"""
def __init__(self, storage_id, model_name=None, manufacturer=None,
nominal_capacity=None, losses_ratio=None):
self._storage_id = storage_id
self._model_name = model_name
self._manufacturer = manufacturer
self._nominal_capacity = nominal_capacity
self._losses_ratio = losses_ratio
@property
def id(self):
"""
Get storage id
:return: string
"""
return self._storage_id
@property
def type_energy_stored(self):
"""
Get type of energy stored from ['electrical', 'thermal']
:return: string
"""
raise NotImplementedError
@property
def model_name(self):
"""
Get system model
:return: string
"""
return self._model_name
@property
def manufacturer(self):
"""
Get name of manufacturer
:return: string
"""
return self._manufacturer
@property
def nominal_capacity(self):
"""
Get the nominal capacity of the storage system in Jules
:return: float
"""
return self._nominal_capacity
@property
def losses_ratio(self):
"""
Get the losses-ratio of storage system in Jules lost / Jules stored
:return: float
"""
return self._losses_ratio
def to_dictionary(self):
"""Class content to dictionary"""
raise NotImplementedError

View File

@ -0,0 +1,99 @@
"""
Energy System catalog heat generation system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
from __future__ import annotations
from abc import ABC
from typing import List, Union
from hub.catalog_factories.data_models.energy_systems.energy_storage_system import EnergyStorageSystem
from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem
class GenerationSystem(ABC):
"""
Heat Generation system class
"""
def __init__(self, system_id, name, model_name=None, manufacturer=None, fuel_type=None,
distribution_systems=None, energy_storage_systems=None, number_of_units=None):
self._system_id = system_id
self._name = name
self._model_name = model_name
self._manufacturer = manufacturer
self._fuel_type = fuel_type
self._distribution_systems = distribution_systems
self._energy_storage_systems = energy_storage_systems
self._number_of_units = number_of_units
@property
def id(self):
"""
Get system id
:return: float
"""
return self._system_id
@property
def name(self):
"""
Get system name
:return: string
"""
return self._name
@property
def system_type(self):
"""
Get type
:return: string
"""
raise NotImplementedError
@property
def model_name(self):
"""
Get system id
:return: float
"""
return self._model_name
@property
def manufacturer(self):
"""
Get name
:return: string
"""
return self._manufacturer
@property
def fuel_type(self):
"""
Get fuel_type from [renewable, gas, diesel, electricity, wood, coal, biogas]
:return: string
"""
return self._fuel_type
@property
def distribution_systems(self) -> Union[None, List[DistributionSystem]]:
"""
Get distributions systems connected to this generation system
:return: [DistributionSystem]
"""
return self._distribution_systems
@property
def energy_storage_systems(self) -> Union[None, List[EnergyStorageSystem]]:
"""
Get energy storage systems connected to this generation system
:return: [EnergyStorageSystem]
"""
return self._energy_storage_systems
def to_dictionary(self):
"""Class content to dictionary"""
raise NotImplementedError

View File

@ -0,0 +1,344 @@
"""
Energy System catalog non PV generation system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
from typing import Union
from hub.catalog_factories.data_models.energy_systems.performance_curves import PerformanceCurves
from hub.catalog_factories.data_models.energy_systems.generation_system import GenerationSystem
class NonPvGenerationSystem(GenerationSystem):
"""
Non PV Generation system class
"""
def __init__(self, system_id, name, system_type, model_name=None, manufacturer=None, fuel_type=None,
nominal_heat_output=None, maximum_heat_output=None, minimum_heat_output=None, source_medium=None,
supply_medium=None, heat_efficiency=None, nominal_cooling_output=None, maximum_cooling_output=None,
minimum_cooling_output=None, cooling_efficiency=None, electricity_efficiency=None,
source_temperature=None, source_mass_flow=None, nominal_electricity_output=None,
maximum_heat_supply_temperature=None, minimum_heat_supply_temperature=None,
maximum_cooling_supply_temperature=None, minimum_cooling_supply_temperature=None, heat_output_curve=None,
heat_fuel_consumption_curve=None, heat_efficiency_curve=None, cooling_output_curve=None,
cooling_fuel_consumption_curve=None, cooling_efficiency_curve=None,
distribution_systems=None, energy_storage_systems=None, domestic_hot_water=False,
reversible=None, simultaneous_heat_cold=None):
super().__init__(system_id=system_id, name=name, model_name=model_name, manufacturer=manufacturer,
fuel_type=fuel_type, distribution_systems=distribution_systems,
energy_storage_systems=energy_storage_systems)
self._system_type = system_type
self._nominal_heat_output = nominal_heat_output
self._maximum_heat_output = maximum_heat_output
self._minimum_heat_output = minimum_heat_output
self._heat_efficiency = heat_efficiency
self._nominal_cooling_output = nominal_cooling_output
self._maximum_cooling_output = maximum_cooling_output
self._minimum_cooling_output = minimum_cooling_output
self._cooling_efficiency = cooling_efficiency
self._electricity_efficiency = electricity_efficiency
self._nominal_electricity_output = nominal_electricity_output
self._source_medium = source_medium
self._source_temperature = source_temperature
self._source_mass_flow = source_mass_flow
self._supply_medium = supply_medium
self._maximum_heat_supply_temperature = maximum_heat_supply_temperature
self._minimum_heat_supply_temperature = minimum_heat_supply_temperature
self._maximum_cooling_supply_temperature = maximum_cooling_supply_temperature
self._minimum_cooling_supply_temperature = minimum_cooling_supply_temperature
self._heat_output_curve = heat_output_curve
self._heat_fuel_consumption_curve = heat_fuel_consumption_curve
self._heat_efficiency_curve = heat_efficiency_curve
self._cooling_output_curve = cooling_output_curve
self._cooling_fuel_consumption_curve = cooling_fuel_consumption_curve
self._cooling_efficiency_curve = cooling_efficiency_curve
self._domestic_hot_water = domestic_hot_water
self._reversible = reversible
self._simultaneous_heat_cold = simultaneous_heat_cold
@property
def system_type(self):
"""
Get type
:return: string
"""
return self._system_type
@property
def nominal_heat_output(self):
"""
Get nominal heat output of heat generation devices in W
:return: float
"""
return self._nominal_heat_output
@property
def maximum_heat_output(self):
"""
Get maximum heat output of heat generation devices in W
:return: float
"""
return self._maximum_heat_output
@property
def minimum_heat_output(self):
"""
Get minimum heat output of heat generation devices in W
:return: float
"""
return self._minimum_heat_output
@property
def source_medium(self):
"""
Get source_type from [air, water, ground, district_heating, grid, on_site_electricity]
:return: string
"""
return self._source_medium
@property
def supply_medium(self):
"""
Get the supply medium from ['air', 'water']
:return: string
"""
return self._supply_medium
@property
def heat_efficiency(self):
"""
Get heat_efficiency
:return: float
"""
return self._heat_efficiency
@property
def nominal_cooling_output(self):
"""
Get nominal cooling output of heat generation devices in W
:return: float
"""
return self._nominal_cooling_output
@property
def maximum_cooling_output(self):
"""
Get maximum heat output of heat generation devices in W
:return: float
"""
return self._maximum_cooling_output
@property
def minimum_cooling_output(self):
"""
Get minimum heat output of heat generation devices in W
:return: float
"""
return self._minimum_cooling_output
@property
def cooling_efficiency(self):
"""
Get cooling_efficiency
:return: float
"""
return self._cooling_efficiency
@property
def electricity_efficiency(self):
"""
Get electricity_efficiency
:return: float
"""
return self._electricity_efficiency
@property
def source_temperature(self):
"""
Get source_temperature in degree Celsius
:return: float
"""
return self._source_temperature
@property
def source_mass_flow(self):
"""
Get source_mass_flow in kg/s
:return: float
"""
return self._source_mass_flow
@property
def nominal_electricity_output(self):
"""
Get nominal_power_output of electricity generation devices or inverters in W
:return: float
"""
return self._nominal_electricity_output
@property
def maximum_heat_supply_temperature(self):
"""
Get the maximum heat supply temperature in degree Celsius
:return: float
"""
return self._minimum_heat_supply_temperature
@property
def minimum_heat_supply_temperature(self):
"""
Get the minimum heat supply temperature in degree Celsius
:return: float
"""
return self._minimum_heat_supply_temperature
@property
def maximum_cooling_supply_temperature(self):
"""
Get the maximum cooling supply temperature in degree Celsius
:return: float
"""
return self._maximum_cooling_supply_temperature
@property
def minimum_cooling_supply_temperature(self):
"""
Get the minimum cooling supply temperature in degree Celsius
:return: float
"""
return self._minimum_cooling_supply_temperature
@property
def heat_output_curve(self) -> Union[None, PerformanceCurves]:
"""
Get the heat output curve of the heat generation device
:return: PerformanceCurve
"""
return self._heat_output_curve
@property
def heat_fuel_consumption_curve(self) -> Union[None, PerformanceCurves]:
"""
Get the heating fuel consumption curve of the heat generation device
:return: PerformanceCurve
"""
return self._heat_fuel_consumption_curve
@property
def heat_efficiency_curve(self) -> Union[None, PerformanceCurves]:
"""
Get the heating efficiency curve of the heat generation device
:return: PerformanceCurve
"""
return self._heat_efficiency_curve
@property
def cooling_output_curve(self) -> Union[None, PerformanceCurves]:
"""
Get the heat output curve of the heat generation device
:return: PerformanceCurve
"""
return self._cooling_output_curve
@property
def cooling_fuel_consumption_curve(self) -> Union[None, PerformanceCurves]:
"""
Get the heating fuel consumption curve of the heat generation device
:return: PerformanceCurve
"""
return self._cooling_fuel_consumption_curve
@property
def cooling_efficiency_curve(self) -> Union[None, PerformanceCurves]:
"""
Get the heating efficiency curve of the heat generation device
:return: PerformanceCurve
"""
return self._cooling_efficiency_curve
@property
def domestic_hot_water(self):
"""
Get the ability to produce domestic hot water
:return: bool
"""
return self._domestic_hot_water
@property
def reversibility(self):
"""
Get the ability to produce heating and cooling
:return: bool
"""
return self._reversible
@property
def simultaneous_heat_cold(self):
"""
Get the ability to produce heating and cooling at the same time
:return: bool
"""
return self._simultaneous_heat_cold
def to_dictionary(self):
"""Class content to dictionary"""
_distribution_systems = [_distribution_system.to_dictionary() for _distribution_system in
self.distribution_systems] if self.distribution_systems is not None else None
_energy_storage_systems = [_energy_storage_system.to_dictionary() for _energy_storage_system in
self.energy_storage_systems] if self.energy_storage_systems is not None else None
_heat_output_curve = self.heat_output_curve.to_dictionary() if (
self.heat_output_curve is not None) else None
_heat_fuel_consumption_curve = self.heat_fuel_consumption_curve.to_dictionary() if (
self.heat_fuel_consumption_curve is not None) else None
_heat_efficiency_curve = self.heat_efficiency_curve.to_dictionary() if (
self.heat_efficiency_curve is not None) else None
_cooling_output_curve = self.cooling_output_curve.to_dictionary() if (
self.cooling_output_curve is not None) else None
_cooling_fuel_consumption_curve = self.cooling_fuel_consumption_curve.to_dictionary() if (
self.cooling_fuel_consumption_curve is not None) else None
_cooling_efficiency_curve = self.cooling_efficiency_curve.to_dictionary() if (
self.cooling_efficiency_curve is not None) else None
content = {
'Energy Generation component':
{
'id': self.id,
'model name': self.model_name,
'manufacturer': self.manufacturer,
'type': self.system_type,
'fuel type': self.fuel_type,
'nominal heat output [W]': self.nominal_heat_output,
'maximum heat output [W]': self.maximum_heat_output,
'minimum heat output [W]': self.minimum_heat_output,
'source medium': self.source_medium,
'supply medium': self.supply_medium,
'source temperature [Celsius]': self.source_temperature,
'source mass flow [kg/s]': self.source_mass_flow,
'heat efficiency': self.heat_efficiency,
'nominal cooling output [W]': self.nominal_cooling_output,
'maximum cooling output [W]': self.maximum_cooling_output,
'minimum cooling output [W]': self.minimum_cooling_output,
'cooling efficiency': self.cooling_efficiency,
'electricity efficiency': self.electricity_efficiency,
'nominal power output [W]': self.nominal_electricity_output,
'maximum heating supply temperature [Celsius]': self.maximum_heat_supply_temperature,
'minimum heating supply temperature [Celsius]': self.minimum_heat_supply_temperature,
'maximum cooling supply temperature [Celsius]': self.maximum_cooling_supply_temperature,
'minimum cooling supply temperature [Celsius]': self.minimum_cooling_supply_temperature,
'heat output curve': self.heat_output_curve,
'heat fuel consumption curve': self.heat_fuel_consumption_curve,
'heat efficiency curve': _heat_efficiency_curve,
'cooling output curve': self.cooling_output_curve,
'cooling fuel consumption curve': self.cooling_fuel_consumption_curve,
'cooling efficiency curve': self.cooling_efficiency_curve,
'distribution systems connected': _distribution_systems,
'storage systems connected': _energy_storage_systems,
'domestic hot water production capability': self.domestic_hot_water,
'reversible cycle': self.reversibility,
'simultaneous heat and cooling production': self.simultaneous_heat_cold
}
}
return content

View File

@ -0,0 +1,72 @@
"""
Energy System catalog heat generation system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from __future__ import annotations
class PerformanceCurves:
"""
Parameter function class
"""
def __init__(self, curve_type, dependant_variable, parameters, coefficients):
self._curve_type = curve_type
self._dependant_variable = dependant_variable
self._parameters = parameters
self._coefficients = coefficients
@property
def curve_type(self):
"""
The type of the fit function from the following
Linear =>>> y = a + b*x
Exponential =>>> y = a*(b**x)
Second degree polynomial =>>> y = a + b*x + c*(x**2)
Power =>>> y = a*(x**b)
Bi-Quadratic =>>> y = a + b*x + c*(x**2) + d*z + e*(z**2) + f*x*z
Get the type of function from ['linear', 'exponential', 'second degree polynomial', 'power', 'bi-quadratic']
:return: string
"""
return self._curve_type
@property
def dependant_variable(self):
"""
y (e.g. COP in COP = a*source temperature**2 + b*source temperature + c*source temperature*supply temperature +
d*supply temperature + e*supply temperature**2 + f)
"""
return self._dependant_variable
@property
def parameters(self):
"""
Get the list of parameters involved in fitting process as ['x', 'z'] (e.g. [source temperature, supply temperature]
in COP=)
:return: string
"""
return self._parameters
@property
def coefficients(self):
"""
Get the coefficients of the functions as list of ['a', 'b', 'c', 'd', 'e', 'f']
:return: [coefficients]
"""
return self._coefficients
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Parameter Function': {
'curve type': self.curve_type,
'dependant variable': self.dependant_variable,
'parameter(s)': self.parameters,
'coefficients': self.coefficients,
}
}
return content

View File

@ -0,0 +1,153 @@
"""
Energy System catalog heat generation system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from hub.catalog_factories.data_models.energy_systems.generation_system import GenerationSystem
class PvGenerationSystem(GenerationSystem):
"""
Electricity Generation system class
"""
def __init__(self, system_id, name, system_type, model_name=None, manufacturer=None, electricity_efficiency=None,
nominal_electricity_output=None, nominal_ambient_temperature=None, nominal_cell_temperature=None,
nominal_radiation=None, standard_test_condition_cell_temperature=None,
standard_test_condition_maximum_power=None, cell_temperature_coefficient=None, width=None, height=None,
distribution_systems=None, energy_storage_systems=None):
super().__init__(system_id=system_id, name=name, model_name=model_name,
manufacturer=manufacturer, fuel_type='renewable', distribution_systems=distribution_systems,
energy_storage_systems=energy_storage_systems)
self._system_type = system_type
self._electricity_efficiency = electricity_efficiency
self._nominal_electricity_output = nominal_electricity_output
self._nominal_ambient_temperature = nominal_ambient_temperature
self._nominal_cell_temperature = nominal_cell_temperature
self._nominal_radiation = nominal_radiation
self._standard_test_condition_cell_temperature = standard_test_condition_cell_temperature
self._standard_test_condition_maximum_power = standard_test_condition_maximum_power
self._cell_temperature_coefficient = cell_temperature_coefficient
self._width = width
self._height = height
@property
def system_type(self):
"""
Get type
:return: string
"""
return self._system_type
@property
def nominal_electricity_output(self):
"""
Get nominal_power_output of electricity generation devices or inverters in W
:return: float
"""
return self._nominal_electricity_output
@property
def electricity_efficiency(self):
"""
Get electricity_efficiency
:return: float
"""
return self._electricity_efficiency
@property
def nominal_ambient_temperature(self):
"""
Get nominal ambient temperature of PV panels in degree Celsius
:return: float
"""
return self._nominal_ambient_temperature
@property
def nominal_cell_temperature(self):
"""
Get nominal cell temperature of PV panels in degree Celsius
:return: float
"""
return self._nominal_cell_temperature
@property
def nominal_radiation(self):
"""
Get nominal radiation of PV panels
:return: float
"""
return self._nominal_radiation
@property
def standard_test_condition_cell_temperature(self):
"""
Get standard test condition cell temperature of PV panels in degree Celsius
:return: float
"""
return self._standard_test_condition_cell_temperature
@property
def standard_test_condition_maximum_power(self):
"""
Get standard test condition maximum power of PV panels in W
:return: float
"""
return self._standard_test_condition_maximum_power
@property
def cell_temperature_coefficient(self):
"""
Get cell temperature coefficient of PV module
:return: float
"""
return self._cell_temperature_coefficient
@property
def width(self):
"""
Get PV module width in m
:return: float
"""
return self._width
@property
def height(self):
"""
Get PV module height in m
:return: float
"""
return self._height
def to_dictionary(self):
"""Class content to dictionary"""
_distribution_systems = [_distribution_system.to_dictionary() for _distribution_system in
self.distribution_systems] if self.distribution_systems is not None else None
_energy_storage_systems = [_energy_storage_system.to_dictionary() for _energy_storage_system in
self.energy_storage_systems] if self.energy_storage_systems is not None else None
content = {
'Energy Generation component':
{
'id': self.id,
'model name': self.model_name,
'manufacturer': self.manufacturer,
'type': self.system_type,
'fuel type': self.fuel_type,
'electricity efficiency': self.electricity_efficiency,
'nominal power output [W]': self.nominal_electricity_output,
'nominal ambient temperature [Celsius]': self.nominal_ambient_temperature,
'nominal cell temperature [Celsius]': self.nominal_cell_temperature,
'nominal radiation [W/m2]': self.nominal_radiation,
'standard test condition cell temperature [Celsius]': self.standard_test_condition_cell_temperature,
'standard test condition maximum power [W]': self.standard_test_condition_maximum_power,
'cell temperature coefficient': self.cell_temperature_coefficient,
'width': self.width,
'height': self.height,
'distribution systems connected': _distribution_systems,
'storage systems connected': _energy_storage_systems
}
}
return content

View File

@ -0,0 +1,99 @@
"""
Energy Systems catalog System
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
from typing import Union, List
from pathlib import Path
from hub.catalog_factories.data_models.energy_systems.generation_system import GenerationSystem
from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem
class System:
"""
System class
"""
def __init__(self,
system_id,
demand_types,
name=None,
generation_systems=None,
distribution_systems=None,
configuration_schema=None):
self._system_id = system_id
self._name = name
self._demand_types = demand_types
self._generation_systems = generation_systems
self._distribution_systems = distribution_systems
self._configuration_schema = configuration_schema
@property
def id(self):
"""
Get equipment id
:return: string
"""
return self._system_id
@property
def name(self):
"""
Get the system name
:return: string
"""
return self._name
@property
def demand_types(self):
"""
Get demand able to cover from ['heating', 'cooling', 'domestic_hot_water', 'electricity']
:return: [string]
"""
return self._demand_types
@property
def generation_systems(self) -> Union[None, List[GenerationSystem]]:
"""
Get generation systems
:return: [GenerationSystem]
"""
return self._generation_systems
@property
def distribution_systems(self) -> Union[None, List[DistributionSystem]]:
"""
Get distribution systems
:return: [DistributionSystem]
"""
return self._distribution_systems
@property
def configuration_schema(self) -> Path:
"""
Get system configuration schema
:return: Path
"""
return self._configuration_schema
def to_dictionary(self):
"""Class content to dictionary"""
_generation_systems = []
for _generation in self.generation_systems:
_generation_systems.append(_generation.to_dictionary())
_distribution_systems = [_distribution.to_dictionary() for _distribution in
self.distribution_systems] if self.distribution_systems is not None else None
content = {'system': {'id': self.id,
'name': self.name,
'demand types': self.demand_types,
'generation system(s)': _generation_systems,
'distribution system(s)': _distribution_systems,
'configuration schema path': self.configuration_schema
}
}
return content

View File

@ -0,0 +1,126 @@
"""
Energy System catalog thermal storage system
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
Code contributors: Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
from hub.catalog_factories.data_models.energy_systems.energy_storage_system import EnergyStorageSystem
from hub.catalog_factories.data_models.construction.layer import Layer
from hub.catalog_factories.data_models.construction.material import Material
class ThermalStorageSystem(EnergyStorageSystem):
""""
Energy Storage System Class
"""
def __init__(self, storage_id, type_energy_stored=None, model_name=None, manufacturer=None, storage_type=None,
nominal_capacity=None, losses_ratio=None, volume=None, height=None, layers=None,
maximum_operating_temperature=None, storage_medium=None, heating_coil_capacity=None):
super().__init__(storage_id, model_name, manufacturer, nominal_capacity, losses_ratio)
self._type_energy_stored = type_energy_stored
self._storage_type = storage_type
self._volume = volume
self._height = height
self._layers = layers
self._maximum_operating_temperature = maximum_operating_temperature
self._storage_medium = storage_medium
self._heating_coil_capacity = heating_coil_capacity
@property
def type_energy_stored(self):
"""
Get type of energy stored from ['electrical', 'thermal']
:return: string
"""
return self._type_energy_stored
@property
def storage_type(self):
"""
Get storage type from ['thermal', 'sensible', 'latent']
:return: string
"""
return self._storage_type
@property
def volume(self):
"""
Get the physical volume of the storage system in cubic meters
:return: float
"""
return self._volume
@property
def height(self):
"""
Get the diameter of the storage system in meters
:return: float
"""
return self._height
@property
def layers(self) -> [Layer]:
"""
Get construction layers
:return: [layer]
"""
return self._layers
@property
def maximum_operating_temperature(self):
"""
Get maximum operating temperature of the storage system in degree Celsius
:return: float
"""
return self._maximum_operating_temperature
@property
def storage_medium(self) -> Material:
"""
Get thermodynamic characteristics of the storage medium
:return: [material
"""
return self._storage_medium
@property
def heating_coil_capacity(self):
"""
Get heating coil capacity in Watts
:return: [material
"""
return self._heating_coil_capacity
def to_dictionary(self):
"""Class content to dictionary"""
_layers = None
_medias = None
if self.layers is not None:
_layers = []
for _layer in self.layers:
_layers.append(_layer.to_dictionary())
if self.storage_medium is not None:
_medias = self.storage_medium.to_dictionary()
content = {
'Storage component':
{
'storage id': self.id,
'type of energy stored': self.type_energy_stored,
'model name': self.model_name,
'manufacturer': self.manufacturer,
'storage type': self.storage_type,
'nominal capacity [J]': self.nominal_capacity,
'losses-ratio [J/J]': self.losses_ratio,
'volume [m3]': self.volume,
'height [m]': self.height,
'layers': _layers,
'maximum operating temperature [Celsius]': self.maximum_operating_temperature,
'storage_medium': self.storage_medium.to_dictionary(),
'heating coil capacity [W]': self.heating_coil_capacity
}
}
return content

View File

@ -0,0 +1,55 @@
"""
Greenery catalog content
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
class Content:
"""
Content class
"""
def __init__(self, vegetations, plants, soils):
self._vegetations = vegetations
self._plants = plants
self._soils = soils
@property
def vegetations(self):
"""
All vegetation in the catalog
"""
return self._vegetations
@property
def plants(self):
"""
All plants in the catalog
"""
return self._plants
@property
def soils(self):
"""
All soils in the catalog
"""
return self._soils
def to_dictionary(self):
"""Class content to dictionary"""
_archetypes = []
for _archetype in self.vegetations:
_archetypes.append(_archetype.to_dictionary())
content = {'Archetypes': _archetypes}
return content
def __str__(self):
"""Print content"""
_archetypes = []
for _archetype in self.vegetations:
_archetypes.append(_archetype.to_dictionary())
content = {'Archetypes': _archetypes}
return str(content)

View File

@ -0,0 +1,117 @@
"""
Greenery catalog data model Plant class
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from hub.catalog_factories.data_models.greenery.soil import Soil as hub_soil
class Plant:
"""
Plant class
"""
def __init__(self, category, plant):
self._name = plant.name
self._category = category
self._height = plant.height
self._leaf_area_index = plant.leafAreaIndex
self._leaf_reflectivity = plant.leafReflectivity
self._leaf_emissivity = plant.leafEmissivity
self._minimal_stomatal_resistance = plant.minimalStomatalResistance
self._co2_sequestration = plant.co2Sequestration
self._grows_on = []
for soil in plant.growsOn:
self._grows_on.append(hub_soil(soil))
@property
def name(self):
"""
Get plant name
:return: string
"""
return self._name
@property
def category(self):
"""
Get plant category name
:return: string
"""
return self._category
@property
def height(self):
"""
Get plant height in m
:return: float
"""
return self._height
@property
def leaf_area_index(self):
"""
Get plant leaf area index
:return: float
"""
return self._leaf_area_index
@property
def leaf_reflectivity(self):
"""
Get plant leaf area index
:return: float
"""
return self._leaf_reflectivity
@property
def leaf_emissivity(self):
"""
Get plant leaf emissivity
:return: float
"""
return self._leaf_emissivity
@property
def minimal_stomatal_resistance(self):
"""
Get plant minimal stomatal resistance in s/m
:return: float
"""
return self._minimal_stomatal_resistance
@property
def co2_sequestration(self):
"""
Get plant co2 sequestration capacity in kg CO2 equivalent
:return: float
"""
return self._co2_sequestration
@property
def grows_on(self) -> [hub_soil]:
"""
Get plant compatible soils
:return: [Soil]
"""
return self._grows_on
def to_dictionary(self):
"""Class content to dictionary"""
_soils = []
for _soil in self.grows_on:
_soils.append(_soil.to_dictionary())
content = {'Plant': {'name': self.name,
'category': self.category,
'height [m]': self.height,
'leaf area index': self.leaf_area_index,
'leaf reflectivity': self.leaf_reflectivity,
'leaf emissivity': self.leaf_emissivity,
'minimal stomatal resistance [s/m]': self.minimal_stomatal_resistance,
'co2 sequestration [kg????]': self.co2_sequestration,
'soils where it grows on': _soils
}
}
return content

View File

@ -0,0 +1,46 @@
"""
Greenery catalog data model Plant percentage class
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from hub.catalog_factories.data_models.greenery.plant import Plant as HubPlant
class PlantPercentage(HubPlant):
"""
Plant percentage class
"""
def __init__(self, percentage, plant_category, plant):
super().__init__(plant_category, plant)
self._percentage = percentage
@property
def percentage(self):
"""
Get plant percentage
:return: float
"""
return self._percentage
def to_dictionary(self):
"""Class content to dictionary"""
_soils = []
for _soil in self.grows_on:
_soils.append(_soil.to_dictionary())
content = {'Plant': {'name': self.name,
'percentage': self.percentage,
'category': self.category,
'height [m]': self.height,
'leaf area index': self.leaf_area_index,
'leaf reflectivity': self.leaf_reflectivity,
'leaf emissivity': self.leaf_emissivity,
'minimal stomatal resistance [s/m]': self.minimal_stomatal_resistance,
'co2 sequestration [kg????]': self.co2_sequestration,
'soils where it grows on': _soils
}
}
return content

View File

@ -0,0 +1,129 @@
"""
Greenery catalog data model Soil class
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
class Soil:
"""
Soil class
"""
def __init__(self, soil):
self._name = soil.name
self._roughness = soil.roughness
self._dry_conductivity = soil.conductivityOfDrySoil
self._dry_density = soil.densityOfDrySoil
self._dry_specific_heat = soil.specificHeatOfDrySoil
self._thermal_absorptance = soil.thermalAbsorptance
self._solar_absorptance = soil.solarAbsorptance
self._visible_absorptance = soil.visibleAbsorptance
self._saturation_volumetric_moisture_content = soil.saturationVolumetricMoistureContent
self._residual_volumetric_moisture_content = soil.residualVolumetricMoistureContent
self._initial_volumetric_moisture_content = soil.initialVolumetricMoistureContent
@property
def name(self):
"""
Get soil name
:return: string
"""
return self._name
@property
def roughness(self):
"""
Get soil roughness
:return: string
"""
return self._roughness
@property
def dry_conductivity(self):
"""
Get soil dry conductivity in W/mK
:return: float
"""
return self._dry_conductivity
@property
def dry_density(self):
"""
Get soil dry density in kg/m3
:return: float
"""
return self._dry_density
@property
def dry_specific_heat(self):
"""
Get soil dry specific heat in J/kgK
:return: float
"""
return self._dry_specific_heat
@property
def thermal_absorptance(self):
"""
Get soil thermal absortance
:return: float
"""
return self._thermal_absorptance
@property
def solar_absorptance(self):
"""
Get soil solar absortance
:return: float
"""
return self._solar_absorptance
@property
def visible_absorptance(self):
"""
Get soil visible absortance
:return: float
"""
return self._visible_absorptance
@property
def saturation_volumetric_moisture_content(self):
"""
Get soil saturation volumetric moisture content
:return: float
"""
return self._saturation_volumetric_moisture_content
@property
def residual_volumetric_moisture_content(self):
"""
Get soil residual volumetric moisture content
:return: float
"""
return self._residual_volumetric_moisture_content
@property
def initial_volumetric_moisture_content(self):
"""
Get soil initial volumetric moisture content
:return: float
"""
return self._initial_volumetric_moisture_content
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Soil': {'name': self.name,
# 'roughness': self.roughness, # todo: this line prints value=2????
'dry conductivity [W/m2K]': self.dry_conductivity,
'dry density [kg/m3]': self.dry_density,
'dry specific heat [J/kgK]': self.dry_specific_heat,
'thermal absorptance': self.thermal_absorptance,
'solar absorptance': self.solar_absorptance,
'visible absorptance': self.visible_absorptance,
'saturation volumetric moisture content [units??]': self.saturation_volumetric_moisture_content,
'residual volumetric moisture content [units??]': self.residual_volumetric_moisture_content
}
}
return content

View File

@ -0,0 +1,198 @@
"""
Greenery catalog data model Vegetation class
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from hub.catalog_factories.data_models.greenery.plant_percentage import PlantPercentage
class Vegetation:
"""
Vegetation class
"""
def __init__(self, category, vegetation, plant_percentages):
self._name = vegetation.name
self._category = category
self._soil_thickness = vegetation.thicknessOfSoil
self._management = vegetation.management
self._air_gap = vegetation.airGap
self._soil_name = vegetation.soil.name
self._soil_roughness = vegetation.soil.roughness
self._dry_soil_conductivity = vegetation.soil.conductivityOfDrySoil
self._dry_soil_density = vegetation.soil.densityOfDrySoil
self._dry_soil_specific_heat = vegetation.soil.specificHeatOfDrySoil
self._soil_thermal_absorptance = vegetation.soil.thermalAbsorptance
self._soil_solar_absorptance = vegetation.soil.solarAbsorptance
self._soil_visible_absorptance = vegetation.soil.visibleAbsorptance
self._soil_saturation_volumetric_moisture_content = vegetation.soil.saturationVolumetricMoistureContent
self._soil_residual_volumetric_moisture_content = vegetation.soil.residualVolumetricMoistureContent
self._soil_initial_volumetric_moisture_content = vegetation.soil.initialVolumetricMoistureContent
self._plant_percentages = plant_percentages
@property
def name(self):
"""
Get vegetation name
:return: string
"""
return self._name
@property
def category(self):
"""
Get vegetation category
:return: string
"""
return self._category
@property
def soil_thickness(self):
"""
Get soil thickness in m
:return: float
"""
return self._soil_thickness
@property
def management(self):
"""
Get management
:return: string
"""
return self._management
@property
def air_gap(self):
"""
Get air gap in m
:return: float
"""
return self._air_gap
@property
def plant_percentages(self) -> [PlantPercentage]:
"""
Get plant percentages
:return: [PlantPercentage]
"""
percentage = 0.0
for plant_percentage in self._plant_percentages:
percentage += float(plant_percentage.percentage)
if percentage > 100:
raise ValueError('the plant percentage in this vegetation is over 100%')
return self._plant_percentages
@property
def soil_name(self):
"""
Get soil name
:return: string
"""
return self._soil_name
@property
def soil_roughness(self):
"""
Get soil roughness
:return: float
"""
return self._soil_roughness
@property
def dry_soil_conductivity(self):
"""
Get soil dry conductivity in W/mK
:return: float
"""
return self._dry_soil_conductivity
@property
def dry_soil_density(self):
"""
Get soil dry density in kg/m3
:return: float
"""
return self._dry_soil_density
@property
def dry_soil_specific_heat(self):
"""
Get soil dry specific heat in J/kgK
:return: float
"""
return self._dry_soil_specific_heat
@property
def soil_thermal_absorptance(self):
"""
Get soil thermal absortance
:return: float
"""
return self._soil_thermal_absorptance
@property
def soil_solar_absorptance(self):
"""
Get soil solar absortance
:return: float
"""
return self._soil_solar_absorptance
@property
def soil_visible_absorptance(self):
"""
Get soil visible absortance
:return: float
"""
return self._soil_visible_absorptance
@property
def soil_saturation_volumetric_moisture_content(self):
"""
Get soil saturation volumetric moisture content
:return: float
"""
return self._soil_saturation_volumetric_moisture_content
@property
def soil_residual_volumetric_moisture_content(self):
"""
Get soil residual volumetric moisture content
:return: float
"""
return self._soil_residual_volumetric_moisture_content
@property
def soil_initial_volumetric_moisture_content(self):
"""
Get soil initial volumetric moisture content
:return: float
"""
return self._soil_initial_volumetric_moisture_content
def to_dictionary(self):
"""Class content to dictionary"""
_plants = []
for _plant in self.plant_percentages:
_plants.append(_plant.to_dictionary())
content = {'Archetype': {'name': self.name,
'category': self.category,
'air gap thickness [m]': self.air_gap,
'soil thickness [m]': self.soil_thickness,
'soil name': self.soil_name,
# 'soil roughness': self.soil_roughness, # todo: this line prints value=2????
'dry soil conductivity [W/m2K]': self.dry_soil_conductivity,
'dry soil density [kg/m3]': self.dry_soil_density,
'dry soil specific heat [J/kgK]': self.dry_soil_specific_heat,
'soil thermal absorptance': self.soil_thermal_absorptance,
'soil solar absorptance': self.soil_solar_absorptance,
'soil visible absorptance': self.soil_visible_absorptance,
'soil saturation volumetric moisture content [units??]': self.soil_saturation_volumetric_moisture_content,
'soil residual volumetric moisture content [units??]': self.soil_residual_volumetric_moisture_content,
'plants': _plants
}
}
return content

View File

@ -0,0 +1,76 @@
"""
Usage catalog appliances
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from typing import Union, List
from hub.catalog_factories.data_models.usages.schedule import Schedule
class Appliances:
"""
Appliances class
"""
def __init__(self, density, convective_fraction, radiative_fraction, latent_fraction, schedules):
self._density = density
self._convective_fraction = convective_fraction
self._radiative_fraction = radiative_fraction
self._latent_fraction = latent_fraction
self._schedules = schedules
@property
def density(self) -> Union[None, float]:
"""
Get appliances density in W/m2
:return: None or float
"""
return self._density
@property
def convective_fraction(self) -> Union[None, float]:
"""
Get convective fraction
:return: None or float
"""
return self._convective_fraction
@property
def radiative_fraction(self) -> Union[None, float]:
"""
Get radiant fraction
:return: None or float
"""
return self._radiative_fraction
@property
def latent_fraction(self) -> Union[None, float]:
"""
Get latent fraction
:return: None or float
"""
return self._latent_fraction
@property
def schedules(self) -> Union[None, List[Schedule]]:
"""
Get schedules
dataType = fraction
:return: None or [Schedule]
"""
return self._schedules
def to_dictionary(self):
"""Class content to dictionary"""
_schedules = []
for _schedule in self.schedules:
_schedules.append(_schedule.to_dictionary())
content = {'Appliances': {'density [W/m2]': self.density,
'convective fraction': self.convective_fraction,
'radiative fraction': self.radiative_fraction,
'latent fraction': self.latent_fraction,
'schedules': _schedules}
}
return content

View File

@ -0,0 +1,40 @@
"""
Usage catalog usage
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from hub.catalog_factories.data_models.usages.usage import Usage
class Content:
"""
Content class
"""
def __init__(self, usages):
self._usages = usages
@property
def usages(self) -> [Usage]:
"""
Get catalog usages
"""
return self._usages
def to_dictionary(self):
"""Class content to dictionary"""
_usages = []
for _usage in self.usages:
_usages.append(_usage.to_dictionary())
content = {'Usages': _usages}
return content
def __str__(self):
"""Print content"""
_usages = []
for _usage in self.usages:
_usages.append(_usage.to_dictionary())
content = {'Usages': _usages}
return str(content)

View File

@ -0,0 +1,66 @@
"""
Usage catalog domestic hot water
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import Union, List
from hub.catalog_factories.data_models.usages.schedule import Schedule
class DomesticHotWater:
"""
DomesticHotWater class
"""
def __init__(self, density, peak_flow, service_temperature, schedules):
self._density = density
self._peak_flow = peak_flow
self._service_temperature = service_temperature
self._schedules = schedules
@property
def density(self) -> Union[None, float]:
"""
Get domestic hot water load density in Watts per m2
:return: None or float
"""
return self._density
@property
def peak_flow(self) -> Union[None, float]:
"""
Get domestic hot water peak_flow density in m3 per second and m2
:return: None or float
"""
return self._peak_flow
@property
def service_temperature(self) -> Union[None, float]:
"""
Get service temperature in degrees Celsius
:return: None or float
"""
return self._service_temperature
@property
def schedules(self) -> Union[None, List[Schedule]]:
"""
Get schedules
dataType = fraction of loads
:return: None or [Schedule]
"""
return self._schedules
def to_dictionary(self):
"""Class content to dictionary"""
_schedules = []
for _schedule in self.schedules:
_schedules.append(_schedule.to_dictionary())
content = {'Domestic hot water': {'density [W/m2]': self.density,
'peak flow [m3/sm2]': self.peak_flow,
'service temperature [Celsius]': self.service_temperature,
'schedules': _schedules}
}
return content

View File

@ -0,0 +1,76 @@
"""
Usage catalog lighting
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from typing import Union, List
from hub.catalog_factories.data_models.usages.schedule import Schedule
class Lighting:
"""
Lighting class
"""
def __init__(self, density, convective_fraction, radiative_fraction, latent_fraction, schedules):
self._density = density
self._convective_fraction = convective_fraction
self._radiative_fraction = radiative_fraction
self._latent_fraction = latent_fraction
self._schedules = schedules
@property
def density(self) -> Union[None, float]:
"""
Get lighting density in Watts per m2
:return: None or float
"""
return self._density
@property
def convective_fraction(self) -> Union[None, float]:
"""
Get convective fraction
:return: None or float
"""
return self._convective_fraction
@property
def radiative_fraction(self) -> Union[None, float]:
"""
Get radiant fraction
:return: None or float
"""
return self._radiative_fraction
@property
def latent_fraction(self) -> Union[None, float]:
"""
Get latent fraction
:return: None or float
"""
return self._latent_fraction
@property
def schedules(self) -> Union[None, List[Schedule]]:
"""
Get schedules
dataType = fraction
:return: None or [Schedule]
"""
return self._schedules
def to_dictionary(self):
"""Class content to dictionary"""
_schedules = []
for _schedule in self.schedules:
_schedules.append(_schedule.to_dictionary())
content = {'Lighting': {'density [W/m2]': self.density,
'convective fraction': self.convective_fraction,
'radiative fraction': self.radiative_fraction,
'latent fraction': self.latent_fraction,
'schedules': _schedules}
}
return content

View File

@ -0,0 +1,80 @@
"""
Usage catalog occupancy
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
"""
from typing import Union, List
from hub.catalog_factories.data_models.usages.schedule import Schedule
class Occupancy:
"""
Occupancy class
"""
def __init__(self,
occupancy_density,
sensible_convective_internal_gain,
sensible_radiative_internal_gain,
latent_internal_gain,
schedules):
self._occupancy_density = occupancy_density
self._sensible_convective_internal_gain = sensible_convective_internal_gain
self._sensible_radiative_internal_gain = sensible_radiative_internal_gain
self._latent_internal_gain = latent_internal_gain
self._schedules = schedules
@property
def occupancy_density(self) -> Union[None, float]:
"""
Get density in persons per m2
:return: None or float
"""
return self._occupancy_density
@property
def sensible_convective_internal_gain(self) -> Union[None, float]:
"""
Get sensible convective internal gain in Watts per m2
:return: None or float
"""
return self._sensible_convective_internal_gain
@property
def sensible_radiative_internal_gain(self) -> Union[None, float]:
"""
Get sensible radiant internal gain in Watts per m2
:return: None or float
"""
return self._sensible_radiative_internal_gain
@property
def latent_internal_gain(self) -> Union[None, float]:
"""
Get latent internal gain in Watts per m2
:return: None or float
"""
return self._latent_internal_gain
@property
def schedules(self) -> Union[None, List[Schedule]]:
"""
Get occupancy schedules
dataType = fraction
:return: None or [Schedule]
"""
return self._schedules
def to_dictionary(self):
"""Class content to dictionary"""
_schedules = []
for _schedule in self.schedules:
_schedules.append(_schedule.to_dictionary())
content = {'Occupancy': {'occupancy density [persons/m2]': self.occupancy_density,
'sensible convective internal gain [W/m2]': self.sensible_convective_internal_gain,
'sensible radiative internal gain [W/m2]': self.sensible_radiative_internal_gain,
'latent internal gain [W/m2]': self.latent_internal_gain,
'schedules': _schedules}
}
return content

View File

@ -0,0 +1,86 @@
"""
Usage catalog schedule
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from typing import Union, List
class Schedule:
"""
Schedule class
"""
def __init__(self, schedule_type, values, data_type, time_step, time_range, day_types):
self._type = schedule_type
self._values = values
self._data_type = data_type
self._time_step = time_step
self._time_range = time_range
self._day_types = day_types
@property
def type(self) -> Union[None, str]:
"""
Get schedule type
:return: None or str
"""
return self._type
@property
def values(self):
"""
Get schedule values
:return: [Any]
"""
return self._values
@property
def data_type(self) -> Union[None, str]:
"""
Get schedule data type from:
['any_number', 'fraction', 'on_off', 'temperature', 'humidity', 'control_type']
:return: None or str
"""
return self._data_type
@property
def time_step(self) -> Union[None, str]:
"""
Get schedule time step from:
['second', 'minute', 'hour', 'day', 'week', 'month']
:return: None or str
"""
return self._time_step
@property
def time_range(self) -> Union[None, str]:
"""
Get schedule time range from:
['minute', 'hour', 'day', 'week', 'month', 'year']
:return: None or str
"""
return self._time_range
@property
def day_types(self) -> Union[None, List[str]]:
"""
Get schedule day types, as many as needed from:
['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'holiday', 'winter_design_day',
'summer_design_day']
:return: None or [str]
"""
return self._day_types
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Schedule': {'type': self.type,
'time range': self.time_range,
'time step': self.time_step,
'data type': self.data_type,
'day types': self.day_types,
'values': self.values}
}
return content

View File

@ -0,0 +1,98 @@
"""
Usage catalog thermal control
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from typing import Union, List
from hub.catalog_factories.data_models.usages.schedule import Schedule
class ThermalControl:
"""
ThermalControl class
"""
def __init__(self, mean_heating_set_point,
heating_set_back,
mean_cooling_set_point,
hvac_availability_schedules,
heating_set_point_schedules,
cooling_set_point_schedules):
self._mean_heating_set_point = mean_heating_set_point
self._heating_set_back = heating_set_back
self._mean_cooling_set_point = mean_cooling_set_point
self._hvac_availability_schedules = hvac_availability_schedules
self._heating_set_point_schedules = heating_set_point_schedules
self._cooling_set_point_schedules = cooling_set_point_schedules
@property
def mean_heating_set_point(self) -> Union[None, float]:
"""
Get heating set point defined for a thermal zone in Celsius
:return: None or float
"""
return self._mean_heating_set_point
@property
def heating_set_back(self) -> Union[None, float]:
"""
Get heating set back defined for a thermal zone in Celsius
:return: None or float
"""
return self._heating_set_back
@property
def mean_cooling_set_point(self) -> Union[None, float]:
"""
Get cooling set point defined for a thermal zone in Celsius
:return: None or float
"""
return self._mean_cooling_set_point
@property
def hvac_availability_schedules(self) -> Union[None, List[Schedule]]:
"""
Get the availability of the conditioning system defined for a thermal zone
dataType = on/off
:return: None or [Schedule]
"""
return self._hvac_availability_schedules
@property
def heating_set_point_schedules(self) -> Union[None, List[Schedule]]:
"""
Get heating set point schedule defined for a thermal zone in Celsius
dataType = temperature
:return: None or [Schedule]
"""
return self._heating_set_point_schedules
@property
def cooling_set_point_schedules(self) -> Union[None, List[Schedule]]:
"""
Get cooling set point schedule defined for a thermal zone in Celsius
dataType = temperature
:return: None or [Schedule]
"""
return self._cooling_set_point_schedules
def to_dictionary(self):
"""Class content to dictionary"""
_hvac_schedules = []
for _schedule in self.hvac_availability_schedules:
_hvac_schedules.append(_schedule.to_dictionary())
_heating_set_point_schedules = []
for _schedule in self.heating_set_point_schedules:
_heating_set_point_schedules.append(_schedule.to_dictionary())
_cooling_set_point_schedules = []
for _schedule in self.cooling_set_point_schedules:
_cooling_set_point_schedules.append(_schedule.to_dictionary())
content = {'Thermal control': {'mean heating set point [Celsius]': self.mean_heating_set_point,
'heating set back [Celsius]': self.heating_set_back,
'mean cooling set point [Celsius]': self.mean_cooling_set_point,
'hvac availability schedules': _hvac_schedules,
'heating set point schedules': _heating_set_point_schedules,
'cooling set point schedules': _cooling_set_point_schedules}
}
return content

View File

@ -0,0 +1,143 @@
"""
Usage catalog usage
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from typing import Union
from hub.catalog_factories.data_models.usages.appliances import Appliances
from hub.catalog_factories.data_models.usages.lighting import Lighting
from hub.catalog_factories.data_models.usages.occupancy import Occupancy
from hub.catalog_factories.data_models.usages.thermal_control import ThermalControl
from hub.catalog_factories.data_models.usages.domestic_hot_water import DomesticHotWater
class Usage:
"""
Usage class
"""
def __init__(self, name,
hours_day,
days_year,
mechanical_air_change,
ventilation_rate,
occupancy,
lighting,
appliances,
thermal_control,
domestic_hot_water):
self._name = name
self._hours_day = hours_day
self._days_year = days_year
self._mechanical_air_change = mechanical_air_change
self._ventilation_rate = ventilation_rate
self._occupancy = occupancy
self._lighting = lighting
self._appliances = appliances
self._thermal_control = thermal_control
self._domestic_hot_water = domestic_hot_water
@property
def name(self) -> Union[None, str]:
"""
Get usage zone usage name
:return: None or str
"""
return self._name
@property
def hours_day(self) -> Union[None, float]:
"""
Get usage zone usage hours per day
:return: None or float
"""
return self._hours_day
@property
def days_year(self) -> Union[None, float]:
"""
Get usage zone usage days per year
:return: None or float
"""
return self._days_year
@property
def mechanical_air_change(self) -> Union[None, float]:
"""
Get usage zone mechanical air change in air change per second (1/s)
:return: None or float
"""
return self._mechanical_air_change
@property
def ventilation_rate(self) -> Union[None, float]:
"""
Get usage zone ventilation rate in m3/m2s
:return: None or float
"""
return self._ventilation_rate
@property
def occupancy(self) -> Union[None, Occupancy]:
"""
Get occupancy in the usage zone
:return: None or Occupancy
"""
return self._occupancy
@property
def lighting(self) -> Union[None, Lighting]:
"""
Get lighting information
:return: None or Lighting
"""
return self._lighting
@lighting.setter
def lighting(self, value):
"""
Set lighting information
:param value: Lighting
"""
self._lighting = value
@property
def appliances(self) -> Union[None, Appliances]:
"""
Get appliances information
:return: None or Appliances
"""
return self._appliances
@property
def thermal_control(self) -> Union[None, ThermalControl]:
"""
Get thermal control information
:return: None or ThermalControl
"""
return self._thermal_control
@property
def domestic_hot_water(self) -> Union[None, DomesticHotWater]:
"""
Get domestic hot water information
:return: None or DomesticHotWater
"""
return self._domestic_hot_water
def to_dictionary(self):
"""Class content to dictionary"""
content = {'Usage': {'name': self.name,
'hours a day': self.hours_day,
'days a year': self.days_year,
'mechanical air change [ACH]': self.mechanical_air_change,
'ventilation rate [m3/sm2]': self.ventilation_rate,
'occupancy': self.occupancy.to_dictionary(),
'lighting': self.lighting.to_dictionary(),
'appliances': self.appliances.to_dictionary(),
'thermal control': self.thermal_control.to_dictionary(),
'domestic hot water': self.domestic_hot_water.to_dictionary()
}
}
return content

View File

@ -0,0 +1,256 @@
"""
Montreal custom energy systems catalog module
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 ast import literal_eval
import xmltodict
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.energy_systems.system import System
from hub.catalog_factories.data_models.energy_systems.content import Content
from hub.catalog_factories.data_models.energy_systems.non_pv_generation_system import NonPvGenerationSystem
from hub.catalog_factories.data_models.energy_systems.pv_generation_system import PvGenerationSystem
from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem
from hub.catalog_factories.data_models.energy_systems.emission_system import EmissionSystem
from hub.catalog_factories.data_models.energy_systems.archetype import Archetype
from hub.catalog_factories.data_models.energy_systems.thermal_storage_system import ThermalStorageSystem
from hub.catalog_factories.data_models.energy_systems.electrical_storage_system import ElectricalStorageSystem
class MontrealCustomCatalog(Catalog):
"""
Montreal custom energy systems catalog class
"""
def __init__(self, path):
path = str(path / 'montreal_custom_systems.xml')
with open(path, 'r', encoding='utf-8') as xml:
self._archetypes = xmltodict.parse(xml.read(), force_list=('system', 'system_cluster', 'equipment',
'demand', 'system_id'))
self._catalog_generation_equipments = self._load_generation_equipments()
self._catalog_emission_equipments = self._load_emission_equipments()
self._catalog_distribution_equipments = self._load_distribution_equipments()
self._catalog_systems = self._load_systems()
self._catalog_archetypes = self._load_archetypes()
# store the full catalog data model in self._content
self._content = Content(self._catalog_archetypes,
self._catalog_systems,
self._catalog_generation_equipments,
self._catalog_distribution_equipments)
def _load_generation_equipments(self):
_equipments = []
_storages = []
equipments = self._archetypes['catalog']['generation_equipments']['equipment']
for equipment in equipments:
equipment_id = float(equipment['@id'])
equipment_type = equipment['@type']
fuel_type = equipment['@fuel_type']
model_name = equipment['name']
heating_efficiency = None
if 'heating_efficiency' in equipment:
heating_efficiency = float(equipment['heating_efficiency'])
cooling_efficiency = None
if 'cooling_efficiency' in equipment:
cooling_efficiency = float(equipment['cooling_efficiency'])
electricity_efficiency = None
if 'electrical_efficiency' in equipment:
electricity_efficiency = float(equipment['electrical_efficiency'])
storage_systems = None
storage = literal_eval(equipment['storage'].capitalize())
if storage:
if equipment_type == 'electricity generator':
storage_system = ElectricalStorageSystem(equipment_id)
else:
storage_system = ThermalStorageSystem(equipment_id)
storage_systems = [storage_system]
if model_name == 'PV system':
system_type = 'Photovoltaic'
generation_system = PvGenerationSystem(equipment_id,
name=None,
system_type= system_type,
model_name=model_name,
electricity_efficiency=electricity_efficiency,
energy_storage_systems=storage_systems
)
else:
generation_system = NonPvGenerationSystem(equipment_id,
name=None,
model_name=model_name,
system_type=equipment_type,
fuel_type=fuel_type,
heat_efficiency=heating_efficiency,
cooling_efficiency=cooling_efficiency,
electricity_efficiency=electricity_efficiency,
energy_storage_systems=storage_systems,
domestic_hot_water=False
)
_equipments.append(generation_system)
return _equipments
def _load_distribution_equipments(self):
_equipments = []
equipments = self._archetypes['catalog']['distribution_equipments']['equipment']
for equipment in equipments:
equipment_id = float(equipment['@id'])
equipment_type = equipment['@type']
model_name = equipment['name']
distribution_heat_losses = None
if 'distribution_heat_losses' in equipment:
distribution_heat_losses = float(equipment['distribution_heat_losses']['#text']) / 100
distribution_consumption_fix_flow = None
if 'distribution_consumption_fix_flow' in equipment:
distribution_consumption_fix_flow = float(equipment['distribution_consumption_fix_flow']['#text']) / 100
distribution_consumption_variable_flow = None
if 'distribution_consumption_variable_flow' in equipment:
distribution_consumption_variable_flow = float(
equipment['distribution_consumption_variable_flow']['#text']) / 100
emission_equipment = equipment['dissipation_id']
_emission_equipments = None
for equipment_archetype in self._catalog_emission_equipments:
if int(equipment_archetype.id) == int(emission_equipment):
_emission_equipments = [equipment_archetype]
distribution_system = DistributionSystem(equipment_id,
model_name=model_name,
system_type=equipment_type,
distribution_consumption_fix_flow=distribution_consumption_fix_flow,
distribution_consumption_variable_flow=distribution_consumption_variable_flow,
heat_losses=distribution_heat_losses,
emission_systems=_emission_equipments)
_equipments.append(distribution_system)
return _equipments
def _load_emission_equipments(self):
_equipments = []
equipments = self._archetypes['catalog']['dissipation_equipments']['equipment']
for equipment in equipments:
equipment_id = float(equipment['@id'])
equipment_type = equipment['@type']
model_name = equipment['name']
parasitic_consumption = None
if 'parasitic_consumption' in equipment:
parasitic_consumption = float(equipment['parasitic_consumption']['#text']) / 100
emission_system = EmissionSystem(equipment_id,
model_name=model_name,
system_type=equipment_type,
parasitic_energy_consumption=parasitic_consumption)
_equipments.append(emission_system)
return _equipments
def _load_systems(self):
_catalog_systems = []
systems = self._archetypes['catalog']['systems']['system']
for system in systems:
system_id = float(system['@id'])
name = system['name']
demands = system['demands']['demand']
generation_equipment = system['equipments']['generation_id']
_generation_equipments = None
for equipment_archetype in self._catalog_generation_equipments:
if int(equipment_archetype.id) == int(generation_equipment):
_generation_equipments = [equipment_archetype]
distribution_equipment = system['equipments']['distribution_id']
_distribution_equipments = None
for equipment_archetype in self._catalog_distribution_equipments:
if int(equipment_archetype.id) == int(distribution_equipment):
_distribution_equipments = [equipment_archetype]
_catalog_systems.append(System(system_id,
demands,
name=name,
generation_systems=_generation_equipments,
distribution_systems=_distribution_equipments))
return _catalog_systems
def _load_archetypes(self):
_catalog_archetypes = []
system_clusters = self._archetypes['catalog']['system_clusters']['system_cluster']
for system_cluster in system_clusters:
name = system_cluster['@name']
systems = system_cluster['systems']['system_id']
_systems = []
for system in systems:
for system_archetype in self._catalog_systems:
if int(system_archetype.id) == int(system):
_systems.append(system_archetype)
_catalog_archetypes.append(Archetype(name, _systems))
return _catalog_archetypes
def names(self, category=None):
"""
Get the catalog elements names
:parm: optional category filter
"""
if category is None:
_names = {'archetypes': [], 'systems': [], 'generation_equipments': [], 'distribution_equipments': [],
'emission_equipments': []}
for archetype in self._content.archetypes:
_names['archetypes'].append(archetype.name)
for system in self._content.systems:
_names['systems'].append(system.name)
for equipment in self._content.generation_equipments:
_names['generation_equipments'].append(equipment.model_name)
for equipment in self._content.distribution_equipments:
_names['distribution_equipments'].append(equipment.model_name)
else:
_names = {category: []}
if category.lower() == 'archetypes':
for archetype in self._content.archetypes:
_names[category].append(archetype.name)
elif category.lower() == 'systems':
for system in self._content.systems:
_names[category].append(system.name)
elif category.lower() == 'generation_equipments':
for system in self._content.generation_equipments:
_names[category].append(system.model_name)
elif category.lower() == 'distribution_equipments':
for system in self._content.distribution_equipments:
_names[category].append(system.model_name)
else:
raise ValueError(f'Unknown category [{category}]')
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: optional category filter
"""
if category is None:
return self._content
if category.lower() == 'archetypes':
return self._content.archetypes
if category.lower() == 'systems':
return self._content.systems
if category.lower() == 'generation_equipments':
return self._content.generation_equipments
if category.lower() == 'distribution_equipments':
return self._content.distribution_equipments
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for entry in self._content.archetypes:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.systems:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.generation_equipments:
if entry.model_name.lower() == name.lower():
return entry
for entry in self._content.distribution_equipments:
if entry.model_name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,557 @@
"""
Montreal future energy system catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Saeed Ranjbar saeed.ranjbar@concordia.ca
"""
import xmltodict
from pathlib import Path
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem
from hub.catalog_factories.data_models.energy_systems.emission_system import EmissionSystem
from hub.catalog_factories.data_models.energy_systems.system import System
from hub.catalog_factories.data_models.energy_systems.content import Content
from hub.catalog_factories.data_models.energy_systems.non_pv_generation_system import NonPvGenerationSystem
from hub.catalog_factories.data_models.energy_systems.pv_generation_system import PvGenerationSystem
from hub.catalog_factories.data_models.energy_systems.thermal_storage_system import ThermalStorageSystem
from hub.catalog_factories.data_models.energy_systems.performance_curves import PerformanceCurves
from hub.catalog_factories.data_models.energy_systems.archetype import Archetype
from hub.catalog_factories.data_models.construction.material import Material
from hub.catalog_factories.data_models.construction.layer import Layer
class MontrealFutureSystemCatalogue(Catalog):
"""
North america energy system catalog class
"""
def __init__(self, path):
path = str(path / 'montreal_future_systems.xml')
with open(path, 'r', encoding='utf-8') as xml:
self._archetypes = xmltodict.parse(xml.read(),
force_list=['pv_generation_component', 'templateStorages', 'demand'])
self._storage_components = self._load_storage_components()
self._generation_components = self._load_generation_components()
self._energy_emission_components = self._load_emission_equipments()
self._distribution_components = self._load_distribution_equipments()
self._systems = self._load_systems()
self._system_archetypes = self._load_archetypes()
self._content = Content(self._system_archetypes,
self._systems,
generations=self._generation_components,
distributions=self._distribution_components)
def _load_generation_components(self):
generation_components = []
non_pv_generation_components = self._archetypes['EnergySystemCatalog']['energy_generation_components'][
'non_pv_generation_component']
if non_pv_generation_components is not None:
for non_pv in non_pv_generation_components:
system_id = non_pv['system_id']
name = non_pv['name']
system_type = non_pv['system_type']
model_name = non_pv['model_name']
manufacturer = non_pv['manufacturer']
fuel_type = non_pv['fuel_type']
distribution_systems = non_pv['distribution_systems']
energy_storage_systems = None
if non_pv['energy_storage_systems'] is not None:
storage_component = non_pv['energy_storage_systems']['storage_id']
storage_systems = self._search_storage_equipment(self._load_storage_components(), storage_component)
energy_storage_systems = storage_systems
nominal_heat_output = non_pv['nominal_heat_output']
maximum_heat_output = non_pv['maximum_heat_output']
minimum_heat_output = non_pv['minimum_heat_output']
source_medium = non_pv['source_medium']
supply_medium = non_pv['supply_medium']
heat_efficiency = non_pv['heat_efficiency']
nominal_cooling_output = non_pv['nominal_cooling_output']
maximum_cooling_output = non_pv['maximum_cooling_output']
minimum_cooling_output = non_pv['minimum_cooling_output']
cooling_efficiency = non_pv['cooling_efficiency']
electricity_efficiency = non_pv['electricity_efficiency']
source_temperature = non_pv['source_temperature']
source_mass_flow = non_pv['source_mass_flow']
nominal_electricity_output = non_pv['nominal_electricity_output']
maximum_heat_supply_temperature = non_pv['maximum_heat_supply_temperature']
minimum_heat_supply_temperature = non_pv['minimum_heat_supply_temperature']
maximum_cooling_supply_temperature = non_pv['maximum_cooling_supply_temperature']
minimum_cooling_supply_temperature = non_pv['minimum_cooling_supply_temperature']
heat_output_curve = None
heat_fuel_consumption_curve = None
heat_efficiency_curve = None
cooling_output_curve = None
cooling_fuel_consumption_curve = None
cooling_efficiency_curve = None
if non_pv['heat_output_curve'] is not None:
curve_type = non_pv['heat_output_curve']['curve_type']
dependant_variable = non_pv['heat_output_curve']['dependant_variable']
parameters = non_pv['heat_output_curve']['parameters']
coefficients = list(non_pv['heat_output_curve']['coefficients'].values())
heat_output_curve = PerformanceCurves(curve_type, dependant_variable, parameters, coefficients)
if non_pv['heat_fuel_consumption_curve'] is not None:
curve_type = non_pv['heat_fuel_consumption_curve']['curve_type']
dependant_variable = non_pv['heat_fuel_consumption_curve']['dependant_variable']
parameters = non_pv['heat_fuel_consumption_curve']['parameters']
coefficients = list(non_pv['heat_fuel_consumption_curve']['coefficients'].values())
heat_fuel_consumption_curve = PerformanceCurves(curve_type, dependant_variable, parameters, coefficients)
if non_pv['heat_efficiency_curve'] is not None:
curve_type = non_pv['heat_efficiency_curve']['curve_type']
dependant_variable = non_pv['heat_efficiency_curve']['dependant_variable']
parameters = non_pv['heat_efficiency_curve']['parameters']
coefficients = list(non_pv['heat_efficiency_curve']['coefficients'].values())
heat_efficiency_curve = PerformanceCurves(curve_type, dependant_variable, parameters, coefficients)
if non_pv['cooling_output_curve'] is not None:
curve_type = non_pv['cooling_output_curve']['curve_type']
dependant_variable = non_pv['cooling_output_curve']['dependant_variable']
parameters = non_pv['cooling_output_curve']['parameters']
coefficients = list(non_pv['cooling_output_curve']['coefficients'].values())
cooling_output_curve = PerformanceCurves(curve_type, dependant_variable, parameters, coefficients)
if non_pv['cooling_fuel_consumption_curve'] is not None:
curve_type = non_pv['cooling_fuel_consumption_curve']['curve_type']
dependant_variable = non_pv['cooling_fuel_consumption_curve']['dependant_variable']
parameters = non_pv['cooling_fuel_consumption_curve']['parameters']
coefficients = list(non_pv['cooling_fuel_consumption_curve']['coefficients'].values())
cooling_fuel_consumption_curve = PerformanceCurves(curve_type, dependant_variable, parameters, coefficients)
if non_pv['cooling_efficiency_curve'] is not None:
curve_type = non_pv['cooling_efficiency_curve']['curve_type']
dependant_variable = non_pv['cooling_efficiency_curve']['dependant_variable']
parameters = non_pv['cooling_efficiency_curve']['parameters']
coefficients = list(non_pv['cooling_efficiency_curve']['coefficients'].values())
cooling_efficiency_curve = PerformanceCurves(curve_type, dependant_variable, parameters, coefficients)
dhw = None
if non_pv['domestic_hot_water'] is not None:
if non_pv['domestic_hot_water'] == 'True':
dhw = True
else:
dhw = False
reversible = None
if non_pv['reversible'] is not None:
if non_pv['reversible'] == 'True':
reversible = True
else:
reversible = False
dual_supply = None
if non_pv['simultaneous_heat_cold'] is not None:
if non_pv['simultaneous_heat_cold'] == 'True':
dual_supply = True
else:
dual_supply = False
non_pv_component = NonPvGenerationSystem(system_id=system_id,
name=name,
system_type=system_type,
model_name=model_name,
manufacturer=manufacturer,
fuel_type=fuel_type,
nominal_heat_output=nominal_heat_output,
maximum_heat_output=maximum_heat_output,
minimum_heat_output=minimum_heat_output,
source_medium=source_medium,
supply_medium=supply_medium,
heat_efficiency=heat_efficiency,
nominal_cooling_output=nominal_cooling_output,
maximum_cooling_output=maximum_cooling_output,
minimum_cooling_output=minimum_cooling_output,
cooling_efficiency=cooling_efficiency,
electricity_efficiency=electricity_efficiency,
source_temperature=source_temperature,
source_mass_flow=source_mass_flow,
nominal_electricity_output=nominal_electricity_output,
maximum_heat_supply_temperature=maximum_heat_supply_temperature,
minimum_heat_supply_temperature=minimum_heat_supply_temperature,
maximum_cooling_supply_temperature=maximum_cooling_supply_temperature,
minimum_cooling_supply_temperature=minimum_cooling_supply_temperature,
heat_output_curve=heat_output_curve,
heat_fuel_consumption_curve=heat_fuel_consumption_curve,
heat_efficiency_curve=heat_efficiency_curve,
cooling_output_curve=cooling_output_curve,
cooling_fuel_consumption_curve=cooling_fuel_consumption_curve,
cooling_efficiency_curve=cooling_efficiency_curve,
distribution_systems=distribution_systems,
energy_storage_systems=energy_storage_systems,
domestic_hot_water=dhw,
reversible=reversible,
simultaneous_heat_cold=dual_supply)
generation_components.append(non_pv_component)
pv_generation_components = self._archetypes['EnergySystemCatalog']['energy_generation_components'][
'pv_generation_component']
if pv_generation_components is not None:
for pv in pv_generation_components:
system_id = pv['system_id']
name = pv['name']
system_type = pv['system_type']
model_name = pv['model_name']
manufacturer = pv['manufacturer']
electricity_efficiency = pv['electricity_efficiency']
nominal_electricity_output = pv['nominal_electricity_output']
nominal_ambient_temperature = pv['nominal_ambient_temperature']
nominal_cell_temperature = pv['nominal_cell_temperature']
nominal_radiation = pv['nominal_radiation']
standard_test_condition_cell_temperature = pv['standard_test_condition_cell_temperature']
standard_test_condition_maximum_power = pv['standard_test_condition_maximum_power']
cell_temperature_coefficient = pv['cell_temperature_coefficient']
width = pv['width']
height = pv['height']
distribution_systems = pv['distribution_systems']
energy_storage_systems = None
if pv['energy_storage_systems'] is not None:
storage_component = pv['energy_storage_systems']['storage_id']
storage_systems = self._search_storage_equipment(self._load_storage_components(), storage_component)
energy_storage_systems = storage_systems
pv_component = PvGenerationSystem(system_id=system_id,
name=name,
system_type=system_type,
model_name=model_name,
manufacturer=manufacturer,
electricity_efficiency=electricity_efficiency,
nominal_electricity_output=nominal_electricity_output,
nominal_ambient_temperature=nominal_ambient_temperature,
nominal_cell_temperature=nominal_cell_temperature,
nominal_radiation=nominal_radiation,
standard_test_condition_cell_temperature=
standard_test_condition_cell_temperature,
standard_test_condition_maximum_power=standard_test_condition_maximum_power,
cell_temperature_coefficient=cell_temperature_coefficient,
width=width,
height=height,
distribution_systems=distribution_systems,
energy_storage_systems=energy_storage_systems)
generation_components.append(pv_component)
return generation_components
def _load_distribution_equipments(self):
_equipments = []
distribution_systems = self._archetypes['EnergySystemCatalog']['distribution_systems']['distribution_system']
if distribution_systems is not None:
for distribution_system in distribution_systems:
system_id = None
model_name = None
system_type = None
supply_temperature = None
distribution_consumption_fix_flow = None
distribution_consumption_variable_flow = None
heat_losses = None
generation_systems = None
energy_storage_systems = None
emission_systems = None
distribution_equipment = DistributionSystem(system_id=system_id,
model_name=model_name,
system_type=system_type,
supply_temperature=supply_temperature,
distribution_consumption_fix_flow=distribution_consumption_fix_flow,
distribution_consumption_variable_flow=
distribution_consumption_variable_flow,
heat_losses=heat_losses,
generation_systems=generation_systems,
energy_storage_systems=energy_storage_systems,
emission_systems=emission_systems
)
_equipments.append(distribution_equipment)
return _equipments
def _load_emission_equipments(self):
_equipments = []
dissipation_systems = self._archetypes['EnergySystemCatalog']['dissipation_systems']['dissipation_system']
if dissipation_systems is not None:
for dissipation_system in dissipation_systems:
system_id = None
model_name = None
system_type = None
parasitic_energy_consumption = None
emission_system = EmissionSystem(system_id=system_id,
model_name=model_name,
system_type=system_type,
parasitic_energy_consumption=parasitic_energy_consumption)
_equipments.append(emission_system)
return _equipments
def _load_storage_components(self):
storage_components = []
thermal_storages = self._archetypes['EnergySystemCatalog']['energy_storage_components']['thermalStorages']
template_storages = self._archetypes['EnergySystemCatalog']['energy_storage_components']['templateStorages']
for tes in thermal_storages:
storage_id = tes['storage_id']
type_energy_stored = tes['type_energy_stored']
model_name = tes['model_name']
manufacturer = tes['manufacturer']
storage_type = tes['storage_type']
volume = tes['physical_characteristics']['volume']
height = tes['physical_characteristics']['height']
maximum_operating_temperature = tes['maximum_operating_temperature']
materials = self._load_materials()
insulation_material_id = tes['insulation']['material_id']
insulation_material = self._search_material(materials, insulation_material_id)
material_id = tes['physical_characteristics']['material_id']
tank_material = self._search_material(materials, material_id)
thickness = float(tes['insulation']['insulationThickness']) / 100 # from cm to m
insulation_layer = Layer(None, 'insulation', insulation_material, thickness)
thickness = float(tes['physical_characteristics']['tankThickness']) / 100 # from cm to m
tank_layer = Layer(None, 'tank', tank_material, thickness)
media = self._load_media()
media_id = tes['storage_medium']['medium_id']
medium = self._search_media(media, media_id)
layers = [insulation_layer, tank_layer]
nominal_capacity = tes['nominal_capacity']
losses_ratio = tes['losses_ratio']
heating_coil_capacity = None
storage_component = ThermalStorageSystem(storage_id=storage_id,
model_name=model_name,
type_energy_stored=type_energy_stored,
manufacturer=manufacturer,
storage_type=storage_type,
nominal_capacity=nominal_capacity,
losses_ratio=losses_ratio,
volume=volume,
height=height,
layers=layers,
maximum_operating_temperature=maximum_operating_temperature,
storage_medium=medium,
heating_coil_capacity=heating_coil_capacity)
storage_components.append(storage_component)
for template in template_storages:
storage_id = template['storage_id']
storage_type = template['storage_type']
type_energy_stored = template['type_energy_stored']
maximum_operating_temperature = template['maximum_operating_temperature']
height = float(template['physical_characteristics']['height'])
materials = self._load_materials()
insulation_material_id = template['insulation']['material_id']
insulation_material = self._search_material(materials, insulation_material_id)
material_id = template['physical_characteristics']['material_id']
tank_material = self._search_material(materials, material_id)
thickness = float(template['insulation']['insulationThickness']) / 100 # from cm to m
insulation_layer = Layer(None, 'insulation', insulation_material, thickness)
thickness = float(template['physical_characteristics']['tankThickness']) / 100 # from cm to m
tank_layer = Layer(None, 'tank', tank_material, thickness)
layers = [insulation_layer, tank_layer]
media = self._load_media()
media_id = template['storage_medium']['medium_id']
medium = self._search_media(media, media_id)
model_name = template['model_name']
manufacturer = template['manufacturer']
nominal_capacity = template['nominal_capacity']
losses_ratio = template['losses_ratio']
volume = template['physical_characteristics']['volume']
heating_coil_capacity = None
storage_component = ThermalStorageSystem(storage_id=storage_id,
model_name=model_name,
type_energy_stored=type_energy_stored,
manufacturer=manufacturer,
storage_type=storage_type,
nominal_capacity=nominal_capacity,
losses_ratio=losses_ratio,
volume=volume,
height=height,
layers=layers,
maximum_operating_temperature=maximum_operating_temperature,
storage_medium=medium,
heating_coil_capacity=heating_coil_capacity)
storage_components.append(storage_component)
return storage_components
def _load_systems(self):
base_path = Path(Path(__file__).parent.parent.parent / 'data/energy_systems')
_catalog_systems = []
systems = self._archetypes['EnergySystemCatalog']['systems']['system']
for system in systems:
system_id = system['id']
name = system['name']
demands = system['demands']['demand']
generation_components = system['components']['generation_id']
generation_systems = self._search_generation_equipment(self._load_generation_components(), generation_components)
configuration_schema = Path(base_path / system['schema'])
energy_system = System(system_id=system_id,
name=name,
demand_types=demands,
generation_systems=generation_systems,
distribution_systems=None,
configuration_schema=configuration_schema)
_catalog_systems.append(energy_system)
return _catalog_systems
def _load_archetypes(self):
_system_archetypes = []
system_clusters = self._archetypes['EnergySystemCatalog']['system_archetypes']['system_archetype']
for system_cluster in system_clusters:
name = system_cluster['name']
systems = system_cluster['systems']['system_id']
integer_system_ids = [int(item) for item in systems]
_systems = []
for system_archetype in self._systems:
if int(system_archetype.id) in integer_system_ids:
_systems.append(system_archetype)
_system_archetypes.append(Archetype(name=name, systems=_systems))
return _system_archetypes
def _load_materials(self):
materials = []
_materials = self._archetypes['EnergySystemCatalog']['materials']['material']
for _material in _materials:
material_id = _material['material_id']
name = _material['name']
conductivity = _material['conductivity']
solar_absorptance = _material['solar_absorptance']
thermal_absorptance = _material['thermal_absorptance']
density = _material['density']
specific_heat = _material['specific_heat']
no_mass = _material['no_mass']
visible_absorptance = _material['visible_absorptance']
thermal_resistance = _material['thermal_resistance']
material = Material(material_id,
name,
solar_absorptance=solar_absorptance,
thermal_absorptance=thermal_absorptance,
density=density,
conductivity=conductivity,
thermal_resistance=thermal_resistance,
visible_absorptance=visible_absorptance,
no_mass=no_mass,
specific_heat=specific_heat)
materials.append(material)
return materials
@staticmethod
def _search_material(materials, material_id):
_material = None
for material in materials:
if int(material.id) == int(material_id):
_material = material
break
if _material is None:
raise ValueError(f'Material with the id = [{material_id}] not found in catalog ')
return _material
def _load_media(self):
media = []
_media = [self._archetypes['EnergySystemCatalog']['media']['medium']]
for _medium in _media:
medium_id = _medium['medium_id']
density = _medium['density']
name = _medium['name']
conductivity = _medium['conductivity']
solar_absorptance = _medium['solar_absorptance']
thermal_absorptance = _medium['thermal_absorptance']
specific_heat = _medium['specific_heat']
no_mass = _medium['no_mass']
visible_absorptance = _medium['visible_absorptance']
thermal_resistance = _medium['thermal_resistance']
medium = Material(material_id=medium_id,
name=name,
solar_absorptance=solar_absorptance,
thermal_absorptance=thermal_absorptance,
visible_absorptance=visible_absorptance,
no_mass=no_mass,
thermal_resistance=thermal_resistance,
conductivity=conductivity,
density=density,
specific_heat=specific_heat)
media.append(medium)
return media
@staticmethod
def _search_media(media, medium_id):
_medium = None
for medium in media:
if int(medium.id) == int(medium_id):
_medium = medium
break
if _medium is None:
raise ValueError(f'media with the id = [{medium_id}] not found in catalog ')
return _medium
@staticmethod
def _search_generation_equipment(generation_systems, generation_id):
_generation_systems = []
if isinstance(generation_id, list):
integer_ids = [int(item) for item in generation_id]
for generation in generation_systems:
if int(generation.id) in integer_ids:
_generation_systems.append(generation)
else:
integer_id = int(generation_id)
for generation in generation_systems:
if int(generation.id) == integer_id:
_generation_systems.append(generation)
if len(_generation_systems) == 0:
_generation_systems = None
raise ValueError(f'The system with the following id is not found in catalog [{generation_id}]')
return _generation_systems
@staticmethod
def _search_storage_equipment(storage_systems, storage_id):
_storage_systems = []
for storage in storage_systems:
if storage.id in storage_id:
_storage_systems.append(storage)
if len(_storage_systems) == 0:
_storage_systems = None
raise ValueError(f'The system with the following id is not found in catalog [{storage_id}]')
return _storage_systems
def names(self, category=None):
"""
Get the catalog elements names
:parm: optional category filter
"""
if category is None:
_names = {'archetypes': [], 'systems': [], 'generation_equipments': [], 'storage_equipments': []}
for archetype in self._content.archetypes:
_names['archetypes'].append(archetype.name)
for system in self._content.systems:
_names['systems'].append(system.name)
for equipment in self._content.generation_equipments:
_names['generation_equipments'].append(equipment.name)
else:
_names = {category: []}
if category.lower() == 'archetypes':
for archetype in self._content.archetypes:
_names[category].append(archetype.name)
elif category.lower() == 'systems':
for system in self._content.systems:
_names[category].append(system.name)
elif category.lower() == 'generation_equipments':
for system in self._content.generation_equipments:
_names[category].append(system.name)
else:
raise ValueError(f'Unknown category [{category}]')
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: optional category filter
"""
if category is None:
return self._content
if category.lower() == 'archetypes':
return self._content.archetypes
if category.lower() == 'systems':
return self._content.systems
if category.lower() == 'generation_equipments':
return self._content.generation_equipments
raise ValueError(f'Unknown category [{category}]')
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for entry in self._content.archetypes:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.systems:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.generation_equipments:
if entry.name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,49 @@
"""
Energy Systems catalog factory, publish the energy systems information
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from pathlib import Path
from typing import TypeVar
from hub.catalog_factories.energy_systems.montreal_custom_catalog import MontrealCustomCatalog
from hub.catalog_factories.energy_systems.montreal_future_system_catalogue import MontrealFutureSystemCatalogue
from hub.helpers.utils import validate_import_export_type
Catalog = TypeVar('Catalog')
class EnergySystemsCatalogFactory:
"""
Energy system catalog factory class
"""
def __init__(self, handler, base_path=None):
if base_path is None:
base_path = Path(Path(__file__).parent.parent / 'data/energy_systems')
self._handler = '_' + handler.lower()
validate_import_export_type(EnergySystemsCatalogFactory, handler)
self._path = base_path
@property
def _montreal_custom(self):
"""
Retrieve NRCAN catalog
"""
return MontrealCustomCatalog(self._path)
@property
def _montreal_future(self):
"""
Retrieve North American catalog
"""
return MontrealFutureSystemCatalogue(self._path)
@property
def catalog(self) -> Catalog:
"""
Enrich the city given to the class using the class given handler
:return: Catalog
"""
return getattr(self, self._handler, lambda: None)

View File

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="greenerycatalog" nsURI="http://ca.concordia/greenerycatalog"
nsPrefix="greenery">
<eClassifiers xsi:type="ecore:EClass" name="Soil">
<eAnnotations source="http://www.eclipse.org/emf/2002/Ecore">
<details key="constraints" value="nonNegative"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral=""/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="roughness" eType="#//Roughness"
defaultValueLiteral="MediumRough">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="defines the relative roughness of a particular material layer. This parameter only influences the convection coefficients, more specifically the exterior convection coefficient. A nominal value is expected in with the options being &quot;VeryRough&quot;, &quot;Rough&quot;, &quot;MediumRough&quot;, &quot;MediumSmooth&quot;, &quot;Smooth&quot;, and &quot;VerySmooth&quot; in order of roughest to smoothest options."/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="conductivityOfDrySoil"
lowerBound="1" eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="1.0 W/(m*K)">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Thermal conductivity W/(m-K). Range: 0.2 to 1.5"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.2"/>
<details key="max" value="1.5"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="densityOfDrySoil" eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="1100 kg/m³">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Density in kg/m³. "/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.1"/>
<details key="max" value="10000"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="specificHeatOfDrySoil"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="1200 J/(kg*K)">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="in units of J/(kg-K). Range(300 to 2000)"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.2"/>
<details key="max" value="2000"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="thermalAbsorptance" eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.9">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="the fraction of incident long wavelength (>2.5 microns) radiation that is absorbed by the material. This parameter is used when calculating the long wavelength radiant exchange between various surfaces and affects the surface heat balances (both inside and outside as appropriate). For long wavelength radiant exchange, thermal emissivity and thermal emittance are equal to thermal absorptance. Range: 0 to 1"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.0"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="solarAbsorptance" eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.7">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="the fraction of incident solar radiation that is absorbed by the material.&#xA;Solar radiation (0.3 to 2.537 microns) includes the visible spectrum as well as infrared and ultraviolet wavelengths. This parameter is used when calculating the amount of incident solar radiation absorbed by various surfaces and affects the surface heat balances (both inside and outside as appropriate). If solar reflectance (or reflectivity) data is available, then absorptance is equal to 1.0 minus reflectance (for opaque materials).&#xA;Range: 0 to 1"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.0"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="visibleAbsorptance" eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.75">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="The visible absorptance field in the Material input syntax represents the fraction of incident visible wavelength radiation that is absorbed by the material.&#xA;Visible wavelength radiation (0.37 to 0.78 microns weighted by photopic response) is slightly different than solar radiation in that the visible band of wavelengths is much more narrow while solar radiation includes the visible spectrum as well as infrared and ultraviolet wavelengths.&#xA;This parameter is used when calculating the amount of incident visible radiation absorbed by various surfaces and affects the surface heat balances (both inside and outside as appropriate) as well as the daylighting calculations. If visible reflectance (or reflectivity) data is available, then absorptance is equal to 1.0 minus reflectance (for opaque materials).&#xA;Range: 0.5 to 1.0"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.5"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="saturationVolumetricMoistureContent"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.0">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="input of the saturation moisture content of the soil layer. Range: 0.1 to 0.5"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.1"/>
<details key="max" value="0.5"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="residualVolumetricMoistureContent"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.05">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Input of the residual moisture content of the soil layer. Range: 0.01 to 0.1"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.01"/>
<details key="max" value="0.1"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="initialVolumetricMoistureContent"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.1">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Range: 0.05 to 0.5"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.05"/>
<details key="max" value="0.5"/>
</eAnnotations>
</eStructuralFeatures>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Plant">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="height" lowerBound="1"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.1 m">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Maximum height of plants. Range: 0.005 to 1 m"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.005"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="leafAreaIndex" lowerBound="1"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="2.5">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Leaf area per unit area of soil surface. Range: 0.001 to 5.0 m²/m²"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.001"/>
<details key="max" value="5.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="leafReflectivity" lowerBound="1"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.1">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Represents the fraction of incident solar radiation that is reflected by the individual leaf surfaces (albedo).&#xA;Range: 0.05 to 0.5"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.05"/>
<details key="max" value="0.5"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="leafEmissivity" lowerBound="1"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.9">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Ratio of thermal radiation emitted from leaf surfaces to that emitted by an ideal black body at the same temperature.&#xA;This parameter is used when calculating the long wavelength radiant exchange at the leaf surfaces.&#xA;Range: 0.8 to 1"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.8"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="minimalStomatalResistance"
lowerBound="1" eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="100.0 s/m">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Resistance of the plants to moisture transport.&#xA;Plants with low values of stomatal resistance will result in higher evapotranspiration rates than plants with high resistance.&#xA;Range: 50 to 300 m/s"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="50.0"/>
<details key="max" value="300.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EReference" name="growsOn" lowerBound="1"
upperBound="-1" eType="#//Soil"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="co2Sequestration" eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="kgCO₂eq"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="SupportEnvelope">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="roughness" eType="#//Roughness"
defaultValueLiteral="MediumRough"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="solarAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="conductivity" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="visibleAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="specificHeat" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="density" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="thermalAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="GreeneryCatalog">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="description" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="source" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="plantCategories" lowerBound="1"
upperBound="-1" eType="#//PlantCategory" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="vegetationCategories" lowerBound="1"
upperBound="-1" eType="#//VegetationCategory" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="soils" upperBound="-1"
eType="#//Soil" containment="true"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="PlantCategory">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Excluding (that is non-overlapping) categories like Trees, Hedeges, Grasses that help users finding a specific biol. plant species."/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="plants" upperBound="-1"
eType="#//Plant" containment="true"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="IrrigationSchedule">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Vegetation">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Plant life or total plant cover (as of an area)"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="thicknessOfSoil" lowerBound="1"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="20 cm">
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0"/>
<details key="max" value="1000"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EReference" name="soil" lowerBound="1" eType="#//Soil"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="plants" lowerBound="1"
upperBound="-1" eType="#//PlantPercentage" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="management" lowerBound="1"
eType="#//Management" defaultValueLiteral="NA"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="airGap" lowerBound="1"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="0.0 cm"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="VegetationCategory">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Excluding (that is non-overlapping) categories to help users finding a specific vegetation template."/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EReference" name="vegetationTemplates" upperBound="-1"
eType="#//Vegetation" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EEnum" name="Management">
<eLiterals name="Intensive"/>
<eLiterals name="Extensive" value="1"/>
<eLiterals name="SemiIntensive" value="2"/>
<eLiterals name="NA" value="3"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="PlantPercentage">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="percentage" lowerBound="1"
eType="ecore:EDataType platform:/plugin/de.hftstuttgart.cityunits.model/model/Quantities.ecore#//Quantity"
defaultValueLiteral="100">
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0"/>
<details key="max" value="100"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EReference" name="plant" lowerBound="1" eType="#//Plant"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EEnum" name="Roughness">
<eLiterals name="VeryRough"/>
<eLiterals name="Rough" value="1"/>
<eLiterals name="MediumRough" value="2"/>
<eLiterals name="MediumSmooth" value="3"/>
<eLiterals name="Smooth" value="4"/>
<eLiterals name="VerySmooth" value="5"/>
</eClassifiers>
</ecore:EPackage>

View File

@ -0,0 +1,318 @@
"""Definition of meta model 'greenerycatalog'."""
from functools import partial
import pyecore.ecore as Ecore
from pyecore.ecore import *
name = 'greenerycatalog'
nsURI = 'http://ca.concordia/greenerycatalog'
nsPrefix = 'greenery'
eClass = EPackage(name=name, nsURI=nsURI, nsPrefix=nsPrefix)
eClassifiers = {}
getEClassifier = partial(Ecore.getEClassifier, searchspace=eClassifiers)
Management = EEnum('Management', literals=['Intensive', 'Extensive', 'SemiIntensive', 'NA'])
Roughness = EEnum('Roughness', literals=['VeryRough', 'Rough',
'MediumRough', 'MediumSmooth', 'Smooth', 'VerySmooth'])
class Soil(EObject, metaclass=MetaEClass):
name = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
roughness = EAttribute(eType=Roughness, unique=True, derived=False,
changeable=True, default_value=Roughness.MediumRough)
conductivityOfDrySoil = EAttribute(
eType=EString, unique=True, derived=False, changeable=True, default_value='1.0 W/(m*K)')
densityOfDrySoil = EAttribute(eType=EString, unique=True, derived=False,
changeable=True, default_value='1100 kg/m³')
specificHeatOfDrySoil = EAttribute(
eType=EString, unique=True, derived=False, changeable=True, default_value='1200 J/(kg*K)')
thermalAbsorptance = EAttribute(eType=EString, unique=True,
derived=False, changeable=True, default_value='0.9')
solarAbsorptance = EAttribute(eType=EString, unique=True,
derived=False, changeable=True, default_value='0.7')
visibleAbsorptance = EAttribute(eType=EString, unique=True,
derived=False, changeable=True, default_value='0.75')
saturationVolumetricMoistureContent = EAttribute(
eType=EString, unique=True, derived=False, changeable=True, default_value='0.0')
residualVolumetricMoistureContent = EAttribute(
eType=EString, unique=True, derived=False, changeable=True, default_value='0.05')
initialVolumetricMoistureContent = EAttribute(
eType=EString, unique=True, derived=False, changeable=True, default_value='0.1')
def __init__(self, *, name=None, roughness=None, conductivityOfDrySoil=None, densityOfDrySoil=None, specificHeatOfDrySoil=None, thermalAbsorptance=None, solarAbsorptance=None, visibleAbsorptance=None, saturationVolumetricMoistureContent=None, residualVolumetricMoistureContent=None, initialVolumetricMoistureContent=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if name is not None:
self.name = name
if roughness is not None:
self.roughness = roughness
if conductivityOfDrySoil is not None:
self.conductivityOfDrySoil = conductivityOfDrySoil
if densityOfDrySoil is not None:
self.densityOfDrySoil = densityOfDrySoil
if specificHeatOfDrySoil is not None:
self.specificHeatOfDrySoil = specificHeatOfDrySoil
if thermalAbsorptance is not None:
self.thermalAbsorptance = thermalAbsorptance
if solarAbsorptance is not None:
self.solarAbsorptance = solarAbsorptance
if visibleAbsorptance is not None:
self.visibleAbsorptance = visibleAbsorptance
if saturationVolumetricMoistureContent is not None:
self.saturationVolumetricMoistureContent = saturationVolumetricMoistureContent
if residualVolumetricMoistureContent is not None:
self.residualVolumetricMoistureContent = residualVolumetricMoistureContent
if initialVolumetricMoistureContent is not None:
self.initialVolumetricMoistureContent = initialVolumetricMoistureContent
class Plant(EObject, metaclass=MetaEClass):
name = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
height = EAttribute(eType=EString, unique=True, derived=False,
changeable=True, default_value='0.1 m')
leafAreaIndex = EAttribute(eType=EString, unique=True, derived=False,
changeable=True, default_value='2.5')
leafReflectivity = EAttribute(eType=EString, unique=True,
derived=False, changeable=True, default_value='0.1')
leafEmissivity = EAttribute(eType=EString, unique=True, derived=False,
changeable=True, default_value='0.9')
minimalStomatalResistance = EAttribute(
eType=EString, unique=True, derived=False, changeable=True, default_value='100.0 s/m')
co2Sequestration = EAttribute(eType=EString, unique=True, derived=False,
changeable=True, default_value='kgCO₂eq')
growsOn = EReference(ordered=True, unique=True, containment=False, derived=False, upper=-1)
def __init__(self, *, name=None, height=None, leafAreaIndex=None, leafReflectivity=None, leafEmissivity=None, minimalStomatalResistance=None, growsOn=None, co2Sequestration=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if name is not None:
self.name = name
if height is not None:
self.height = height
if leafAreaIndex is not None:
self.leafAreaIndex = leafAreaIndex
if leafReflectivity is not None:
self.leafReflectivity = leafReflectivity
if leafEmissivity is not None:
self.leafEmissivity = leafEmissivity
if minimalStomatalResistance is not None:
self.minimalStomatalResistance = minimalStomatalResistance
if co2Sequestration is not None:
self.co2Sequestration = co2Sequestration
if growsOn:
self.growsOn.extend(growsOn)
class SupportEnvelope(EObject, metaclass=MetaEClass):
roughness = EAttribute(eType=Roughness, unique=True, derived=False,
changeable=True, default_value=Roughness.MediumRough)
solarAbsorptance = EAttribute(eType=EDouble, unique=True,
derived=False, changeable=True, default_value=0.0)
conductivity = EAttribute(eType=EDouble, unique=True, derived=False,
changeable=True, default_value=0.0)
visibleAbsorptance = EAttribute(eType=EDouble, unique=True,
derived=False, changeable=True, default_value=0.0)
specificHeat = EAttribute(eType=EDouble, unique=True, derived=False,
changeable=True, default_value=0.0)
density = EAttribute(eType=EDouble, unique=True, derived=False,
changeable=True, default_value=0.0)
thermalAbsorptance = EAttribute(eType=EDouble, unique=True,
derived=False, changeable=True, default_value=0.0)
def __init__(self, *, roughness=None, solarAbsorptance=None, conductivity=None, visibleAbsorptance=None, specificHeat=None, density=None, thermalAbsorptance=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if roughness is not None:
self.roughness = roughness
if solarAbsorptance is not None:
self.solarAbsorptance = solarAbsorptance
if conductivity is not None:
self.conductivity = conductivity
if visibleAbsorptance is not None:
self.visibleAbsorptance = visibleAbsorptance
if specificHeat is not None:
self.specificHeat = specificHeat
if density is not None:
self.density = density
if thermalAbsorptance is not None:
self.thermalAbsorptance = thermalAbsorptance
class GreeneryCatalog(EObject, metaclass=MetaEClass):
name = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
description = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
source = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
plantCategories = EReference(ordered=True, unique=True,
containment=True, derived=False, upper=-1)
vegetationCategories = EReference(ordered=True, unique=True,
containment=True, derived=False, upper=-1)
soils = EReference(ordered=True, unique=True, containment=True, derived=False, upper=-1)
def __init__(self, *, name=None, description=None, source=None, plantCategories=None, vegetationCategories=None, soils=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if name is not None:
self.name = name
if description is not None:
self.description = description
if source is not None:
self.source = source
if plantCategories:
self.plantCategories.extend(plantCategories)
if vegetationCategories:
self.vegetationCategories.extend(vegetationCategories)
if soils:
self.soils.extend(soils)
class PlantCategory(EObject, metaclass=MetaEClass):
"""Excluding (that is non-overlapping) categories like Trees, Hedeges, Grasses that help users finding a specific biol. plant species."""
name = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
plants = EReference(ordered=True, unique=True, containment=True, derived=False, upper=-1)
def __init__(self, *, name=None, plants=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if name is not None:
self.name = name
if plants:
self.plants.extend(plants)
class IrrigationSchedule(EObject, metaclass=MetaEClass):
name = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
def __init__(self, *, name=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if name is not None:
self.name = name
class Vegetation(EObject, metaclass=MetaEClass):
"""Plant life or total plant cover (as of an area)"""
name = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
thicknessOfSoil = EAttribute(eType=EString, unique=True, derived=False,
changeable=True, default_value='20 cm')
management = EAttribute(eType=Management, unique=True, derived=False,
changeable=True, default_value=Management.NA)
airGap = EAttribute(eType=EString, unique=True, derived=False,
changeable=True, default_value='0.0 cm')
soil = EReference(ordered=True, unique=True, containment=False, derived=False)
plants = EReference(ordered=True, unique=True, containment=True, derived=False, upper=-1)
def __init__(self, *, name=None, thicknessOfSoil=None, soil=None, plants=None, management=None, airGap=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if name is not None:
self.name = name
if thicknessOfSoil is not None:
self.thicknessOfSoil = thicknessOfSoil
if management is not None:
self.management = management
if airGap is not None:
self.airGap = airGap
if soil is not None:
self.soil = soil
if plants:
self.plants.extend(plants)
class VegetationCategory(EObject, metaclass=MetaEClass):
"""Excluding (that is non-overlapping) categories to help users finding a specific vegetation template."""
name = EAttribute(eType=EString, unique=True, derived=False, changeable=True)
vegetationTemplates = EReference(ordered=True, unique=True,
containment=True, derived=False, upper=-1)
def __init__(self, *, vegetationTemplates=None, name=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if name is not None:
self.name = name
if vegetationTemplates:
self.vegetationTemplates.extend(vegetationTemplates)
class PlantPercentage(EObject, metaclass=MetaEClass):
percentage = EAttribute(eType=EString, unique=True, derived=False,
changeable=True, default_value='100')
plant = EReference(ordered=True, unique=True, containment=False, derived=False)
def __init__(self, *, percentage=None, plant=None):
# if kwargs:
# raise AttributeError('unexpected arguments: {}'.format(kwargs))
super().__init__()
if percentage is not None:
self.percentage = percentage
if plant is not None:
self.plant = plant

View File

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="greenerycatalog" nsURI="http://ca.concordia/greenerycatalog"
nsPrefix="greenery">
<eClassifiers xsi:type="ecore:EClass" name="Soil">
<eAnnotations source="http://www.eclipse.org/emf/2002/Ecore">
<details key="constraints" value="nonNegative"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral=""/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="roughness" eType="#//Roughness"
defaultValueLiteral="MediumRough">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="defines the relative roughness of a particular material layer. This parameter only influences the convection coefficients, more specifically the exterior convection coefficient. A nominal value is expected in with the options being &quot;VeryRough&quot;, &quot;Rough&quot;, &quot;MediumRough&quot;, &quot;MediumSmooth&quot;, &quot;Smooth&quot;, and &quot;VerySmooth&quot; in order of roughest to smoothest options."/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="conductivityOfDrySoil"
lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="1.0 W/(m*K)">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Thermal conductivity W/(m-K). Range: 0.2 to 1.5"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.2"/>
<details key="max" value="1.5"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="densityOfDrySoil" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="1100 kg/m³">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Density in kg/m³. "/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.1"/>
<details key="max" value="10000"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="specificHeatOfDrySoil"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="1200 J/(kg*K)">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="in units of J/(kg-K). Range(300 to 2000)"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.2"/>
<details key="max" value="2000"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="thermalAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.9">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="the fraction of incident long wavelength (>2.5 microns) radiation that is absorbed by the material. This parameter is used when calculating the long wavelength radiant exchange between various surfaces and affects the surface heat balances (both inside and outside as appropriate). For long wavelength radiant exchange, thermal emissivity and thermal emittance are equal to thermal absorptance. Range: 0 to 1"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.0"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="solarAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.7">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="the fraction of incident solar radiation that is absorbed by the material.&#xA;Solar radiation (0.3 to 2.537 microns) includes the visible spectrum as well as infrared and ultraviolet wavelengths. This parameter is used when calculating the amount of incident solar radiation absorbed by various surfaces and affects the surface heat balances (both inside and outside as appropriate). If solar reflectance (or reflectivity) data is available, then absorptance is equal to 1.0 minus reflectance (for opaque materials).&#xA;Range: 0 to 1"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.0"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="visibleAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.75">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="The visible absorptance field in the Material input syntax represents the fraction of incident visible wavelength radiation that is absorbed by the material.&#xA;Visible wavelength radiation (0.37 to 0.78 microns weighted by photopic response) is slightly different than solar radiation in that the visible band of wavelengths is much more narrow while solar radiation includes the visible spectrum as well as infrared and ultraviolet wavelengths.&#xA;This parameter is used when calculating the amount of incident visible radiation absorbed by various surfaces and affects the surface heat balances (both inside and outside as appropriate) as well as the daylighting calculations. If visible reflectance (or reflectivity) data is available, then absorptance is equal to 1.0 minus reflectance (for opaque materials).&#xA;Range: 0.5 to 1.0"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.5"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="saturationVolumetricMoistureContent"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.0">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="input of the saturation moisture content of the soil layer. Range: 0.1 to 0.5"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.1"/>
<details key="max" value="0.5"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="residualVolumetricMoistureContent"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.05">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Input of the residual moisture content of the soil layer. Range: 0.01 to 0.1"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.01"/>
<details key="max" value="0.1"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="initialVolumetricMoistureContent"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.1">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Range: 0.05 to 0.5"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.05"/>
<details key="max" value="0.5"/>
</eAnnotations>
</eStructuralFeatures>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Plant">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="height" lowerBound="1"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.1 m">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Maximum height of plants. Range: 0.005 to 1 m"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.005"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="leafAreaIndex" lowerBound="1"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="2.5">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Leaf area per unit area of soil surface. Range: 0.001 to 5.0 m²/m²"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.001"/>
<details key="max" value="5.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="leafReflectivity" lowerBound="1"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.1">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Represents the fraction of incident solar radiation that is reflected by the individual leaf surfaces (albedo).&#xA;Range: 0.05 to 0.5"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.05"/>
<details key="max" value="0.5"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="leafEmissivity" lowerBound="1"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.9">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Ratio of thermal radiation emitted from leaf surfaces to that emitted by an ideal black body at the same temperature.&#xA;This parameter is used when calculating the long wavelength radiant exchange at the leaf surfaces.&#xA;Range: 0.8 to 1"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0.8"/>
<details key="max" value="1.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="minimalStomatalResistance"
lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="100.0 s/m">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Resistance of the plants to moisture transport.&#xA;Plants with low values of stomatal resistance will result in higher evapotranspiration rates than plants with high resistance.&#xA;Range: 50 to 300 m/s"/>
</eAnnotations>
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="50.0"/>
<details key="max" value="300.0"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EReference" name="growsOn" lowerBound="1"
upperBound="-1" eType="#//Soil"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="co2Sequestration" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="kgCO₂eq"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="SupportEnvelope">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="roughness" eType="#//Roughness"
defaultValueLiteral="MediumRough"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="solarAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="conductivity" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="visibleAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="specificHeat" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="density" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="thermalAbsorptance" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EDouble"
defaultValueLiteral="0.0"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="GreeneryCatalog">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="description" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="source" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="plantCategories" lowerBound="1"
upperBound="-1" eType="#//PlantCategory" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="vegetationCategories" lowerBound="1"
upperBound="-1" eType="#//VegetationCategory" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="soils" upperBound="-1"
eType="#//Soil" containment="true"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="PlantCategory">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Excluding (that is non-overlapping) categories like Trees, Hedeges, Grasses that help users finding a specific biol. plant species."/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="plants" upperBound="-1"
eType="#//Plant" containment="true"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="IrrigationSchedule">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="Vegetation">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Plant life or total plant cover (as of an area)"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" lowerBound="1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="thicknessOfSoil" lowerBound="1"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="20 cm">
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0"/>
<details key="max" value="1000"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EReference" name="soil" lowerBound="1" eType="#//Soil"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="plants" lowerBound="1"
upperBound="-1" eType="#//PlantPercentage" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="management" lowerBound="1"
eType="#//Management" defaultValueLiteral="NA"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="airGap" lowerBound="1"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="0.0 cm"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="VegetationCategory">
<eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
<details key="documentation" value="Excluding (that is non-overlapping) categories to help users finding a specific vegetation template."/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EReference" name="vegetationTemplates" upperBound="-1"
eType="#//Vegetation" containment="true"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EEnum" name="Management">
<eLiterals name="Intensive"/>
<eLiterals name="Extensive" value="1"/>
<eLiterals name="SemiIntensive" value="2"/>
<eLiterals name="NA" value="3"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EClass" name="PlantPercentage">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="percentage" lowerBound="1"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"
defaultValueLiteral="100">
<eAnnotations source="http://www.hft-stuttgart.de/UomQuantities">
<details key="min" value="0"/>
<details key="max" value="100"/>
</eAnnotations>
</eStructuralFeatures>
<eStructuralFeatures xsi:type="ecore:EReference" name="plant" lowerBound="1" eType="#//Plant"/>
</eClassifiers>
<eClassifiers xsi:type="ecore:EEnum" name="Roughness">
<eLiterals name="VeryRough"/>
<eLiterals name="Rough" value="1"/>
<eLiterals name="MediumRough" value="2"/>
<eLiterals name="MediumSmooth" value="3"/>
<eLiterals name="Smooth" value="4"/>
<eLiterals name="VerySmooth" value="5"/>
</eClassifiers>
</ecore:EPackage>

View File

@ -0,0 +1,122 @@
"""
Greenery catalog
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
from pyecore.resources import ResourceSet, URI
from hub.catalog_factories.greenery.ecore_greenery.greenerycatalog import GreeneryCatalog as Gc
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.greenery.vegetation import Vegetation as HubVegetation
from hub.catalog_factories.data_models.greenery.plant import Plant as HubPlant
from hub.catalog_factories.data_models.greenery.soil import Soil as HubSoil
from hub.catalog_factories.data_models.greenery.plant_percentage import PlantPercentage as HubPlantPercentage
from hub.catalog_factories.data_models.greenery.content import Content as GreeneryContent
class GreeneryCatalog(Catalog):
"""
Greenery catalog class
"""
def __init__(self, path):
base_path = Path(Path(__file__).parent / 'ecore_greenery/greenerycatalog_no_quantities.ecore').resolve()
resource_set = ResourceSet()
data_model = resource_set.get_resource(URI(str(base_path)))
data_model_root = data_model.contents[0]
resource_set.metamodel_registry[data_model_root.nsURI] = data_model_root
resource = resource_set.get_resource(URI(str(path)))
catalog_data: Gc = resource.contents[0]
plants = []
for plant_category in catalog_data.plantCategories:
name = plant_category.name
for plant in plant_category.plants:
plants.append(HubPlant(name, plant))
vegetations = []
for vegetation_category in catalog_data.vegetationCategories:
name = vegetation_category.name
for vegetation in vegetation_category.vegetationTemplates:
plant_percentages = []
for plant_percentage in vegetation.plants:
plant_category = "Unknown"
for plant in plants:
if plant.name == plant_percentage.plant.name:
plant_category = plant.category
break
plant_percentages.append(
HubPlantPercentage(plant_percentage.percentage, plant_category, plant_percentage.plant)
)
vegetations.append(HubVegetation(name, vegetation, plant_percentages))
plants = []
for plant_category in catalog_data.plantCategories:
name = plant_category.name
for plant in plant_category.plants:
plants.append(HubPlant(name, plant))
soils = []
for soil in catalog_data.soils:
soils.append(HubSoil(soil))
self._content = GreeneryContent(vegetations, plants, soils)
def names(self, category=None):
"""
Get the catalog elements names
:parm: optional category filter
"""
if category is None:
_names = {'vegetations': [], 'plants': [], 'soils': []}
for vegetation in self._content.vegetations:
_names['vegetations'].append(vegetation.name)
for plant in self._content.plants:
_names['plants'].append(plant.name)
for soil in self._content.soils:
_names['soils'].append(soil.name)
else:
_names = {category: []}
if category.lower() == 'vegetations':
for vegetation in self._content.vegetations:
_names[category].append(vegetation.name)
elif category.lower() == 'plants':
for plant in self._content.plants:
_names[category].append(plant.name)
elif category.lower() == 'soils':
for soil in self._content.soils:
_names[category].append(soil.name)
else:
raise ValueError(f'Unknown category [{category}]')
return _names
def get_entry(self, name):
"""
Get one complete entry from the greenery catalog
"""
for entry in self._content.vegetations:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.plants:
if entry.name.lower() == name.lower():
return entry
for entry in self._content.soils:
if entry.name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")
def entries(self, category=None):
"""
Get all entries from the greenery catalog optionally filtered by category
"""
if category is None:
return self._content
if category.lower() == 'vegetations':
return self._content.vegetations
if category.lower() == 'plants':
return self._content.plants
if category.lower() == 'soils':
return self._content.soils
raise ValueError(f'Unknown category [{category}]')

View File

@ -0,0 +1,40 @@
"""
Greenery catalog publish the greenery information
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
from typing import TypeVar
from hub.catalog_factories.greenery.greenery_catalog import GreeneryCatalog
Catalog = TypeVar('Catalog')
class GreeneryCatalogFactory:
"""
GreeneryCatalogFactory class
"""
def __init__(self, handler, base_path=None):
if base_path is None:
base_path = (Path(__file__).parent.parent / 'data/greenery').resolve()
self._handler = '_' + handler.lower()
self._path = base_path
@property
def _nrel(self):
"""
Return a greenery catalog based in NREL using ecore as datasource
:return: GreeneryCatalog
"""
return GreeneryCatalog((self._path / 'ecore_greenery_catalog.xml').resolve())
@property
def catalog(self) -> Catalog:
"""
Enrich the city given to the class using the class given handler
:return: Catalog
"""
return getattr(self, self._handler, lambda: None)

View File

View File

@ -0,0 +1,236 @@
"""
Comnet usage catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import io
from typing import Dict
import pandas as pd
import hub.helpers.constants as cte
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.usages.appliances import Appliances
from hub.catalog_factories.data_models.usages.content import Content
from hub.catalog_factories.data_models.usages.lighting import Lighting
from hub.catalog_factories.data_models.usages.occupancy import Occupancy
from hub.catalog_factories.data_models.usages.domestic_hot_water import DomesticHotWater
from hub.catalog_factories.data_models.usages.schedule import Schedule
from hub.catalog_factories.data_models.usages.thermal_control import ThermalControl
from hub.catalog_factories.data_models.usages.usage import Usage
from hub.catalog_factories.usage.usage_helper import UsageHelper
from hub.helpers.configuration_helper import ConfigurationHelper as ch
class ComnetCatalog(Catalog):
"""
Comnet catalog class
"""
def __init__(self, path):
self._comnet_archetypes_path = str(path / 'comnet_archetypes.xlsx')
self._comnet_schedules_path = str(path / 'comnet_schedules_archetypes.xlsx')
self._archetypes = self._read_archetype_file()
self._schedules = self._read_schedules_file()
sensible_convective = ch().comnet_occupancy_sensible_convective
sensible_radiative = ch().comnet_occupancy_sensible_radiant
lighting_convective = ch().comnet_lighting_convective
lighting_radiative = ch().comnet_lighting_radiant
lighting_latent = ch().comnet_lighting_latent
appliances_convective = ch().comnet_plugs_convective
appliances_radiative = ch().comnet_plugs_radiant
appliances_latent = ch().comnet_plugs_latent
usages = []
for schedule_key in self._archetypes['schedules_key']:
comnet_usage = schedule_key
schedule_name = self._archetypes['schedules_key'][schedule_key]
hours_day = None
days_year = None
occupancy_archetype = self._archetypes['occupancy'][comnet_usage]
lighting_archetype = self._archetypes['lighting'][comnet_usage]
appliances_archetype = self._archetypes['plug loads'][comnet_usage]
mechanical_air_change = None # comnet provides ventilation rate only
ventilation_rate = self._archetypes['ventilation rate'][comnet_usage]
# convert cfm/ft2 to m3/m2.s
ventilation_rate = ventilation_rate / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS)
domestic_hot_water_archetype = self._archetypes['water heating'][comnet_usage]
# get occupancy
occupancy_density = occupancy_archetype[0] / pow(cte.METERS_TO_FEET, 2)
sensible_heat_gain = occupancy_archetype[1] * cte.BTU_H_TO_WATTS
latent_heat_gain = occupancy_archetype[1] * cte.BTU_H_TO_WATTS
if occupancy_density != 0:
occupancy_density = 1 / occupancy_density
sensible_convective_internal_gain = occupancy_density * sensible_heat_gain * sensible_convective
sensible_radiative_internal_gain = occupancy_density * sensible_heat_gain * sensible_radiative
latent_internal_gain = occupancy_density * latent_heat_gain
occupancy = Occupancy(occupancy_density,
sensible_convective_internal_gain,
sensible_radiative_internal_gain,
latent_internal_gain,
self._schedules[schedule_name]['Occupancy'])
# get lighting
density = lighting_archetype[4] * pow(cte.METERS_TO_FEET, 2)
lighting = Lighting(density,
lighting_convective,
lighting_radiative,
lighting_latent,
self._schedules[schedule_name]['Lights'])
# get appliances
density = appliances_archetype[0]
if density == 'n.a.':
density = 0
# convert W/ft2 to W/m2
density = float(density) * pow(cte.METERS_TO_FEET, 2)
appliances = Appliances(density,
appliances_convective,
appliances_radiative,
appliances_latent,
self._schedules[schedule_name]['Receptacle'])
# get thermal control
thermal_control = ThermalControl(None,
None,
None,
self._schedules[schedule_name]['HVAC Avail'],
self._schedules[schedule_name]['HtgSetPt'],
self._schedules[schedule_name]['ClgSetPt']
)
# get domestic hot water
density = domestic_hot_water_archetype
# convert Btu/h/occ to W/m2
density = float(density) * cte.BTU_H_TO_WATTS * occupancy_density
domestic_hot_water_service_temperature = self._schedules[schedule_name]['WtrHtrSetPt'][0].values[0]
domestic_hot_water = DomesticHotWater(density,
None,
domestic_hot_water_service_temperature,
self._schedules[schedule_name]['Service Hot Water']
)
usages.append(Usage(comnet_usage,
hours_day,
days_year,
mechanical_air_change,
ventilation_rate,
occupancy,
lighting,
appliances,
thermal_control,
domestic_hot_water))
self._content = Content(usages)
def _read_schedules_file(self) -> Dict:
dictionary = {}
comnet_usages = UsageHelper().comnet_schedules_key_to_comnet_schedules
comnet_days = UsageHelper().comnet_days
comnet_data_types = UsageHelper().comnet_data_type_to_hub_data_type
for usage_name in comnet_usages:
if usage_name == 'C-13 Data Center':
continue
with open(self._comnet_schedules_path, 'rb') as xls:
_extracted_data = pd.read_excel(
io.BytesIO(xls.read()),
sheet_name=comnet_usages[usage_name],
skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA"
)
_schedules = {}
for row in range(0, 39, 3):
_schedule_values = {}
schedule_name = _extracted_data.loc[row:row, 'Description'].item()
schedule_data_type = comnet_data_types[_extracted_data.loc[row:row, 'Type'].item()]
for day in comnet_days:
# Monday to Friday
start = row
end = row + 1
if day == cte.SATURDAY:
start = start + 1
end = end + 1
elif day in (cte.SUNDAY, cte.HOLIDAY):
start = start + 2
end = end + 2
_schedule_values[day] = _extracted_data.iloc[start:end, 3:27].to_numpy().tolist()[0]
_schedule = []
for day in _schedule_values:
if schedule_name in ('ClgSetPt', 'HtgSetPt', 'WtrHtrSetPt'):
# to celsius
if 'n.a.' in _schedule_values[day]:
_schedule_values[day] = None
else:
_schedule_values[day] = [(float(value)-32)*5/9 for value in _schedule_values[day]]
_schedule.append(Schedule(schedule_name, _schedule_values[day], schedule_data_type, cte.HOUR, cte.DAY, [day]))
_schedules[schedule_name] = _schedule
dictionary[usage_name] = _schedules
return dictionary
def _read_archetype_file(self) -> Dict:
"""
reads xlsx files containing usage information into a dictionary
:return : Dict
"""
number_usage_types = 33
with open(self._comnet_archetypes_path, 'rb') as xls:
_extracted_data = pd.read_excel(
io.BytesIO(xls.read()),
sheet_name="Modeling Data",
skiprows=[0, 1, 2, 24],
nrows=number_usage_types, usecols="A:AB"
)
lighting_data = {}
plug_loads_data = {}
occupancy_data = {}
ventilation_rate = {}
water_heating = {}
process_data = {}
schedules_key = {}
for j in range(0, number_usage_types-1):
usage_parameters = _extracted_data.iloc[j]
usage_type = usage_parameters[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()
ventilation_rate[usage_type] = usage_parameters[20:21].item()
water_heating[usage_type] = usage_parameters[23:24].item()
process_data[usage_type] = usage_parameters[24:26].values.tolist()
schedules_key[usage_type] = usage_parameters[27:28].item()
return {'lighting': lighting_data,
'plug loads': plug_loads_data,
'occupancy': occupancy_data,
'ventilation rate': ventilation_rate,
'water heating': water_heating,
'process': process_data,
'schedules_key': schedules_key
}
def names(self, category=None):
"""
Get the catalog elements names
:parm: for usage catalog category filter does nothing as there is only one category (usages)
"""
_names = {'usages': []}
for usage in self._content.usages:
_names['usages'].append(usage.name)
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: for usage catalog category filter does nothing as there is only one category (usages)
"""
return self._content
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for usage in self._content.usages:
if usage.name.lower() == name.lower():
return usage
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,234 @@
"""
Eilat usage catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import io
from typing import Dict
import pandas as pd
import hub.helpers.constants as cte
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.usages.appliances import Appliances
from hub.catalog_factories.data_models.usages.content import Content
from hub.catalog_factories.data_models.usages.lighting import Lighting
from hub.catalog_factories.data_models.usages.occupancy import Occupancy
from hub.catalog_factories.data_models.usages.domestic_hot_water import DomesticHotWater
from hub.catalog_factories.data_models.usages.schedule import Schedule
from hub.catalog_factories.data_models.usages.thermal_control import ThermalControl
from hub.catalog_factories.data_models.usages.usage import Usage
from hub.catalog_factories.usage.usage_helper import UsageHelper
from hub.helpers.configuration_helper import ConfigurationHelper as ch
class EilatCatalog(Catalog):
"""
Eilat catalog class
"""
def __init__(self, path):
self._eilat_archetypes_path = str(path / 'eilat_archetypes.xlsx')
self._eilat_schedules_path = str(path / 'eilat_schedules_archetypes.xlsx')
self._archetypes = self._read_archetype_file()
self._schedules = self._read_schedules_file()
sensible_convective = ch().comnet_occupancy_sensible_convective
sensible_radiative = ch().comnet_occupancy_sensible_radiant
lighting_convective = ch().comnet_lighting_convective
lighting_radiative = ch().comnet_lighting_radiant
lighting_latent = ch().comnet_lighting_latent
appliances_convective = ch().comnet_plugs_convective
appliances_radiative = ch().comnet_plugs_radiant
appliances_latent = ch().comnet_plugs_latent
usages = []
for schedule_key in self._archetypes['schedules_key']:
eilat_usage = schedule_key
schedule_name = self._archetypes['schedules_key'][schedule_key]
hours_day = None
days_year = None
occupancy_archetype = self._archetypes['occupancy'][eilat_usage]
lighting_archetype = self._archetypes['lighting'][eilat_usage]
appliances_archetype = self._archetypes['plug loads'][eilat_usage]
mechanical_air_change = None # eilat provides ventilation rate only
ventilation_rate = self._archetypes['ventilation rate'][eilat_usage]
# convert cfm/ft2 to m3/m2.s
ventilation_rate = ventilation_rate / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS)
domestic_hot_water_archetype = self._archetypes['water heating'][eilat_usage]
# get occupancy
occupancy_density = occupancy_archetype[0] / pow(cte.METERS_TO_FEET, 2)
sensible_heat_gain = occupancy_archetype[1] * cte.BTU_H_TO_WATTS
latent_heat_gain = occupancy_archetype[1] * cte.BTU_H_TO_WATTS
if occupancy_density != 0:
occupancy_density = 1 / occupancy_density
sensible_convective_internal_gain = occupancy_density * sensible_heat_gain * sensible_convective
sensible_radiative_internal_gain = occupancy_density * sensible_heat_gain * sensible_radiative
latent_internal_gain = occupancy_density * latent_heat_gain
occupancy = Occupancy(occupancy_density,
sensible_convective_internal_gain,
sensible_radiative_internal_gain,
latent_internal_gain,
self._schedules[schedule_name]['Occupancy'])
# get lighting
density = lighting_archetype[4] * pow(cte.METERS_TO_FEET, 2)
lighting = Lighting(density,
lighting_convective,
lighting_radiative,
lighting_latent,
self._schedules[schedule_name]['Lights'])
# get appliances
density = appliances_archetype[0]
if density == 'n.a.':
density = 0
# convert W/ft2 to W/m2
density = float(density) * pow(cte.METERS_TO_FEET, 2)
appliances = Appliances(density,
appliances_convective,
appliances_radiative,
appliances_latent,
self._schedules[schedule_name]['Receptacle'])
# get thermal control
thermal_control = ThermalControl(None,
None,
None,
self._schedules[schedule_name]['HVAC Avail'],
self._schedules[schedule_name]['HtgSetPt'],
self._schedules[schedule_name]['ClgSetPt']
)
# get domestic hot water
density = domestic_hot_water_archetype
# convert Btu/h/occ to W/m2
density = float(density) * cte.BTU_H_TO_WATTS * occupancy_density
domestic_hot_water_service_temperature = self._schedules[schedule_name]['WtrHtrSetPt'][0].values[0]
domestic_hot_water = DomesticHotWater(density,
None,
domestic_hot_water_service_temperature,
self._schedules[schedule_name]['Service Hot Water']
)
usages.append(Usage(eilat_usage,
hours_day,
days_year,
mechanical_air_change,
ventilation_rate,
occupancy,
lighting,
appliances,
thermal_control,
domestic_hot_water))
self._content = Content(usages)
def _read_schedules_file(self) -> Dict:
dictionary = {}
eilat_usages = UsageHelper().eilat_schedules_key_to_eilat_schedules
eilat_days = UsageHelper().comnet_days
eilat_data_types = UsageHelper().comnet_data_type_to_hub_data_type
for usage_name in eilat_usages:
with open(self._eilat_schedules_path, 'rb') as xls:
_extracted_data = pd.read_excel(
io.BytesIO(xls.read()),
sheet_name=eilat_usages[usage_name],
skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA"
)
_schedules = {}
for row in range(0, 39, 3):
_schedule_values = {}
schedule_name = _extracted_data.loc[row:row, 'Description'].item()
schedule_data_type = eilat_data_types[_extracted_data.loc[row:row, 'Type'].item()]
for day in eilat_days:
# Monday to Friday
start = row
end = row + 1
if day == cte.FRIDAY:
start = start + 1
end = end + 1
elif day in (cte.SATURDAY, cte.HOLIDAY):
start = start + 2
end = end + 2
_schedule_values[day] = _extracted_data.iloc[start:end, 3:27].to_numpy().tolist()[0]
_schedule = []
for day in _schedule_values:
if schedule_name in ('ClgSetPt', 'HtgSetPt', 'WtrHtrSetPt'):
# to celsius
if 'n.a.' in _schedule_values[day]:
_schedule_values[day] = None
else:
_schedule_values[day] = [(float(value)-32)*5/9 for value in _schedule_values[day]]
_schedule.append(Schedule(schedule_name, _schedule_values[day], schedule_data_type, cte.HOUR, cte.DAY, [day]))
_schedules[schedule_name] = _schedule
dictionary[usage_name] = _schedules
return dictionary
def _read_archetype_file(self) -> Dict:
"""
reads xlsx files containing usage information into a dictionary
:return : Dict
"""
number_usage_types = 3
with open(self._eilat_archetypes_path, 'rb') as xls:
_extracted_data = pd.read_excel(
io.BytesIO(xls.read()),
sheet_name="Modeling Data",
skiprows=[0, 1, 2],
nrows=number_usage_types + 1, usecols="A:AB"
)
lighting_data = {}
plug_loads_data = {}
occupancy_data = {}
ventilation_rate = {}
water_heating = {}
process_data = {}
schedules_key = {}
for j in range(0, number_usage_types):
usage_parameters = _extracted_data.iloc[j]
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()
ventilation_rate[usage_type] = usage_parameters[20:21].item()
water_heating[usage_type] = usage_parameters[23:24].item()
process_data[usage_type] = usage_parameters[24:26].values.tolist()
schedules_key[usage_type] = usage_parameters[27:28].item()
return {'lighting': lighting_data,
'plug loads': plug_loads_data,
'occupancy': occupancy_data,
'ventilation rate': ventilation_rate,
'water heating': water_heating,
'process': process_data,
'schedules_key': schedules_key
}
def names(self, category=None):
"""
Get the catalog elements names
:parm: for usage catalog category filter does nothing as there is only one category (usages)
"""
_names = {'usages': []}
for usage in self._content.usages:
_names['usages'].append(usage.name)
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: for usage catalog category filter does nothing as there is only one category (usages)
"""
return self._content
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for usage in self._content.usages:
if usage.name.lower() == name.lower():
return usage
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,220 @@
"""
NRCAN usage catalog
SPDX - License - Identifier: LGPL - 3.0 - or -later
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 json
import urllib.request
from pathlib import Path
import xmltodict
import hub.helpers.constants as cte
from hub.catalog_factories.catalog import Catalog
from hub.catalog_factories.data_models.usages.appliances import Appliances
from hub.catalog_factories.data_models.usages.content import Content
from hub.catalog_factories.data_models.usages.lighting import Lighting
from hub.catalog_factories.data_models.usages.occupancy import Occupancy
from hub.catalog_factories.data_models.usages.domestic_hot_water import DomesticHotWater
from hub.catalog_factories.data_models.usages.schedule import Schedule
from hub.catalog_factories.data_models.usages.thermal_control import ThermalControl
from hub.catalog_factories.data_models.usages.usage import Usage
from hub.catalog_factories.usage.usage_helper import UsageHelper
class NrcanCatalog(Catalog):
"""
Nrcan catalog class
"""
def __init__(self, path):
self._schedules_path = Path(path / 'nrcan_schedules.json').resolve()
self._space_types_path = Path(path / 'nrcan_space_types.json').resolve()
self._space_compliance_path = Path(path / 'nrcan_space_compliance_2015.json').resolve()
self._content = None
self._schedules = {}
self._load_schedules()
self._content = Content(self._load_archetypes())
@staticmethod
def _extract_schedule(raw):
nrcan_schedule_type = raw['category']
if 'Heating' in raw['name'] and 'Water' not in raw['name']:
nrcan_schedule_type = f'{nrcan_schedule_type} Heating'
elif 'Cooling' in raw['name']:
nrcan_schedule_type = f'{nrcan_schedule_type} Cooling'
if nrcan_schedule_type not in UsageHelper().nrcan_schedule_type_to_hub_schedule_type:
return None
hub_type = UsageHelper().nrcan_schedule_type_to_hub_schedule_type[nrcan_schedule_type]
data_type = UsageHelper().nrcan_data_type_to_hub_data_type[raw['units']]
time_step = UsageHelper().nrcan_time_to_hub_time[raw['type']]
# nrcan only uses daily range for the schedules
time_range = cte.DAY
day_types = UsageHelper().nrcan_day_type_to_hub_days[raw['day_types']]
return Schedule(hub_type, raw['values'], data_type, time_step, time_range, day_types)
def _load_schedules(self):
_schedule_types = []
with open(self._schedules_path, 'r') as f:
schedules_type = json.load(f)
for schedule_type in schedules_type['tables']['schedules']['table']:
schedule = NrcanCatalog._extract_schedule(schedule_type)
if schedule_type['name'] not in _schedule_types:
_schedule_types.append(schedule_type['name'])
if schedule is not None:
self._schedules[schedule_type['name']] = [schedule]
else:
if schedule is not None:
_schedules = self._schedules[schedule_type['name']]
_schedules.append(schedule)
self._schedules[schedule_type['name']] = _schedules
def _get_schedules(self, name):
schedule = None
if name in self._schedules:
schedule = self._schedules[name]
return schedule
def _load_archetypes(self):
usages = []
with open(self._space_types_path, 'r') as f:
space_types = json.load(f)['tables']['space_types']['table']
space_types = [st for st in space_types if st['space_type'] == 'WholeBuilding']
with open(self._space_compliance_path, 'r') as f:
space_types_compliance = json.load(f)['tables']['space_compliance']['table']
space_types_compliance = [st for st in space_types_compliance if st['space_type'] == 'WholeBuilding']
space_types_dictionary = {}
for space_type in space_types_compliance:
usage_type = space_type['building_type']
# people/m2
occupancy_density = space_type['occupancy_per_area_people_per_m2']
# W/m2
lighting_density = space_type['lighting_per_area_w_per_m2']
# W/m2
appliances_density = space_type['electric_equipment_per_area_w_per_m2']
# peak flow in gallons/h/m2
domestic_hot_water_peak_flow = (
space_type['service_water_heating_peak_flow_per_area'] *
cte.GALLONS_TO_QUBIC_METERS / cte.HOUR_TO_SECONDS
)
space_types_dictionary[usage_type] = {'occupancy_per_area': occupancy_density,
'lighting_per_area': lighting_density,
'electric_equipment_per_area': appliances_density,
'service_water_heating_peak_flow_per_area': domestic_hot_water_peak_flow
}
for space_type in space_types:
usage_type = space_type['building_type']
space_type_compliance = space_types_dictionary[usage_type]
occupancy_density = space_type_compliance['occupancy_per_area']
lighting_density = space_type_compliance['lighting_per_area']
appliances_density = space_type_compliance['electric_equipment_per_area']
domestic_hot_water_peak_flow = space_type_compliance['service_water_heating_peak_flow_per_area']
occupancy_schedule_name = space_type['occupancy_schedule']
lighting_schedule_name = space_type['lighting_schedule']
appliance_schedule_name = space_type['electric_equipment_schedule']
hvac_schedule_name = space_type['exhaust_schedule']
if 'FAN' in hvac_schedule_name:
hvac_schedule_name = hvac_schedule_name.replace('FAN', 'Fan')
heating_setpoint_schedule_name = space_type['heating_setpoint_schedule']
cooling_setpoint_schedule_name = space_type['cooling_setpoint_schedule']
domestic_hot_water_schedule_name = space_type['service_water_heating_schedule']
occupancy_schedule = self._get_schedules(occupancy_schedule_name)
lighting_schedule = self._get_schedules(lighting_schedule_name)
appliance_schedule = self._get_schedules(appliance_schedule_name)
heating_schedule = self._get_schedules(heating_setpoint_schedule_name)
cooling_schedule = self._get_schedules(cooling_setpoint_schedule_name)
hvac_availability = self._get_schedules(hvac_schedule_name)
domestic_hot_water_load_schedule = self._get_schedules(domestic_hot_water_schedule_name)
# ACH -> 1/s
mechanical_air_change = space_type['ventilation_air_changes'] / cte.HOUR_TO_SECONDS
# cfm/ft2 to m3/m2.s
ventilation_rate = space_type['ventilation_per_area'] / (cte.METERS_TO_FEET * cte.MINUTES_TO_SECONDS)
# cfm/person to m3/m2.s
ventilation_rate += space_type['ventilation_per_person'] / (
pow(cte.METERS_TO_FEET, 3) * cte.MINUTES_TO_SECONDS
) * occupancy_density
lighting_radiative_fraction = space_type['lighting_fraction_radiant']
lighting_convective_fraction = 0
if lighting_radiative_fraction is not None:
lighting_convective_fraction = 1 - lighting_radiative_fraction
lighting_latent_fraction = 0
appliances_radiative_fraction = space_type['electric_equipment_fraction_radiant']
appliances_latent_fraction = space_type['electric_equipment_fraction_latent']
appliances_convective_fraction = 0
if appliances_radiative_fraction is not None and appliances_latent_fraction is not None:
appliances_convective_fraction = 1 - appliances_radiative_fraction - appliances_latent_fraction
domestic_hot_water_service_temperature = space_type['service_water_heating_target_temperature']
occupancy = Occupancy(occupancy_density,
None,
None,
None,
occupancy_schedule)
lighting = Lighting(lighting_density,
lighting_convective_fraction,
lighting_radiative_fraction,
lighting_latent_fraction,
lighting_schedule)
appliances = Appliances(appliances_density,
appliances_convective_fraction,
appliances_radiative_fraction,
appliances_latent_fraction,
appliance_schedule)
thermal_control = ThermalControl(None,
None,
None,
hvac_availability,
heating_schedule,
cooling_schedule)
domestic_hot_water = DomesticHotWater(None,
domestic_hot_water_peak_flow,
domestic_hot_water_service_temperature,
domestic_hot_water_load_schedule)
hours_day = None
days_year = None
usages.append(Usage(usage_type,
hours_day,
days_year,
mechanical_air_change,
ventilation_rate,
occupancy,
lighting,
appliances,
thermal_control,
domestic_hot_water))
return usages
def names(self, category=None):
"""
Get the catalog elements names
:parm: for usage catalog category filter does nothing as there is only one category (usages)
"""
_names = {'usages': []}
for usage in self._content.usages:
_names['usages'].append(usage.name)
return _names
def entries(self, category=None):
"""
Get the catalog elements
:parm: for usage catalog category filter does nothing as there is only one category (usages)
"""
return self._content
def get_entry(self, name):
"""
Get one catalog element by names
:parm: entry name
"""
for usage in self._content.usages:
if usage.name.lower() == name.lower():
return usage
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -0,0 +1,153 @@
"""
Usage helper
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import Dict
import hub.helpers.constants as cte
class UsageHelper:
"""
Usage helper class
"""
_nrcan_schedule_type_to_hub_schedule_type = {
'Lighting': cte.LIGHTING,
'Occupancy': cte.OCCUPANCY,
'Equipment': cte.APPLIANCES,
'Thermostat Setpoint Cooling': cte.COOLING_SET_POINT,
'Thermostat Setpoint Heating': cte.HEATING_SET_POINT,
'Fan': cte.HVAC_AVAILABILITY,
'Service Water Heating': cte.DOMESTIC_HOT_WATER
}
_nrcan_data_type_to_hub_data_type = {
'FRACTION': cte.FRACTION,
'ON_OFF': cte.ON_OFF,
'TEMPERATURE': cte.ANY_NUMBER
}
_nrcan_time_to_hub_time = {
'Hourly': cte.HOUR,
'Constant': cte.CONSTANT
}
_nrcan_day_type_to_hub_days = {
'Default|Wkdy': [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY],
'Sun|Hol': [cte.SUNDAY, cte.HOLIDAY],
'Sat': [cte.SATURDAY],
'Default|WntrDsn|SmrDsn': [cte.MONDAY,
cte.TUESDAY,
cte.WEDNESDAY,
cte.THURSDAY,
cte.FRIDAY,
cte.SATURDAY,
cte.SUNDAY,
cte.HOLIDAY,
cte.WINTER_DESIGN_DAY,
cte.SUMMER_DESIGN_DAY],
'Default': [cte.MONDAY,
cte.TUESDAY,
cte.WEDNESDAY,
cte.THURSDAY,
cte.FRIDAY,
cte.SATURDAY,
cte.SUNDAY,
cte.HOLIDAY,
cte.WINTER_DESIGN_DAY,
cte.SUMMER_DESIGN_DAY]
}
_comnet_days = [cte.MONDAY,
cte.TUESDAY,
cte.WEDNESDAY,
cte.THURSDAY,
cte.FRIDAY,
cte.SATURDAY,
cte.SUNDAY,
cte.HOLIDAY]
_comnet_data_type_to_hub_data_type = {
'Fraction': cte.FRACTION,
'OnOff': cte.ON_OFF,
'Temperature': cte.ANY_NUMBER
}
_comnet_schedules_key_to_comnet_schedules = {
'C-1 Assembly': 'C-1 Assembly',
'C-2 Public': 'C-2 Health',
'C-3 Hotel Motel': 'C-3 Hotel',
'C-4 Manufacturing': 'C-4 Manufacturing',
'C-5 Office': 'C-5 Office',
'C-7 Restaurant': 'C-7 Restaurant',
'C-8 Retail': 'C-8 Retail',
'C-9 Schools': 'C-9 School',
'C-10 Warehouse': 'C-10 Warehouse',
'C-11 Laboratory': 'C-11 Lab',
'C-12 Residential': 'C-12 Residential',
'C-14 Gymnasium': 'C-14 Gymnasium'
}
_eilat_schedules_key_to_eilat_schedules = {
'C-12 Residential': 'C-12 Residential',
'C-15 Dormitory': 'C-15 Dormitory',
'C-16 Hotel employees': 'C-16 Hotel employees'
}
@property
def nrcan_day_type_to_hub_days(self):
"""
Get a dictionary to convert nrcan day types to hub day types
"""
return self._nrcan_day_type_to_hub_days
@property
def nrcan_schedule_type_to_hub_schedule_type(self):
"""
Get a dictionary to convert nrcan schedule types to hub schedule types
"""
return self._nrcan_schedule_type_to_hub_schedule_type
@property
def nrcan_data_type_to_hub_data_type(self):
"""
Get a dictionary to convert nrcan data types to hub data types
"""
return self._nrcan_data_type_to_hub_data_type
@property
def nrcan_time_to_hub_time(self):
"""
Get a dictionary to convert nrcan time to hub time
"""
return self._nrcan_time_to_hub_time
@property
def comnet_data_type_to_hub_data_type(self) -> Dict:
"""
Get a dictionary to convert comnet data types to hub data types
"""
return self._comnet_data_type_to_hub_data_type
@property
def comnet_schedules_key_to_comnet_schedules(self) -> Dict:
"""
Get a dictionary to convert hub schedules to comnet schedules
"""
return self._comnet_schedules_key_to_comnet_schedules
@property
def comnet_days(self) -> [str]:
"""
Get the list of days used in comnet
"""
return self._comnet_days
@property
def eilat_schedules_key_to_eilat_schedules(self) -> [str]:
"""
Get a dictionary to convert hub schedules to eilat schedules
"""
return self._eilat_schedules_key_to_eilat_schedules

View File

@ -0,0 +1,58 @@
"""
Usage catalog factory, publish the usage information
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
from typing import TypeVar
from hub.catalog_factories.usage.comnet_catalog import ComnetCatalog
from hub.catalog_factories.usage.nrcan_catalog import NrcanCatalog
from hub.catalog_factories.usage.eilat_catalog import EilatCatalog
from hub.helpers.utils import validate_import_export_type
Catalog = TypeVar('Catalog')
class UsageCatalogFactory:
"""
Usage catalog factory class
"""
def __init__(self, handler, base_path=None):
if base_path is None:
base_path = Path(Path(__file__).parent.parent / 'data/usage')
self._catalog_type = '_' + handler.lower()
validate_import_export_type(UsageCatalogFactory, handler)
self._path = base_path
@property
def _comnet(self):
"""
Retrieve Comnet catalog
"""
return ComnetCatalog(self._path)
@property
def _nrcan(self):
"""
Retrieve NRCAN catalog
"""
# nrcan retrieves the data directly from github
return NrcanCatalog(self._path)
@property
def _eilat(self):
"""
Retrieve Eilat catalog
"""
return EilatCatalog(self._path)
@property
def catalog(self) -> Catalog:
"""
Enrich the city given to the class using the class given handler
:return: Catalog
"""
return getattr(self, self._catalog_type, lambda: None)

View File

View File

@ -0,0 +1,48 @@
"""
Node module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import uuid
from typing import List, TypeVar
Node = TypeVar('Node')
class Edge:
"""
Generic edge class to be used as base for the network edges
"""
def __init__(self, name, nodes=None):
if nodes is None:
nodes = []
self._name = name
self._id = None
self._nodes = nodes
@property
def name(self):
"""
Get edge name
:return: str
"""
return self._name
@property
def id(self):
"""
Get edge id, a universally unique identifier randomly generated
:return: str
"""
if self._id is None:
self._id = uuid.uuid4()
return self._id
@property
def nodes(self) -> List[Node]:
"""
Get delimiting nodes for the edge
:return: [Node]
"""
return self._nodes

View File

@ -0,0 +1,58 @@
"""
Node module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import uuid
from typing import List, TypeVar
from hub.city_model_structure.attributes.time_series import TimeSeries
Edge = TypeVar('Edge')
class Node:
"""
Generic node class to be used as base for the network nodes
"""
def __init__(self, name, edges=None):
if edges is None:
edges = []
self._name = name
self._id = None
self._edges = edges
self._time_series = None
@property
def name(self):
"""
Get node name
:return: str
"""
return self._name
@property
def id(self):
"""
Get node id, a universally unique identifier randomly generated
:return: str
"""
if self._id is None:
self._id = uuid.uuid4()
return self._id
@property
def edges(self) -> List[Edge]:
"""
Get edges delimited by the node
:return: [Edge]
"""
return self._edges
@property
def time_series(self) -> TimeSeries:
"""
Add explanation here
:return: add type of variable here
"""
return self._time_series

View File

@ -0,0 +1,79 @@
"""
Plane module
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 typing import TypeVar
import numpy as np
Point = TypeVar('Point')
class Plane:
"""
Plane class
"""
def __init__(self, origin, normal):
self._origin = origin
self._normal = normal
self._equation = None
self._opposite_normal = None
@property
def origin(self) -> Point:
"""
Get plane origin point
:return: Point
"""
return self._origin
@property
def normal(self):
"""
Get plane normal [x, y, z]
:return: np.ndarray
"""
return self._normal
@property
def equation(self) -> (float, float, float, float):
"""
Get the plane equation components Ax + By + Cz + D = 0
:return: (A, B, C, D)
"""
if self._equation is None:
a = self.normal[0]
b = self.normal[1]
c = self.normal[2]
d = -1 * self.origin.coordinates[0] * self.normal[0]
d += -1 * self.origin.coordinates[1] * self.normal[1]
d += -1 * self.origin.coordinates[2] * self.normal[2]
self._equation = a, b, c, d
return self._equation
def distance_to_point(self, point):
"""
Distance between the given point and the plane
:return: float
"""
p = point
e = self.equation
denominator = np.abs((p[0] * e[0]) + (p[1] * e[1]) + (p[2] * e[2]) + e[3])
numerator = np.sqrt((e[0]**2) + (e[1]**2) + (e[2]**2))
return float(denominator / numerator)
@property
def opposite_normal(self):
"""
get plane normal in the opposite direction [x, y, z]
:return: np.ndarray
"""
if self._opposite_normal is None:
coordinates = []
for coordinate in self.normal:
coordinates.append(-coordinate)
self._opposite_normal = np.array(coordinates)
return self._opposite_normal

View File

@ -0,0 +1,37 @@
"""
Point module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import math
class Point:
"""
Point class
"""
def __init__(self, coordinates):
self._coordinates = coordinates
@property
def coordinates(self):
"""
Get point coordinates
:return: [ndarray]
"""
return self._coordinates
def distance_to_point(self, other_point):
"""
Calculates distance between points in an n-D Euclidean space
:param other_point: point or vertex
:return: float
"""
power = 0
for dimension in enumerate(self.coordinates):
power += math.pow(other_point.coordinates[dimension]-self.coordinates[dimension], 2)
distance = math.sqrt(power)
return distance

View File

@ -0,0 +1,423 @@
"""
Polygon module
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 __future__ import annotations
import logging
import math
import sys
from typing import List
import numpy as np
import trimesh.creation
import trimesh.geometry
import trimesh.intersections
from shapely.geometry.polygon import Polygon as shapley_polygon
from trimesh import Trimesh
from hub.city_model_structure.attributes.plane import Plane
from hub.city_model_structure.attributes.point import Point
class Polygon:
"""
Polygon class
"""
def __init__(self, coordinates):
self._area = None
self._points = None
self._points_list = None
self._normal = None
self._inverse = None
self._edges = None
self._coordinates = coordinates
self._triangles = None
self._vertices = None
self._faces = None
self._plane = None
@property
def points(self) -> List[Point]:
"""
Get the points belonging to the polygon [[x, y, z],...]
:return: [Point]
"""
if self._points is None:
self._points = []
for coordinate in self.coordinates:
self._points.append(Point(coordinate))
return self._points
@property
def plane(self) -> Plane:
"""
Get the polygon plane
:return: Plane
"""
if self._plane is None:
self._plane = Plane(normal=self.normal, origin=self.points[0])
return self._plane
@property
def coordinates(self) -> List[np.ndarray]:
"""
Get the points in the shape of its coordinates belonging to the polygon [[x, y, z],...]
:return: [np.ndarray]
"""
return self._coordinates
@property
def points_list(self) -> np.ndarray:
"""
Get the solid surface point coordinates list [x, y, z, x, y, z,...]
:return: np.ndarray
"""
if self._points_list is None:
s = self.coordinates
self._points_list = np.reshape(s, len(s) * 3)
return self._points_list
@property
def edges(self) -> List[List[Point]]:
"""
Get polygon edges list
:return: [[Point]]
"""
if self._edges is None:
self._edges = []
for i in range(0, len(self.points) - 1):
point_1 = self.points[i]
point_2 = self.points[i + 1]
self._edges.append([point_1, point_2])
self._edges.append([self.points[len(self.points) - 1], self.points[0]])
return self._edges
@property
def area(self):
"""
Get surface area in square meters
:return: float
"""
if self._area is None:
self._area = 0
for triangle in self.triangles:
a_b = np.zeros(3)
a_c = np.zeros(3)
for i in range(0, 3):
a_b[i] = triangle.coordinates[1][i] - triangle.coordinates[0][i]
a_c[i] = triangle.coordinates[2][i] - triangle.coordinates[0][i]
self._area += np.linalg.norm(np.cross(a_b, a_c)) / 2
return self._area
@area.setter
def area(self, value):
self._area = value
@property
def normal(self) -> np.ndarray:
"""
Get surface normal vector
:return: np.ndarray
"""
if self._normal is None:
points = self.coordinates
# todo: IF THE FIRST ONE IS 0, START WITH THE NEXT
point_origin = points[len(points) - 2]
vector_1 = points[len(points) - 1] - point_origin
vector_2 = points[0] - point_origin
vector_3 = points[1] - point_origin
cross_product = np.cross(vector_1, vector_2)
if np.linalg.norm(cross_product) != 0:
cross_product = cross_product / np.linalg.norm(cross_product)
alpha = self._angle_between_vectors(vector_1, vector_2)
else:
cross_product = [0, 0, 0]
alpha = 0
if len(points) == 3:
return cross_product
if np.linalg.norm(cross_product) == 0:
return cross_product
alpha += self._angle(vector_2, vector_3, cross_product)
for i in range(0, len(points) - 4):
vector_1 = points[i + 1] - point_origin
vector_2 = points[i + 2] - point_origin
alpha += self._angle(vector_1, vector_2, cross_product)
vector_1 = points[len(points) - 1] - point_origin
vector_2 = points[0] - point_origin
if alpha < 0:
cross_product = np.cross(vector_2, vector_1)
else:
cross_product = np.cross(vector_1, vector_2)
self._normal = cross_product / np.linalg.norm(cross_product)
return self._normal
@staticmethod
def _angle(vector_1, vector_2, cross_product):
"""
alpha angle in radians
:param vector_1: [float]
:param vector_2: [float]
:param cross_product: [float]
:return: float
"""
accepted_normal_difference = 0.01
cross_product_next = np.cross(vector_1, vector_2)
if np.linalg.norm(cross_product_next) != 0:
cross_product_next = cross_product_next / np.linalg.norm(cross_product_next)
alpha = Polygon._angle_between_vectors(vector_1, vector_2)
else:
cross_product_next = [0, 0, 0]
alpha = 0
delta_normals = 0
for j in range(0, 3):
delta_normals += cross_product[j] - cross_product_next[j]
if np.abs(delta_normals) < accepted_normal_difference:
return alpha
return -alpha
@staticmethod
def triangle_mesh(vertices, normal) -> Trimesh:
"""
Get the triangulated mesh for the polygon
:return: Trimesh
"""
min_x = 1e16
min_y = 1e16
min_z = 1e16
for vertex in vertices:
if vertex[0] < min_x:
min_x = vertex[0]
if vertex[1] < min_y:
min_y = vertex[1]
if vertex[2] < min_z:
min_z = vertex[2]
new_vertices = []
for vertex in vertices:
vertex = [vertex[0]-min_x, vertex[1]-min_y, vertex[2]-min_z]
new_vertices.append(vertex)
transformation_matrix = trimesh.geometry.plane_transform(origin=new_vertices[0], normal=normal)
coordinates = []
for vertex in vertices:
transformed_vertex = [vertex[0]-min_x, vertex[1]-min_y, vertex[2]-min_z, 1]
transformed_vertex = np.dot(transformation_matrix, transformed_vertex)
coordinate = [transformed_vertex[0], transformed_vertex[1]]
coordinates.append(coordinate)
polygon = shapley_polygon(coordinates)
try:
_, faces = trimesh.creation.triangulate_polygon(polygon, engine='triangle')
mesh = Trimesh(vertices=vertices, faces=faces)
# check orientation
normal_sum = 0
for i in range(0, 3):
normal_sum += normal[i] + mesh.face_normals[0][i]
if abs(normal_sum) <= 1E-10:
new_faces = []
for face in faces:
new_face = []
for i in range(0, len(face)):
new_face.append(face[len(face)-i-1])
new_faces.append(new_face)
mesh = Trimesh(vertices=vertices, faces=new_faces)
return mesh
except ValueError:
logging.error('Not able to triangulate polygon\n')
_vertices = [[0, 0, 0], [0, 0, 1], [0, 1, 0]]
_faces = [[0, 1, 2]]
return Trimesh(vertices=_vertices, faces=_faces)
@property
def triangles(self) -> List[Polygon]:
"""
Triangulate the polygon and return a list of triangular polygons
:return: [Polygon]
"""
if self._triangles is None:
self._triangles = []
_mesh = self.triangle_mesh(self.coordinates, self.normal)
for face in _mesh.faces:
points = []
for vertex in face:
points.append(self.coordinates[vertex])
polygon = Polygon(points)
self._triangles.append(polygon)
return self._triangles
@staticmethod
def _angle_between_vectors(vec_1, vec_2):
"""
angle between vectors in radians
:param vec_1: vector
:param vec_2: vector
:return: float
"""
if np.linalg.norm(vec_1) == 0 or np.linalg.norm(vec_2) == 0:
sys.stderr.write("Warning: impossible to calculate angle between planes' normal. Return 0\n")
return 0
cosine = np.dot(vec_1, vec_2) / np.linalg.norm(vec_1) / np.linalg.norm(vec_2)
if cosine > 1 and cosine - 1 < 1e-5:
cosine = 1
elif cosine < -1 and cosine + 1 > -1e-5:
cosine = -1
alpha = math.acos(cosine)
return alpha
@property
def inverse(self):
"""
Get the polygon coordinates in reversed order
:return: [np.ndarray]
"""
if self._inverse is None:
self._inverse = self.coordinates[::-1]
return self._inverse
def divide(self, plane):
"""
Divides the polygon in two by a plane
:param plane: plane that intersects with self to divide it in two parts (Plane)
:return: Polygon, Polygon, [Point]
"""
tri_polygons = Trimesh(vertices=self.vertices, faces=self.faces)
intersection = trimesh.intersections.mesh_plane(tri_polygons, plane.normal, plane.origin.coordinates)
polys_1 = trimesh.intersections.slice_mesh_plane(tri_polygons, plane.opposite_normal, plane.origin.coordinates)
polys_2 = trimesh.intersections.slice_mesh_plane(tri_polygons, plane.normal, plane.origin.coordinates)
triangles_1 = []
for triangle in polys_1.triangles:
triangles_1.append(Polygon(triangle))
polygon_1 = self._reshape(triangles_1)
triangles_2 = []
for triangle in polys_2.triangles:
triangles_2.append(Polygon(triangle))
polygon_2 = self._reshape(triangles_2)
return polygon_1, polygon_2, intersection
def _reshape(self, triangles) -> Polygon:
edges_list = []
for i in enumerate(triangles):
for edge in triangles[i].edges:
if not self._edge_in_edges_list(edge, edges_list):
edges_list.append(edge)
else:
edges_list = self._remove_from_list(edge, edges_list)
points = self._order_points(edges_list)
return Polygon(points)
@staticmethod
def _edge_in_edges_list(edge, edges_list):
for edge_element in edges_list:
if (edge_element[0].distance_to_point(edge[0]) == 0 and edge_element[1].distance_to_point(edge[1]) == 0) or \
(edge_element[1].distance_to_point(edge[0]) == 0 and edge_element[0].distance_to_point(
edge[1]) == 0):
return True
return False
@staticmethod
def _order_points(edges_list):
# todo: not sure that this method works for any case -> RECHECK
points = edges_list[0]
for _ in range(0, len(points)):
for i in range(1, len(edges_list)):
point_1 = edges_list[i][0]
point_2 = points[len(points) - 1]
if point_1.distance_to_point(point_2) == 0:
points.append(edges_list[i][1])
points.remove(points[len(points) - 1])
array_points = []
for point in points:
array_points.append(point.coordinates)
return np.array(array_points)
@staticmethod
def _remove_from_list(edge, edges_list):
new_list = []
for edge_element in edges_list:
if not ((edge_element[0].distance_to_point(edge[0]) == 0 and edge_element[1].distance_to_point(
edge[1]) == 0) or
(edge_element[1].distance_to_point(edge[0]) == 0 and edge_element[0].distance_to_point(
edge[1]) == 0)):
new_list.append(edge_element)
return new_list
@property
def vertices(self) -> np.ndarray:
"""
Get polygon vertices
:return: np.ndarray(int)
"""
if self._vertices is None:
vertices, self._vertices = [], []
_ = [vertices.extend(s.coordinates) for s in self.triangles]
for vertex_1 in vertices:
found = False
for vertex_2 in self._vertices:
found = False
power = 0
for dimension in range(0, 3):
power += math.pow(vertex_2[dimension] - vertex_1[dimension], 2)
distance = math.sqrt(power)
if distance == 0:
found = True
break
if not found:
self._vertices.append(vertex_1)
self._vertices = np.asarray(self._vertices)
return self._vertices
@property
def faces(self) -> List[List[int]]:
"""
Get polygon triangular faces
:return: [face]
"""
if self._faces is None:
self._faces = []
for polygon in self.triangles:
face = []
points = polygon.coordinates
if len(points) != 3:
sub_polygons = polygon.triangles
# todo: I modified this! To be checked @Guille
if len(sub_polygons) < 1:
continue
for sub_polygon in sub_polygons:
face = []
points = sub_polygon.coordinates
for point in points:
face.append(self._position_of(point, face))
self._faces.append(face)
else:
for point in points:
face.append(self._position_of(point, face))
self._faces.append(face)
return self._faces
def _position_of(self, point, face):
"""
position of a specific point in the list of points that define a face
:return: int
"""
vertices = self.vertices
for i in enumerate(vertices):
# ensure not duplicated vertex
power = 0
vertex2 = vertices[i]
for dimension in range(0, 3):
power += math.pow(vertex2[dimension] - point[dimension], 2)
distance = math.sqrt(power)
if i not in face and distance == 0:
return i
return -1

View File

@ -0,0 +1,254 @@
"""
Polyhedron module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from typing import List, Union
import sys
import math
import numpy as np
from trimesh import Trimesh
from hub.helpers.configuration_helper import ConfigurationHelper
class Polyhedron:
"""
Polyhedron class
"""
def __init__(self, polygons):
self._polygons = polygons
self._polyhedron = None
self._triangulated_polyhedron = None
self._volume = None
self._faces = None
self._vertices = None
self._trimesh = None
self._centroid = None
self._max_z = None
self._max_y = None
self._max_x = None
self._min_z = None
self._min_y = None
self._min_x = None
def _position_of(self, point, face):
"""
position of a specific point in the list of points that define a face
:return: int
"""
vertices = self.vertices
for i, vertex in enumerate(vertices):
# ensure not duplicated vertex
power = 0
vertex2 = vertex
for dimension in range(0, 3):
power += math.pow(vertex2[dimension] - point[dimension], 2)
distance = math.sqrt(power)
if i not in face and distance == 0:
return i
return -1
@property
def vertices(self) -> np.ndarray:
"""
Get polyhedron vertices
:return: np.ndarray(int)
"""
if self._vertices is None:
vertices, self._vertices = [], []
_ = [vertices.extend(s.coordinates) for s in self._polygons]
for vertex_1 in vertices:
found = False
for vertex_2 in self._vertices:
found = False
power = 0
for dimension in range(0, 3):
power += math.pow(vertex_2[dimension] - vertex_1[dimension], 2)
distance = math.sqrt(power)
if distance == 0:
found = True
break
if not found:
self._vertices.append(vertex_1)
self._vertices = np.asarray(self._vertices)
return self._vertices
@property
def faces(self) -> List[List[int]]:
"""
Get polyhedron triangular faces
:return: [face]
"""
if self._faces is None:
self._faces = []
for polygon in self._polygons:
face = []
points = polygon.coordinates
if len(points) != 3:
sub_polygons = polygon.triangles
if len(sub_polygons) < 1:
continue
for sub_polygon in sub_polygons:
face = []
points = sub_polygon.coordinates
for point in points:
face.append(self._position_of(point, face))
self._faces.append(face)
else:
for point in points:
face.append(self._position_of(point, face))
self._faces.append(face)
return self._faces
@property
def trimesh(self) -> Union[Trimesh, None]:
"""
Get polyhedron trimesh
:return: Trimesh
"""
if self._trimesh is None:
for face in self.faces:
if len(face) != 3:
sys.stderr.write('Not able to generate trimesh\n')
return None
self._trimesh = Trimesh(vertices=self.vertices, faces=self.faces)
return self._trimesh
@property
def volume(self):
"""
Get polyhedron volume in cubic meters
:return: float
"""
if self._volume is None:
if self.trimesh is None:
self._volume = np.inf
elif not self.trimesh.is_volume:
self._volume = np.inf
else:
self._volume = self.trimesh.volume
return self._volume
@property
def max_z(self):
"""
Get polyhedron maximal z value in meters
:return: float
"""
if self._max_z is None:
self._max_z = ConfigurationHelper().min_coordinate
for polygon in self._polygons:
for point in polygon.coordinates:
self._max_z = max(self._max_z, point[2])
return self._max_z
@property
def max_y(self):
"""
Get polyhedron maximal y value in meters
:return: float
"""
if self._max_y is None:
self._max_y = ConfigurationHelper().min_coordinate
for polygon in self._polygons:
for point in polygon.coordinates:
if self._max_y < point[1]:
self._max_y = point[1]
return self._max_y
@property
def max_x(self):
"""
Get polyhedron maximal x value in meters
:return: float
"""
if self._max_x is None:
self._max_x = ConfigurationHelper().min_coordinate
for polygon in self._polygons:
for point in polygon.coordinates:
self._max_x = max(self._max_x, point[0])
return self._max_x
@property
def min_z(self):
"""
Get polyhedron minimal z value in meters
:return: float
"""
if self._min_z is None:
self._min_z = self.max_z
for polygon in self._polygons:
for point in polygon.coordinates:
if self._min_z > point[2]:
self._min_z = point[2]
return self._min_z
@property
def min_y(self):
"""
Get polyhedron minimal y value in meters
:return: float
"""
if self._min_y is None:
self._min_y = self.max_y
for polygon in self._polygons:
for point in polygon.coordinates:
if self._min_y > point[1]:
self._min_y = point[1]
return self._min_y
@property
def min_x(self):
"""
Get polyhedron minimal x value in meters
:return: float
"""
if self._min_x is None:
self._min_x = self.max_x
for polygon in self._polygons:
for point in polygon.coordinates:
if self._min_x > point[0]:
self._min_x = point[0]
return self._min_x
@property
def centroid(self) -> Union[None, List[float]]:
"""
Get polyhedron centroid
:return: [x,y,z]
"""
if self._centroid is None:
trimesh = self.trimesh
if trimesh is None:
return None
self._centroid = self.trimesh.centroid
return self._centroid
def stl_export(self, full_path):
"""
Export the polyhedron to stl given file
:param full_path: str
:return: None
"""
self.trimesh.export(full_path, 'stl_ascii')
def obj_export(self, full_path):
"""
Export the polyhedron to obj given file
:param full_path: str
:return: None
"""
self.trimesh.export(full_path, 'obj')
def show(self):
"""
Auxiliary function to render the polyhedron
:return: None
"""
self.trimesh.show()

View File

@ -0,0 +1,41 @@
"""
Record module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
class Record:
"""
Record class
"""
def __init__(self, time=None, value=None, flag=None):
self._time = time
self._value = value
self._flag = flag
@property
def time(self):
"""
Add explanation here
:return: add type of variable here
"""
return self._time
@property
def value(self):
"""
Add explanation here
:return: add type of variable here
"""
return self._value
@property
def flag(self):
"""
Add explanation here
:return: add type of variable here
"""
return self._flag

View File

@ -0,0 +1,140 @@
"""
Schedule module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import uuid
from typing import Union, List
class Schedule:
"""
Schedule class
"""
def __init__(self):
self._id = None
self._type = None
self._values = None
self._data_type = None
self._time_step = None
self._time_range = None
self._day_types = None
@property
def id(self):
"""
Get schedule id, a universally unique identifier randomly generated
:return: str
"""
if self._id is None:
self._id = uuid.uuid4()
return self._id
@property
def type(self) -> Union[None, str]:
"""
Get schedule type
:return: None or str
"""
return self._type
@type.setter
def type(self, value):
"""
Set schedule type
:param: str
"""
if value is not None:
self._type = str(value)
@property
def values(self):
"""
Get schedule values
:return: [Any]
"""
return self._values
@values.setter
def values(self, value):
"""
Set schedule values
:param: [Any]
"""
self._values = value
@property
def data_type(self) -> Union[None, str]:
"""
Get schedule data type from:
['any_number', 'fraction', 'on_off', 'temperature', 'humidity', 'control_type']
:return: None or str
"""
return self._data_type
@data_type.setter
def data_type(self, value):
"""
Set schedule data type
:param: str
"""
if value is not None:
self._data_type = str(value)
@property
def time_step(self) -> Union[None, str]:
"""
Get schedule time step from:
['second', 'minute', 'hour', 'day', 'week', 'month']
:return: None or str
"""
return self._time_step
@time_step.setter
def time_step(self, value):
"""
Set schedule time step
:param: str
"""
if value is not None:
self._time_step = str(value)
@property
def time_range(self) -> Union[None, str]:
"""
Get schedule time range from:
['minute', 'hour', 'day', 'week', 'month', 'year']
:return: None or str
"""
return self._time_range
@time_range.setter
def time_range(self, value):
"""
Set schedule time range
:param: str
"""
if value is not None:
self._time_range = str(value)
@property
def day_types(self) -> Union[None, List[str]]:
"""
Get schedule day types, as many as needed from:
['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'holiday', 'winter_design_day',
'summer_design_day']
:return: None or [str]
"""
return self._day_types
@day_types.setter
def day_types(self, value):
"""
Set schedule day types
:param: [str]
"""
if value is not None:
self._day_types = [str(i) for i in value]

View File

@ -0,0 +1,35 @@
"""
Time series module
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 typing import List
from hub.city_model_structure.attributes.record import Record
class TimeSeries:
"""
TimeSeries class
"""
def __init__(self, time_series_type=None, records=None):
self._time_series_type = time_series_type
self._records = records
@property
def time_series_type(self):
"""
Add explanation here
:return: add type of variable here
"""
return self._time_series_type
@property
def records(self) -> List[Record]:
"""
Add explanation here
:return: List[Record]
"""
return self._records

Some files were not shown because too many files have changed in this diff Show More