Merge branch 'master' into 'db_persistence'
# Conflicts: # .gitignore # city_model_structure/city.py # requirements.txt # unittests/test_energy_systems_water_to_water_hp.py
This commit is contained in:
commit
4bf2ff4ff0
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@
|
|||
.DS_Store
|
||||
.env
|
||||
logs
|
||||
**/__pycache__/
|
||||
|
|
|
@ -164,7 +164,7 @@ When all the dependencies are satisfied, you are all set to start importing your
|
|||
Add the following code to your main.py
|
||||
from imports.geometry_factory import GeometryFactory
|
||||
|
||||
city = GeometryFactory('citygml', 'myfile.gml').city
|
||||
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
|
||||
|
|
|
@ -167,9 +167,9 @@ Attributes with known units should be explicit in method's comment.
|
|||
|
||||
#### To do's.
|
||||
|
||||
Pending to implement operations should be indicated with ToDo comments to highlight the missing functionality.
|
||||
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
|
||||
# todo: right now extracted at the city level, in the future should be extracted also at building level if exist
|
||||
```
|
||||
|
||||
|
|
|
@ -237,7 +237,7 @@ Add the following code to your main.py
|
|||
|
||||
from imports.geometry_factory import GeometryFactory
|
||||
|
||||
city = GeometryFactory('citygml', 'myfile.gml').city
|
||||
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!
|
||||
|
|
|
@ -116,7 +116,7 @@ class NrelCatalog(Catalog):
|
|||
climate_zone = archetype['@climate_zone']
|
||||
construction_period = reference_standard_to_construction_period[archetype['@reference_standard']]
|
||||
average_storey_height = archetype['average_storey_height']['#text']
|
||||
thermal_capacity = archetype['thermal_capacity']['#text']
|
||||
thermal_capacity = str(float(archetype['thermal_capacity']['#text']) * 1000)
|
||||
extra_loses_due_to_thermal_bridges = archetype['extra_loses_due_to_thermal_bridges']['#text']
|
||||
indirect_heated_ratio = archetype['indirect_heated_ratio']['#text']
|
||||
infiltration_rate_for_ventilation_system_off = archetype['infiltration_rate_for_ventilation_system_off']['#text']
|
||||
|
|
|
@ -12,7 +12,7 @@ Catalog = TypeVar('Catalog')
|
|||
|
||||
class GreeneryCatalogFactory:
|
||||
"""
|
||||
GeometryFactory class
|
||||
GreeneryCatalogFactory class
|
||||
"""
|
||||
def __init__(self, file_type, base_path=None):
|
||||
if base_path is None:
|
||||
|
|
|
@ -193,7 +193,6 @@ class ComnetCatalog(Catalog):
|
|||
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,
|
||||
|
|
170
catalog_factories/usage/nrcan_catalog.py
Normal file
170
catalog_factories/usage/nrcan_catalog.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
"""
|
||||
NRCAN usage catalog
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
import xmltodict
|
||||
|
||||
import helpers.constants as cte
|
||||
from catalog_factories.catalog import Catalog
|
||||
from catalog_factories.data_models.usages.appliances import Appliances
|
||||
from catalog_factories.data_models.usages.content import Content
|
||||
from catalog_factories.data_models.usages.lighting import Lighting
|
||||
from catalog_factories.data_models.usages.ocupancy import Occupancy
|
||||
from catalog_factories.data_models.usages.schedule import Schedule
|
||||
from catalog_factories.data_models.usages.thermal_control import ThermalControl
|
||||
from catalog_factories.data_models.usages.usage import Usage
|
||||
from catalog_factories.usage.usage_helper import UsageHelper
|
||||
|
||||
|
||||
class NrcanCatalog(Catalog):
|
||||
def __init__(self, path):
|
||||
path = str(path / 'nrcan.xml')
|
||||
self._content = None
|
||||
self._schedules = {}
|
||||
with open(path) as xml:
|
||||
self._metadata = xmltodict.parse(xml.read())
|
||||
self._base_url = self._metadata['nrcan']['@base_url']
|
||||
self._load_schedules()
|
||||
self._content = Content(self._load_archetypes())
|
||||
|
||||
def _calculate_hours_day(self, function):
|
||||
# todo: pilar need to check how to calculate this value
|
||||
return 24
|
||||
|
||||
@staticmethod
|
||||
def _extract_schedule(raw):
|
||||
nrcan_schedule_type = raw['category']
|
||||
if 'Heating' 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 yearly range for the schedules
|
||||
time_range = cte.YEAR
|
||||
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):
|
||||
usage = self._metadata['nrcan']['standards']['usage']
|
||||
url = f'{self._base_url}{usage["schedules_location"]}'
|
||||
with urllib.request.urlopen(url) as json_file:
|
||||
schedules_type = json.load(json_file)
|
||||
for schedule_type in schedules_type['tables']['schedules']['table']:
|
||||
schedule = NrcanCatalog._extract_schedule(schedule_type)
|
||||
if schedule is not None:
|
||||
self._schedules[schedule_type['name']] = schedule
|
||||
|
||||
def _get_schedule(self, name):
|
||||
if name in self._schedules:
|
||||
return self._schedules[name]
|
||||
|
||||
def _load_archetypes(self):
|
||||
usages = []
|
||||
usage = self._metadata['nrcan']['standards']['usage']
|
||||
url = f'{self._base_url}{usage["space_types_location"]}'
|
||||
with urllib.request.urlopen(url) as json_file:
|
||||
space_types = json.load(json_file)['tables']['space_types']['table']
|
||||
space_types = [st for st in space_types if st['building_type'] == 'Space Function']
|
||||
for space_type in space_types:
|
||||
usage_type = space_type['space_type']
|
||||
mechanical_air_change = space_type['ventilation_air_changes']
|
||||
ventilation_rate = space_type['ventilation_per_area']
|
||||
if ventilation_rate == 0:
|
||||
ventilation_rate = space_type['ventilation_per_person']
|
||||
hours_day = self._calculate_hours_day(usage_type)
|
||||
days_year = 365
|
||||
occupancy_schedule_name = space_type['occupancy_schedule']
|
||||
lighting_schedule_name = space_type['lighting_schedule']
|
||||
appliance_schedule_name = space_type['electric_equipment_schedule']
|
||||
# thermal control
|
||||
heating_setpoint_schedule_name = space_type['heating_setpoint_schedule']
|
||||
cooling_setpoint_schedule_name = space_type['cooling_setpoint_schedule']
|
||||
occupancy_schedule = self._get_schedule(occupancy_schedule_name)
|
||||
lighting_schedule = self._get_schedule(lighting_schedule_name)
|
||||
appliance_schedule = self._get_schedule(appliance_schedule_name)
|
||||
heating_schedule = self._get_schedule(heating_setpoint_schedule_name)
|
||||
cooling_schedule = self._get_schedule(cooling_setpoint_schedule_name)
|
||||
|
||||
occupancy_density = space_type['occupancy_per_area']
|
||||
lighting_density = space_type['lighting_per_area']
|
||||
lighting_radiative_fraction = space_type['lighting_fraction_radiant']
|
||||
if lighting_radiative_fraction is not None:
|
||||
lighting_convective_fraction = 1 - lighting_radiative_fraction
|
||||
lighting_latent_fraction = 0
|
||||
appliances_density = space_type['electric_equipment_per_area']
|
||||
appliances_radiative_fraction = space_type['electric_equipment_fraction_radiant']
|
||||
if appliances_radiative_fraction is not None:
|
||||
appliances_convective_fraction = 1 - appliances_radiative_fraction
|
||||
appliances_latent_fraction = space_type['electric_equipment_fraction_latent']
|
||||
|
||||
occupancy = Occupancy(occupancy_density, 0, 0, 0, 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)
|
||||
if heating_schedule is not None:
|
||||
thermal_control = ThermalControl(max(heating_schedule.values),
|
||||
min(heating_schedule.values),
|
||||
min(cooling_schedule.values),
|
||||
None,
|
||||
heating_schedule,
|
||||
cooling_schedule)
|
||||
else:
|
||||
thermal_control = ThermalControl(None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
usages.append(Usage(usage_type,
|
||||
hours_day,
|
||||
days_year,
|
||||
mechanical_air_change,
|
||||
ventilation_rate,
|
||||
occupancy,
|
||||
lighting,
|
||||
appliances,
|
||||
thermal_control))
|
||||
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.usage)
|
||||
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.usage.lower() == name.lower():
|
||||
return usage
|
||||
raise IndexError(f"{name} doesn't exists in the catalog")
|
|
@ -13,6 +13,51 @@ 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, # Compose 'Thermostat Setpoint' + 'Cooling'
|
||||
'Thermostat Setpoint Heating': cte.HEATING_SET_POINT, # Compose 'Thermostat Setpoint' + 'Heating'
|
||||
}
|
||||
_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]
|
||||
|
||||
}
|
||||
|
||||
_usage_to_hft = {
|
||||
cte.RESIDENTIAL: 'residential',
|
||||
cte.SINGLE_FAMILY_HOUSE: 'Single family house',
|
||||
|
@ -71,7 +116,7 @@ class UsageHelper:
|
|||
cte.GREEN_HOUSE: cte.GREEN_HOUSE,
|
||||
cte.NON_HEATED: cte.NON_HEATED
|
||||
}
|
||||
|
||||
|
||||
_comnet_data_type_to_hub_data_type = {
|
||||
'Fraction': cte.FRACTION,
|
||||
'OnOff': cte.ON_OFF,
|
||||
|
@ -93,6 +138,22 @@ class UsageHelper:
|
|||
'C-14 Gymnasium': 'C-14 Gymnasium'
|
||||
}
|
||||
|
||||
@property
|
||||
def nrcan_day_type_to_hub_days(self):
|
||||
return self._nrcan_day_type_to_hub_days
|
||||
|
||||
@property
|
||||
def nrcan_schedule_type_to_hub_schedule_type(self):
|
||||
return self._nrcan_schedule_type_to_hub_schedule_type
|
||||
|
||||
@property
|
||||
def nrcan_data_type_to_hub_data_type(self):
|
||||
return self._nrcan_data_type_to_hub_data_type
|
||||
|
||||
@property
|
||||
def nrcan_time_to_hub_time(self):
|
||||
return self._nrcan_time_to_hub_time
|
||||
|
||||
@property
|
||||
def comnet_data_type_to_hub_data_type(self):
|
||||
return self._comnet_data_type_to_hub_data_type
|
||||
|
|
|
@ -8,6 +8,7 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|||
from pathlib import Path
|
||||
from typing import TypeVar
|
||||
from catalog_factories.usage.comnet_catalog import ComnetCatalog
|
||||
from catalog_factories.usage.nrcan_catalog import NrcanCatalog
|
||||
Catalog = TypeVar('Catalog')
|
||||
|
||||
|
||||
|
@ -25,6 +26,14 @@ class UsageCatalogFactory:
|
|||
"""
|
||||
return ComnetCatalog(self._path)
|
||||
|
||||
@property
|
||||
def _nrcan(self):
|
||||
"""
|
||||
Retrieve NRCAN catalog
|
||||
"""
|
||||
# nrcan retrieves the data directly from github
|
||||
return NrcanCatalog(self._path)
|
||||
|
||||
@property
|
||||
def catalog(self) -> Catalog:
|
||||
"""
|
||||
|
|
|
@ -8,6 +8,7 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
|
|||
|
||||
from typing import List, Union
|
||||
import numpy as np
|
||||
import helpers.constants as cte
|
||||
from city_model_structure.building_demand.surface import Surface
|
||||
from city_model_structure.city_object import CityObject
|
||||
from city_model_structure.building_demand.household import Household
|
||||
|
@ -19,8 +20,8 @@ class Building(CityObject):
|
|||
"""
|
||||
Building(CityObject) class
|
||||
"""
|
||||
def __init__(self, name, lod, surfaces, year_of_construction, function, city_lower_corner, terrains=None):
|
||||
super().__init__(name, lod, surfaces, city_lower_corner)
|
||||
def __init__(self, name, surfaces, year_of_construction, function, terrains=None):
|
||||
super().__init__(name, surfaces)
|
||||
self._households = None
|
||||
self._basement_heated = None
|
||||
self._attic_heated = None
|
||||
|
@ -37,6 +38,9 @@ class Building(CityObject):
|
|||
self._type = 'building'
|
||||
self._heating = dict()
|
||||
self._cooling = dict()
|
||||
self._lighting_electrical_demand = dict()
|
||||
self._appliances_electrical_demand = dict()
|
||||
self._domestic_hot_water_heat_demand = dict()
|
||||
self._eave_height = None
|
||||
self._grounds = []
|
||||
self._roofs = []
|
||||
|
@ -48,11 +52,11 @@ class Building(CityObject):
|
|||
self._min_z = min(self._min_z, surface.lower_corner[2])
|
||||
surface.id = surface_id
|
||||
# todo: consider all type of surfaces, not only these four
|
||||
if surface.type == 'Ground':
|
||||
if surface.type == cte.GROUND:
|
||||
self._grounds.append(surface)
|
||||
elif surface.type == 'Wall':
|
||||
elif surface.type == cte.WALL:
|
||||
self._walls.append(surface)
|
||||
elif surface.type == 'Roof':
|
||||
elif surface.type == cte.ROOF:
|
||||
self._roofs.append(surface)
|
||||
else:
|
||||
self._internal_walls.append(surface)
|
||||
|
@ -60,11 +64,18 @@ class Building(CityObject):
|
|||
@property
|
||||
def shell(self) -> Polyhedron:
|
||||
"""
|
||||
Get building shell
|
||||
Get building's external polyhedron
|
||||
:return: [Polyhedron]
|
||||
"""
|
||||
polygons = []
|
||||
for surface in self.surfaces:
|
||||
if surface.type is not cte.INTERIOR_WALL:
|
||||
polygons.append(surface.solid_polygon)
|
||||
if surface.holes_polygons is not None:
|
||||
for hole in surface.holes_polygons:
|
||||
polygons.append(hole)
|
||||
if self._shell is None:
|
||||
self._shell = Polyhedron(self.surfaces)
|
||||
self._shell = Polyhedron(polygons)
|
||||
return self._shell
|
||||
|
||||
@property
|
||||
|
@ -103,6 +114,14 @@ class Building(CityObject):
|
|||
"""
|
||||
return self._walls
|
||||
|
||||
@property
|
||||
def internal_walls(self) -> List[Surface]:
|
||||
"""
|
||||
Get building internal wall surfaces
|
||||
:return: [Surface]
|
||||
"""
|
||||
return self._internal_walls
|
||||
|
||||
@property
|
||||
def terrains(self) -> Union[None, List[Surface]]:
|
||||
"""
|
||||
|
@ -115,6 +134,9 @@ class Building(CityObject):
|
|||
def attic_heated(self) -> Union[None, int]:
|
||||
"""
|
||||
Get if the city object attic is heated
|
||||
0: no attic in the building
|
||||
1: attic exists but is not heated
|
||||
2: attic exists and is heated
|
||||
:return: None or int
|
||||
"""
|
||||
return self._attic_heated
|
||||
|
@ -123,6 +145,9 @@ class Building(CityObject):
|
|||
def attic_heated(self, value):
|
||||
"""
|
||||
Set if the city object attic is heated
|
||||
0: no attic in the building
|
||||
1: attic exists but is not heated
|
||||
2: attic exists and is heated
|
||||
:param value: int
|
||||
"""
|
||||
if value is not None:
|
||||
|
@ -132,6 +157,9 @@ class Building(CityObject):
|
|||
def basement_heated(self) -> Union[None, int]:
|
||||
"""
|
||||
Get if the city object basement is heated
|
||||
0: no basement in the building
|
||||
1: basement exists but is not heated
|
||||
2: basement exists and is heated
|
||||
:return: None or int
|
||||
"""
|
||||
return self._basement_heated
|
||||
|
@ -140,6 +168,9 @@ class Building(CityObject):
|
|||
def basement_heated(self, value):
|
||||
"""
|
||||
Set if the city object basement is heated
|
||||
0: no basement in the building
|
||||
1: basement exists but is not heated
|
||||
2: basement exists and is heated
|
||||
:param value: int
|
||||
"""
|
||||
if value is not None:
|
||||
|
@ -150,7 +181,7 @@ class Building(CityObject):
|
|||
"""
|
||||
Raises not implemented error
|
||||
"""
|
||||
# ToDo: this need to be calculated based on the basement and attic heated values
|
||||
# todo: this need to be calculated based on the basement and attic heated values
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
|
@ -253,6 +284,54 @@ class Building(CityObject):
|
|||
"""
|
||||
self._cooling = value
|
||||
|
||||
@property
|
||||
def lighting_electrical_demand(self) -> dict:
|
||||
"""
|
||||
Get lighting electrical demand in Wh
|
||||
:return: dict{DataFrame(float)}
|
||||
"""
|
||||
return self._lighting_electrical_demand
|
||||
|
||||
@lighting_electrical_demand.setter
|
||||
def lighting_electrical_demand(self, value):
|
||||
"""
|
||||
Set lighting electrical demand in Wh
|
||||
:param value: dict{DataFrame(float)}
|
||||
"""
|
||||
self._lighting_electrical_demand = value
|
||||
|
||||
@property
|
||||
def appliances_electrical_demand(self) -> dict:
|
||||
"""
|
||||
Get appliances electrical demand in Wh
|
||||
:return: dict{DataFrame(float)}
|
||||
"""
|
||||
return self._appliances_electrical_demand
|
||||
|
||||
@appliances_electrical_demand.setter
|
||||
def appliances_electrical_demand(self, value):
|
||||
"""
|
||||
Set appliances electrical demand in Wh
|
||||
:param value: dict{DataFrame(float)}
|
||||
"""
|
||||
self._appliances_electrical_demand = value
|
||||
|
||||
@property
|
||||
def domestic_hot_water_heat_demand(self) -> dict:
|
||||
"""
|
||||
Get domestic hot water heat demand in Wh
|
||||
:return: dict{DataFrame(float)}
|
||||
"""
|
||||
return self._domestic_hot_water_heat_demand
|
||||
|
||||
@domestic_hot_water_heat_demand.setter
|
||||
def domestic_hot_water_heat_demand(self, value):
|
||||
"""
|
||||
Set domestic hot water heat demand in Wh
|
||||
:param value: dict{DataFrame(float)}
|
||||
"""
|
||||
self._domestic_hot_water_heat_demand = value
|
||||
|
||||
@property
|
||||
def eave_height(self):
|
||||
"""
|
||||
|
@ -327,7 +406,7 @@ class Building(CityObject):
|
|||
@property
|
||||
def human_readable_name(self):
|
||||
"""
|
||||
Get the human readable name for the building
|
||||
Get the human-readable name for the building
|
||||
:return: str
|
||||
"""
|
||||
return self._human_readable_name
|
||||
|
@ -335,6 +414,6 @@ class Building(CityObject):
|
|||
@human_readable_name.setter
|
||||
def human_readable_name(self, value):
|
||||
"""
|
||||
Set the human readable name for the building
|
||||
Set the human-readable name for the building
|
||||
"""
|
||||
self._human_readable_name = value
|
||||
|
|
|
@ -66,7 +66,9 @@ class Storey:
|
|||
windows_areas = []
|
||||
for hole in surface.holes_polygons:
|
||||
windows_areas.append(hole.area)
|
||||
self._thermal_boundaries.append(ThermalBoundary(surface, surface.solid_polygon.area, windows_areas))
|
||||
new_thermal_boundary = ThermalBoundary(surface, surface.solid_polygon.area, windows_areas)
|
||||
surface.associated_thermal_boundaries.append(new_thermal_boundary)
|
||||
self._thermal_boundaries.append(new_thermal_boundary)
|
||||
return self._thermal_boundaries
|
||||
|
||||
@property
|
||||
|
@ -78,7 +80,7 @@ class Storey:
|
|||
if self._virtual_surfaces is None:
|
||||
self._virtual_surfaces = []
|
||||
for thermal_boundary in self.thermal_boundaries:
|
||||
self._virtual_surfaces.append(thermal_boundary.virtual_internal_surface)
|
||||
self._virtual_surfaces.append(thermal_boundary.internal_surface)
|
||||
return self._virtual_surfaces
|
||||
|
||||
@property
|
||||
|
|
|
@ -13,6 +13,8 @@ from typing import List, Union
|
|||
from city_model_structure.attributes.polygon import Polygon
|
||||
from city_model_structure.attributes.plane import Plane
|
||||
from city_model_structure.attributes.point import Point
|
||||
from city_model_structure.greenery.vegetation import Vegetation
|
||||
from city_model_structure.building_demand.thermal_boundary import ThermalBoundary
|
||||
import helpers.constants as cte
|
||||
|
||||
|
||||
|
@ -21,15 +23,13 @@ class Surface:
|
|||
Surface class
|
||||
"""
|
||||
|
||||
def __init__(self, solid_polygon, perimeter_polygon, holes_polygons=None, name=None, surface_type=None, swr=None):
|
||||
def __init__(self, solid_polygon, perimeter_polygon, holes_polygons=None, name=None, surface_type=None):
|
||||
self._type = surface_type
|
||||
self._swr = swr
|
||||
self._name = name
|
||||
self._id = None
|
||||
self._azimuth = None
|
||||
self._inclination = None
|
||||
self._area_above_ground = None
|
||||
self._area_below_ground = None
|
||||
self._area = None
|
||||
self._lower_corner = None
|
||||
self._upper_corner = None
|
||||
self._shared_surfaces = []
|
||||
|
@ -37,9 +37,11 @@ class Surface:
|
|||
self._perimeter_polygon = perimeter_polygon
|
||||
self._holes_polygons = holes_polygons
|
||||
self._solid_polygon = solid_polygon
|
||||
self._pv_system_installed = None
|
||||
self._short_wave_reflectance = None
|
||||
self._long_wave_emittance = None
|
||||
self._inverse = None
|
||||
# todo: create self._associated_thermal_boundaries and bring the vegetation here instead of in thermal_boundary
|
||||
self._associated_thermal_boundaries = []
|
||||
self._vegetation = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -78,23 +80,6 @@ class Surface:
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def swr(self) -> Union[None, float]:
|
||||
"""
|
||||
Get surface short wave reflectance
|
||||
:return: None or float
|
||||
"""
|
||||
return self._swr
|
||||
|
||||
@swr.setter
|
||||
def swr(self, value):
|
||||
"""
|
||||
Set surface short wave reflectance
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._swr = float(value)
|
||||
|
||||
def _max_coord(self, axis):
|
||||
if axis == 'x':
|
||||
axis = 0
|
||||
|
@ -146,23 +131,13 @@ class Surface:
|
|||
return self._upper_corner
|
||||
|
||||
@property
|
||||
def area_above_ground(self):
|
||||
def perimeter_area(self):
|
||||
"""
|
||||
Get surface area above ground in square meters
|
||||
Get perimeter surface area in square meters (opaque + transparent)
|
||||
:return: float
|
||||
"""
|
||||
if self._area_above_ground is None:
|
||||
self._area_above_ground = self.perimeter_polygon.area - self.area_below_ground
|
||||
return self._area_above_ground
|
||||
|
||||
# todo: to be implemented when adding terrains
|
||||
@property
|
||||
def area_below_ground(self):
|
||||
"""
|
||||
Get surface area below ground in square meters
|
||||
:return: float
|
||||
"""
|
||||
return 0.0
|
||||
self._area = self.perimeter_polygon.area
|
||||
return self._area
|
||||
|
||||
@property
|
||||
def azimuth(self):
|
||||
|
@ -262,6 +237,42 @@ class Surface:
|
|||
"""
|
||||
self._holes_polygons = value
|
||||
|
||||
@property
|
||||
def short_wave_reflectance(self):
|
||||
"""
|
||||
Get the short wave reflectance, this includes all solar spectrum, visible and not visible
|
||||
The absorptance as an opaque surface, can be calculated as 1-short_wave_reflectance
|
||||
:return: float
|
||||
"""
|
||||
return self._short_wave_reflectance
|
||||
|
||||
@short_wave_reflectance.setter
|
||||
def short_wave_reflectance(self, value):
|
||||
"""
|
||||
Set the short wave reflectance, this includes all solar spectrum, visible and not visible
|
||||
The absorptance as an opaque surface, can be calculated as 1-short_wave_reflectance
|
||||
:param value: float
|
||||
"""
|
||||
self._short_wave_reflectance = value
|
||||
|
||||
@property
|
||||
def long_wave_emittance(self):
|
||||
"""
|
||||
Get the long wave emittance af the surface
|
||||
The thermal absorptance can be calculated as 1-long_wave_emittance
|
||||
:return: float
|
||||
"""
|
||||
return self._long_wave_emittance
|
||||
|
||||
@long_wave_emittance.setter
|
||||
def long_wave_emittance(self, value):
|
||||
"""
|
||||
Set the long wave emittance af the surface
|
||||
The thermal absorptance can be calculated as 1-long_wave_emittance
|
||||
:param value: float
|
||||
"""
|
||||
self._long_wave_emittance = value
|
||||
|
||||
@property
|
||||
def inverse(self) -> Surface:
|
||||
"""
|
||||
|
@ -302,3 +313,35 @@ class Surface:
|
|||
surface_child = Surface(part_1, part_1, name=self.name, surface_type=self.type)
|
||||
rest_surface = Surface(part_2, part_2, name=self.name, surface_type=self.type)
|
||||
return surface_child, rest_surface, intersection
|
||||
|
||||
@property
|
||||
def vegetation(self) -> Union[None, Vegetation]:
|
||||
"""
|
||||
Get the vegetation construction at the external surface of the thermal boundary
|
||||
:return: None or Vegetation
|
||||
"""
|
||||
return self._vegetation
|
||||
|
||||
@vegetation.setter
|
||||
def vegetation(self, value):
|
||||
"""
|
||||
Set the vegetation construction at the external surface of the thermal boundary
|
||||
:param value: Vegetation
|
||||
"""
|
||||
self._vegetation = value
|
||||
|
||||
@property
|
||||
def associated_thermal_boundaries(self) -> Union[None, List[ThermalBoundary]]:
|
||||
"""
|
||||
Get the list of thermal boundaries that has this surface as external face
|
||||
:return: None or [ThermalBoundary]
|
||||
"""
|
||||
return self._associated_thermal_boundaries
|
||||
|
||||
@associated_thermal_boundaries.setter
|
||||
def associated_thermal_boundaries(self, value):
|
||||
"""
|
||||
Set the list of thermal boundaries that has this surface as external face
|
||||
:param value: None or [ThermalBoundary]
|
||||
"""
|
||||
self._associated_thermal_boundaries = value
|
||||
|
|
|
@ -7,13 +7,14 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
|
|||
"""
|
||||
|
||||
import uuid
|
||||
from typing import List, Union
|
||||
from typing import List, Union, TypeVar
|
||||
from helpers.configuration_helper import ConfigurationHelper as ch
|
||||
import helpers.constants as cte
|
||||
from city_model_structure.building_demand.layer import Layer
|
||||
from city_model_structure.building_demand.thermal_opening import ThermalOpening
|
||||
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
||||
from city_model_structure.building_demand.surface import Surface
|
||||
from city_model_structure.greenery.vegetation import Vegetation
|
||||
|
||||
Surface = TypeVar('Surface')
|
||||
|
||||
|
||||
class ThermalBoundary:
|
||||
|
@ -28,22 +29,14 @@ class ThermalBoundary:
|
|||
self._thermal_zones = None
|
||||
self._thermal_openings = None
|
||||
self._layers = None
|
||||
self._outside_solar_absorptance = None
|
||||
self._outside_thermal_absorptance = None
|
||||
self._outside_visible_absorptance = None
|
||||
self._u_value = None
|
||||
self._outside_shortwave_reflectance = None
|
||||
self._construction_name = None
|
||||
self._hi = ch().convective_heat_transfer_coefficient_interior
|
||||
self._he = ch().convective_heat_transfer_coefficient_exterior
|
||||
self._hi = ch().convective_heat_transfer_coefficient_interior
|
||||
self._u_value = None
|
||||
self._construction_name = None
|
||||
self._thickness = None
|
||||
self._virtual_internal_surface = None
|
||||
self._inside_emissivity = None
|
||||
self._alpha_coefficient = None
|
||||
self._radiative_coefficient = None
|
||||
self._internal_surface = None
|
||||
self._window_ratio = None
|
||||
self._window_ratio_is_calculated = False
|
||||
self._vegetation = None
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
|
@ -79,23 +72,6 @@ class ThermalBoundary:
|
|||
"""
|
||||
self._thermal_zones = value
|
||||
|
||||
# todo: do I need these two??
|
||||
@property
|
||||
def azimuth(self):
|
||||
"""
|
||||
Get the thermal boundary azimuth in radians
|
||||
:return: float
|
||||
"""
|
||||
return self.parent_surface.azimuth
|
||||
|
||||
@property
|
||||
def inclination(self):
|
||||
"""
|
||||
Get the thermal boundary inclination in radians
|
||||
:return: float
|
||||
"""
|
||||
return self.parent_surface.inclination
|
||||
|
||||
@property
|
||||
def opaque_area(self):
|
||||
"""
|
||||
|
@ -118,58 +94,6 @@ class ThermalBoundary:
|
|||
self._thickness += layer.thickness
|
||||
return self._thickness
|
||||
|
||||
@property
|
||||
def outside_solar_absorptance(self) -> Union[None, float]:
|
||||
"""
|
||||
Get thermal boundary outside solar absorptance
|
||||
:return: None or float
|
||||
"""
|
||||
return self._outside_solar_absorptance
|
||||
|
||||
@outside_solar_absorptance.setter
|
||||
def outside_solar_absorptance(self, value):
|
||||
"""
|
||||
Set thermal boundary outside solar absorptance
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._outside_solar_absorptance = float(value)
|
||||
self._outside_shortwave_reflectance = 1.0 - float(value)
|
||||
|
||||
@property
|
||||
def outside_thermal_absorptance(self) -> Union[None, float]:
|
||||
"""
|
||||
Get thermal boundary outside thermal absorptance
|
||||
:return: float
|
||||
"""
|
||||
return self._outside_thermal_absorptance
|
||||
|
||||
@outside_thermal_absorptance.setter
|
||||
def outside_thermal_absorptance(self, value):
|
||||
"""
|
||||
Set thermal boundary outside thermal absorptance
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._outside_thermal_absorptance = float(value)
|
||||
|
||||
@property
|
||||
def outside_visible_absorptance(self) -> Union[None, float]:
|
||||
"""
|
||||
Get thermal boundary outside visible absorptance
|
||||
:return: None or float
|
||||
"""
|
||||
return self._outside_visible_absorptance
|
||||
|
||||
@outside_visible_absorptance.setter
|
||||
def outside_visible_absorptance(self, value):
|
||||
"""
|
||||
Set thermal boundary outside visible absorptance
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._outside_visible_absorptance = float(value)
|
||||
|
||||
@property
|
||||
def thermal_openings(self) -> Union[None, List[ThermalOpening]]:
|
||||
"""
|
||||
|
@ -290,13 +214,16 @@ class ThermalBoundary:
|
|||
if self._u_value is None:
|
||||
h_i = self.hi
|
||||
h_e = self.he
|
||||
r_value = 1.0/h_i + 1.0/h_e
|
||||
if self.type == cte.GROUND:
|
||||
r_value = 1.0 / h_i + ch().soil_thickness / ch().soil_conductivity
|
||||
else:
|
||||
r_value = 1.0/h_i + 1.0/h_e
|
||||
try:
|
||||
for layer in self.layers:
|
||||
if layer.material.no_mass:
|
||||
r_value += float(layer.material.thermal_resistance)
|
||||
else:
|
||||
r_value = r_value + float(layer.material.conductivity) / float(layer.thickness)
|
||||
r_value += float(layer.thickness) / float(layer.material.conductivity)
|
||||
self._u_value = 1.0/r_value
|
||||
except TypeError:
|
||||
raise Exception('Constructions layers are not initialized') from TypeError
|
||||
|
@ -311,24 +238,6 @@ class ThermalBoundary:
|
|||
if value is not None:
|
||||
self._u_value = float(value)
|
||||
|
||||
@property
|
||||
def outside_shortwave_reflectance(self) -> Union[None, float]:
|
||||
"""
|
||||
Get thermal boundary external shortwave reflectance
|
||||
:return: None or float
|
||||
"""
|
||||
return self._outside_shortwave_reflectance
|
||||
|
||||
@outside_shortwave_reflectance.setter
|
||||
def outside_shortwave_reflectance(self, value):
|
||||
"""
|
||||
Set thermal boundary external shortwave reflectance
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._outside_shortwave_reflectance = float(value)
|
||||
self._outside_solar_absorptance = 1.0 - float(value)
|
||||
|
||||
@property
|
||||
def hi(self) -> Union[None, float]:
|
||||
"""
|
||||
|
@ -364,78 +273,11 @@ class ThermalBoundary:
|
|||
self._he = value
|
||||
|
||||
@property
|
||||
def virtual_internal_surface(self) -> Surface:
|
||||
def internal_surface(self) -> Surface:
|
||||
"""
|
||||
Get the internal surface of the thermal boundary
|
||||
:return: Surface
|
||||
"""
|
||||
if self._virtual_internal_surface is None:
|
||||
self._virtual_internal_surface = self.parent_surface.inverse
|
||||
return self._virtual_internal_surface
|
||||
|
||||
@property
|
||||
def inside_emissivity(self) -> Union[None, float]:
|
||||
"""
|
||||
Get the short wave emissivity factor of the thermal boundary's internal surface (-)
|
||||
:return: None or float
|
||||
"""
|
||||
return self._inside_emissivity
|
||||
|
||||
@inside_emissivity.setter
|
||||
def inside_emissivity(self, value):
|
||||
"""
|
||||
Set short wave emissivity factor of the thermal boundary's internal surface (-)
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._inside_emissivity = float(value)
|
||||
|
||||
@property
|
||||
def alpha_coefficient(self) -> Union[None, float]:
|
||||
"""
|
||||
Get the long wave emissivity factor of the thermal boundary's internal surface (-)
|
||||
:return: None or float
|
||||
"""
|
||||
return self._alpha_coefficient
|
||||
|
||||
@alpha_coefficient.setter
|
||||
def alpha_coefficient(self, value):
|
||||
"""
|
||||
Set long wave emissivity factor of the thermal boundary's internal surface (-)
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._alpha_coefficient = float(value)
|
||||
|
||||
@property
|
||||
def radiative_coefficient(self) -> Union[None, float]:
|
||||
"""
|
||||
Get the radiative coefficient of the thermal boundary's external surface (-)
|
||||
:return: None or float
|
||||
"""
|
||||
return self._radiative_coefficient
|
||||
|
||||
@radiative_coefficient.setter
|
||||
def radiative_coefficient(self, value):
|
||||
"""
|
||||
Set radiative coefficient of the thermal boundary's external surface (-)
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._radiative_coefficient = float(value)
|
||||
|
||||
@property
|
||||
def vegetation(self) -> Union[None, Vegetation]:
|
||||
"""
|
||||
Get the vegetation construction at the external surface of the thermal boundary
|
||||
:return: None or Vegetation
|
||||
"""
|
||||
return self._vegetation
|
||||
|
||||
@vegetation.setter
|
||||
def vegetation(self, value):
|
||||
"""
|
||||
Set the vegetation construction at the external surface of the thermal boundary
|
||||
:param value: Vegetation
|
||||
"""
|
||||
self._vegetation = value
|
||||
if self._internal_surface is None:
|
||||
self._internal_surface = self.parent_surface.inverse
|
||||
return self._internal_surface
|
||||
|
|
|
@ -19,19 +19,13 @@ class ThermalOpening:
|
|||
def __init__(self):
|
||||
self._id = None
|
||||
self._area = None
|
||||
self._openable_ratio = None
|
||||
self._conductivity = None
|
||||
self._frame_ratio = None
|
||||
self._g_value = None
|
||||
self._thickness = None
|
||||
self._front_side_solar_transmittance_at_normal_incidence = None
|
||||
self._back_side_solar_transmittance_at_normal_incidence = None
|
||||
self._overall_u_value = None
|
||||
self._hi = ch().convective_heat_transfer_coefficient_interior
|
||||
self._he = ch().convective_heat_transfer_coefficient_exterior
|
||||
self._inside_emissivity = None
|
||||
self._alpha_coefficient = None
|
||||
self._radiative_coefficient = None
|
||||
self._construction_name = None
|
||||
|
||||
@property
|
||||
|
@ -61,20 +55,6 @@ class ThermalOpening:
|
|||
if value is not None:
|
||||
self._area = float(value)
|
||||
|
||||
@property
|
||||
def openable_ratio(self):
|
||||
"""
|
||||
Raises not implemented error
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@openable_ratio.setter
|
||||
def openable_ratio(self, value):
|
||||
"""
|
||||
Raises not implemented error
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def conductivity(self) -> Union[None, float]:
|
||||
"""
|
||||
|
@ -96,7 +76,7 @@ class ThermalOpening:
|
|||
if self._overall_u_value is None and self.thickness is not None:
|
||||
h_i = self.hi
|
||||
h_e = self.he
|
||||
r_value = 1 / h_i + 1 / h_e + float(self._conductivity) / float(self.thickness)
|
||||
r_value = 1 / h_i + 1 / h_e + float(self.thickness) / float(self._conductivity)
|
||||
self._overall_u_value = 1 / r_value
|
||||
|
||||
@property
|
||||
|
@ -119,7 +99,7 @@ class ThermalOpening:
|
|||
@property
|
||||
def g_value(self) -> Union[None, float]:
|
||||
"""
|
||||
Get thermal opening g-value
|
||||
Get thermal opening transmittance at normal incidence
|
||||
:return: None or float
|
||||
"""
|
||||
return self._g_value
|
||||
|
@ -127,7 +107,7 @@ class ThermalOpening:
|
|||
@g_value.setter
|
||||
def g_value(self, value):
|
||||
"""
|
||||
Set thermal opening g-value
|
||||
Set thermal opening transmittance at normal incidence
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
|
@ -154,43 +134,9 @@ class ThermalOpening:
|
|||
if self._overall_u_value is None and self.conductivity is not None:
|
||||
h_i = self.hi
|
||||
h_e = self.he
|
||||
r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self._thickness)
|
||||
r_value = 1 / h_i + 1 / h_e + float(self._thickness) / float(self.conductivity)
|
||||
self._overall_u_value = 1 / r_value
|
||||
|
||||
@property
|
||||
def front_side_solar_transmittance_at_normal_incidence(self) -> Union[None, float]:
|
||||
"""
|
||||
Get thermal opening front side solar transmittance at normal incidence
|
||||
:return: None or float
|
||||
"""
|
||||
return self._front_side_solar_transmittance_at_normal_incidence
|
||||
|
||||
@front_side_solar_transmittance_at_normal_incidence.setter
|
||||
def front_side_solar_transmittance_at_normal_incidence(self, value):
|
||||
"""
|
||||
Set thermal opening front side solar transmittance at normal incidence
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._front_side_solar_transmittance_at_normal_incidence = float(value)
|
||||
|
||||
@property
|
||||
def back_side_solar_transmittance_at_normal_incidence(self) -> Union[None, float]:
|
||||
"""
|
||||
Get thermal opening back side solar transmittance at normal incidence
|
||||
:return: None or float
|
||||
"""
|
||||
return self._back_side_solar_transmittance_at_normal_incidence
|
||||
|
||||
@back_side_solar_transmittance_at_normal_incidence.setter
|
||||
def back_side_solar_transmittance_at_normal_incidence(self, value):
|
||||
"""
|
||||
Set thermal opening back side solar transmittance at normal incidence
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._back_side_solar_transmittance_at_normal_incidence = float(value)
|
||||
|
||||
@property
|
||||
def overall_u_value(self) -> Union[None, float]:
|
||||
"""
|
||||
|
@ -242,57 +188,6 @@ class ThermalOpening:
|
|||
if value is not None:
|
||||
self._he = float(value)
|
||||
|
||||
@property
|
||||
def inside_emissivity(self) -> Union[None, float]:
|
||||
"""
|
||||
Get the short wave emissivity factor of the thermal opening's internal surface (-)
|
||||
:return: None or float
|
||||
"""
|
||||
return self._inside_emissivity
|
||||
|
||||
@inside_emissivity.setter
|
||||
def inside_emissivity(self, value):
|
||||
"""
|
||||
Set short wave emissivity factor of the thermal opening's internal surface (-)
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._inside_emissivity = float(value)
|
||||
|
||||
@property
|
||||
def alpha_coefficient(self) -> Union[None, float]:
|
||||
"""
|
||||
Get the long wave emissivity factor of the thermal opening's internal surface (-)
|
||||
:return: None or float
|
||||
"""
|
||||
return self._alpha_coefficient
|
||||
|
||||
@alpha_coefficient.setter
|
||||
def alpha_coefficient(self, value):
|
||||
"""
|
||||
Set long wave emissivity factor of the thermal opening's internal surface (-)
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._alpha_coefficient = float(value)
|
||||
|
||||
@property
|
||||
def radiative_coefficient(self) -> Union[None, float]:
|
||||
"""
|
||||
Get the radiative coefficient of the thermal opening's external surface (-)
|
||||
:return: None or float
|
||||
"""
|
||||
return self._radiative_coefficient
|
||||
|
||||
@radiative_coefficient.setter
|
||||
def radiative_coefficient(self, value):
|
||||
"""
|
||||
Set radiative coefficient of the thermal opening's external surface (-)
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
self._radiative_coefficient = float(value)
|
||||
|
||||
@property
|
||||
def construction_name(self):
|
||||
"""
|
||||
|
|
|
@ -15,7 +15,6 @@ from city_model_structure.building_demand.appliances import Appliances
|
|||
from city_model_structure.building_demand.lighting import Lighting
|
||||
from city_model_structure.building_demand.internal_gain import InternalGain
|
||||
from city_model_structure.building_demand.thermal_control import ThermalControl
|
||||
from city_model_structure.energy_systems.hvac_system import HvacSystem
|
||||
from city_model_structure.attributes.schedule import Schedule
|
||||
import helpers.constants as cte
|
||||
|
||||
|
@ -53,26 +52,26 @@ class ThermalZone:
|
|||
self._appliances = None
|
||||
self._internal_gains = None
|
||||
self._thermal_control = None
|
||||
self._usage_zones = None
|
||||
self._usages = None
|
||||
|
||||
@property
|
||||
def usage_zones(self):
|
||||
# example 70-office_30-residential
|
||||
if self._usage_from_parent:
|
||||
self._usage_zones = copy.deepcopy(self._parent_internal_zone.usage_zones)
|
||||
self._usages = copy.deepcopy(self._parent_internal_zone.usage_zones)
|
||||
else:
|
||||
values = self._usage.split('_')
|
||||
usages = []
|
||||
for value in values:
|
||||
usages.append(value.split('-'))
|
||||
self._usage_zones = []
|
||||
for parent_usage_zone in self._parent_internal_zone.usage_zones:
|
||||
self._usages = []
|
||||
for parent_usage in self._parent_internal_zone.usage_zones:
|
||||
for value in usages:
|
||||
if parent_usage_zone.usage == value[1]:
|
||||
new_usage_zone = copy.deepcopy(parent_usage_zone)
|
||||
new_usage_zone.percentage = float(value[0])/100
|
||||
self._usage_zones.append(new_usage_zone)
|
||||
return self._usage_zones
|
||||
if parent_usage.usage == value[1]:
|
||||
new_usage = copy.deepcopy(parent_usage)
|
||||
new_usage.percentage = float(value[0])/100
|
||||
self._usages.append(new_usage)
|
||||
return self._usages
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
|
@ -103,7 +102,7 @@ class ThermalZone:
|
|||
@property
|
||||
def additional_thermal_bridge_u_value(self) -> Union[None, float]:
|
||||
"""
|
||||
Get thermal zone additional thermal bridge u value W/m2K
|
||||
Get thermal zone additional thermal bridge u value per footprint area W/m2K
|
||||
:return: None or float
|
||||
"""
|
||||
return self._additional_thermal_bridge_u_value
|
||||
|
@ -111,7 +110,7 @@ class ThermalZone:
|
|||
@additional_thermal_bridge_u_value.setter
|
||||
def additional_thermal_bridge_u_value(self, value):
|
||||
"""
|
||||
Set thermal zone additional thermal bridge u value W/m2K
|
||||
Set thermal zone additional thermal bridge u value per footprint area W/m2K
|
||||
:param value: float
|
||||
"""
|
||||
if value is not None:
|
||||
|
@ -208,15 +207,6 @@ class ThermalZone:
|
|||
if value is not None:
|
||||
self._ordinate_number = int(value)
|
||||
|
||||
@property
|
||||
def hvac_system(self) -> Union[None, HvacSystem]:
|
||||
"""
|
||||
Get HVAC system installed for this thermal zone
|
||||
From internal_zone
|
||||
:return: None or HvacSystem
|
||||
"""
|
||||
return self._parent_internal_zone.hvac_system
|
||||
|
||||
@property
|
||||
def view_factors_matrix(self):
|
||||
"""
|
||||
|
@ -483,6 +473,9 @@ class ThermalZone:
|
|||
* internal_gain.latent_fraction
|
||||
for usage_zone in self.usage_zones:
|
||||
for internal_gain in usage_zone.internal_gains:
|
||||
if internal_gain.schedules is None:
|
||||
_schedules_defined = False
|
||||
break
|
||||
if len(internal_gain.schedules) == 0:
|
||||
_schedules_defined = False
|
||||
break
|
||||
|
|
|
@ -7,6 +7,7 @@ Code contributors: Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|||
"""
|
||||
import uuid
|
||||
from typing import Union, List
|
||||
import helpers.constants as cte
|
||||
from city_model_structure.building_demand.occupancy import Occupancy
|
||||
from city_model_structure.building_demand.lighting import Lighting
|
||||
from city_model_structure.building_demand.appliances import Appliances
|
||||
|
@ -79,9 +80,47 @@ class UsageZone:
|
|||
@property
|
||||
def internal_gains(self) -> List[InternalGain]:
|
||||
"""
|
||||
Get usage zone internal gains
|
||||
:return: [InternalGain]
|
||||
Calculates and returns the list of all internal gains defined
|
||||
:return: InternalGains
|
||||
"""
|
||||
if self._internal_gains is None:
|
||||
if self.occupancy is not None:
|
||||
if self.occupancy.latent_internal_gain is not None:
|
||||
_internal_gain = InternalGain()
|
||||
_internal_gain.type = cte.OCCUPANCY
|
||||
_total_heat_gain = (self.occupancy.sensible_convective_internal_gain
|
||||
+ self.occupancy.sensible_radiative_internal_gain
|
||||
+ self.occupancy.latent_internal_gain)
|
||||
_internal_gain.average_internal_gain = _total_heat_gain
|
||||
_internal_gain.latent_fraction = self.occupancy.latent_internal_gain / _total_heat_gain
|
||||
_internal_gain.radiative_fraction = self.occupancy.sensible_radiative_internal_gain / _total_heat_gain
|
||||
_internal_gain.convective_fraction = self.occupancy.sensible_convective_internal_gain / _total_heat_gain
|
||||
_internal_gain.schedules = self.occupancy.occupancy_schedules
|
||||
self._internal_gains = [_internal_gain]
|
||||
if self.lighting is not None:
|
||||
_internal_gain = InternalGain()
|
||||
_internal_gain.type = cte.LIGHTING
|
||||
_internal_gain.average_internal_gain = self.lighting.density
|
||||
_internal_gain.latent_fraction = self.lighting.latent_fraction
|
||||
_internal_gain.radiative_fraction = self.lighting.radiative_fraction
|
||||
_internal_gain.convective_fraction = self.lighting.convective_fraction
|
||||
_internal_gain.schedules = self.lighting.schedules
|
||||
if self._internal_gains is not None:
|
||||
self._internal_gains.append(_internal_gain)
|
||||
else:
|
||||
self._internal_gains = [_internal_gain]
|
||||
if self.appliances is not None:
|
||||
_internal_gain = InternalGain()
|
||||
_internal_gain.type = cte.APPLIANCES
|
||||
_internal_gain.average_internal_gain = self.appliances.density
|
||||
_internal_gain.latent_fraction = self.appliances.latent_fraction
|
||||
_internal_gain.radiative_fraction = self.appliances.radiative_fraction
|
||||
_internal_gain.convective_fraction = self.appliances.convective_fraction
|
||||
_internal_gain.schedules = self.appliances.schedules
|
||||
if self._internal_gains is not None:
|
||||
self._internal_gains.append(_internal_gain)
|
||||
else:
|
||||
self._internal_gains = [_internal_gain]
|
||||
return self._internal_gains
|
||||
|
||||
@internal_gains.setter
|
||||
|
|
|
@ -17,8 +17,8 @@ class BusSystem(CityObject):
|
|||
"""
|
||||
BusSystem(CityObject) class
|
||||
"""
|
||||
def __init__(self, name, lod, surfaces, city_lower_corner):
|
||||
super().__init__(name, lod, surfaces, city_lower_corner)
|
||||
def __init__(self, name, surfaces, city_lower_corner):
|
||||
super().__init__(name, surfaces, city_lower_corner)
|
||||
self._bus_routes = None
|
||||
self._bus_network = None
|
||||
self._buses = None
|
||||
|
|
|
@ -20,6 +20,7 @@ from city_model_structure.city_objects_cluster import CityObjectsCluster
|
|||
from city_model_structure.buildings_cluster import BuildingsCluster
|
||||
from city_model_structure.fuel import Fuel
|
||||
from city_model_structure.iot.station import Station
|
||||
from city_model_structure.level_of_detail import LevelOfDetail
|
||||
from city_model_structure.machine import Machine
|
||||
from city_model_structure.parts_consisting_building import PartsConsistingBuilding
|
||||
from city_model_structure.subway_entrance import SubwayEntrance
|
||||
|
@ -59,6 +60,7 @@ class City:
|
|||
self._machines = None
|
||||
self._stations = []
|
||||
self._lca_materials = None
|
||||
self._level_of_detail = LevelOfDetail()
|
||||
|
||||
@property
|
||||
def fuels(self) -> [Fuel]:
|
||||
|
@ -426,9 +428,9 @@ class City:
|
|||
"""
|
||||
self._lca_materials = value
|
||||
|
||||
def lca_material(self, lca_id) -> LcaMaterial:
|
||||
def lca_material(self, lca_id) -> Union[LcaMaterial, None]:
|
||||
"""
|
||||
Get the lca materiol matching the given Id
|
||||
Get the lca material matching the given Id
|
||||
:return: LcaMaterial or None
|
||||
"""
|
||||
for lca_material in self.lca_materials:
|
||||
|
@ -448,3 +450,11 @@ class City:
|
|||
for city_object in city.city_objects:
|
||||
_merge_city.add_city_object(city_object)
|
||||
return _merge_city
|
||||
|
||||
@property
|
||||
def level_of_detail(self) -> LevelOfDetail:
|
||||
"""
|
||||
Get level of detail of different aspects of the city: geometry, construction and usage
|
||||
:return: LevelOfDetail
|
||||
"""
|
||||
return self._level_of_detail
|
||||
|
|
|
@ -18,11 +18,9 @@ class CityObject:
|
|||
"""
|
||||
class CityObject
|
||||
"""
|
||||
def __init__(self, name, lod, surfaces, city_lower_corner):
|
||||
def __init__(self, name, surfaces):
|
||||
self._name = name
|
||||
self._lod = lod
|
||||
self._surfaces = surfaces
|
||||
self._city_lower_corner = city_lower_corner
|
||||
self._type = None
|
||||
self._city_object_lower_corner = None
|
||||
self._detailed_polyhedron = None
|
||||
|
@ -45,15 +43,6 @@ class CityObject:
|
|||
"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def lod(self) -> int:
|
||||
"""
|
||||
Get city object level of detail 1, 2, 3 or 4
|
||||
:return: int
|
||||
"""
|
||||
lod = int(math.log(self._lod, 2) + 1)
|
||||
return lod
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
"""
|
||||
|
@ -236,3 +225,5 @@ class CityObject:
|
|||
:param value: [Sensor]
|
||||
"""
|
||||
self._sensors = value
|
||||
|
||||
|
||||
|
|
|
@ -20,8 +20,7 @@ class CityObjectsCluster(ABC, CityObject):
|
|||
self._cluster_type = cluster_type
|
||||
self._city_objects = city_objects
|
||||
self._sensors = []
|
||||
self._lod = ''
|
||||
super().__init__(name, self._lod, None, None)
|
||||
super().__init__(name, None)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
|
@ -16,8 +16,8 @@ class EnergySystem(CityObject):
|
|||
EnergySystem(CityObject) class
|
||||
"""
|
||||
|
||||
def __init__(self, name, lod, surfaces, city_lower_corner):
|
||||
super().__init__(name, lod, surfaces, city_lower_corner)
|
||||
def __init__(self, name, surfaces):
|
||||
super().__init__(name, surfaces)
|
||||
self._air_source_hp = None
|
||||
self._water_to_water_hp = None
|
||||
self._type = 'energy_system'
|
||||
|
|
|
@ -4,7 +4,8 @@ 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 Union
|
||||
from typing import Union, List
|
||||
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
||||
|
||||
|
||||
class HvacSystem:
|
||||
|
@ -13,11 +14,12 @@ class HvacSystem:
|
|||
"""
|
||||
def __init__(self):
|
||||
self._type = None
|
||||
self._thermal_zones = None
|
||||
|
||||
@property
|
||||
def type(self) -> Union[None, str]:
|
||||
"""
|
||||
Get hvac system type a thermal zone
|
||||
Get hvac system type
|
||||
:return: None or str
|
||||
"""
|
||||
return self._type
|
||||
|
@ -25,9 +27,24 @@ class HvacSystem:
|
|||
@type.setter
|
||||
def type(self, value):
|
||||
"""
|
||||
Set heating set point defined for a thermal zone
|
||||
Set hvac system type
|
||||
:param value: str
|
||||
"""
|
||||
if value is not None:
|
||||
self._type = str(value)
|
||||
|
||||
@property
|
||||
def thermal_zones(self) -> Union[None, List[ThermalZone]]:
|
||||
"""
|
||||
Get list of zones that this unit serves
|
||||
:return: None or [ThermalZone]
|
||||
"""
|
||||
return self._thermal_zones
|
||||
|
||||
@thermal_zones.setter
|
||||
def thermal_zones(self, value):
|
||||
"""
|
||||
Set list of zones that this unit serves
|
||||
:param value: [ThermalZone]
|
||||
"""
|
||||
self._thermal_zones = value
|
||||
|
|
33
city_model_structure/energy_systems/hvac_terminal_unit.py
Normal file
33
city_model_structure/energy_systems/hvac_terminal_unit.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
"""
|
||||
HvacTerminalUnit 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 Union
|
||||
|
||||
|
||||
class HvacTerminalUnit:
|
||||
"""
|
||||
HvacTerminalUnit class
|
||||
"""
|
||||
def __init__(self):
|
||||
self._type = None
|
||||
|
||||
@property
|
||||
def type(self) -> Union[None, str]:
|
||||
"""
|
||||
Get type of hvac terminal unit defined for a thermal zone
|
||||
:return: None or str
|
||||
"""
|
||||
return self._type
|
||||
|
||||
@type.setter
|
||||
def type(self, value):
|
||||
"""
|
||||
Set type of hvac terminal unit defined for a thermal zone
|
||||
:param value: str
|
||||
"""
|
||||
if value is not None:
|
||||
self._type = str(value)
|
||||
|
61
city_model_structure/level_of_detail.py
Normal file
61
city_model_structure/level_of_detail.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
"""
|
||||
Level of detail module
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
|
||||
|
||||
class LevelOfDetail:
|
||||
"""
|
||||
Level of detail for the city class
|
||||
"""
|
||||
def __init__(self):
|
||||
self._geometry = None
|
||||
self._construction = None
|
||||
self._usage = None
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
"""
|
||||
Get the city minimal geometry level of detail from 0 to 4
|
||||
:return: int
|
||||
"""
|
||||
return self._geometry
|
||||
|
||||
@geometry.setter
|
||||
def geometry(self, value):
|
||||
"""
|
||||
Set the city minimal geometry level of detail from 0 to 4
|
||||
"""
|
||||
self._geometry = value
|
||||
|
||||
@property
|
||||
def construction(self):
|
||||
"""
|
||||
Get the city minimal construction level of detail, 1 or 2
|
||||
:return: int
|
||||
"""
|
||||
return self._construction
|
||||
|
||||
@construction.setter
|
||||
def construction(self, value):
|
||||
"""
|
||||
Set the city minimal construction level of detail, 1 or 2
|
||||
"""
|
||||
self._construction = value
|
||||
|
||||
@property
|
||||
def usage(self):
|
||||
"""
|
||||
Get the city minimal usage level of detail, 1 or 2
|
||||
:return: int
|
||||
"""
|
||||
return self._usage
|
||||
|
||||
@usage.setter
|
||||
def usage(self, value):
|
||||
"""
|
||||
Set the city minimal usage level of detail, 1 or 2
|
||||
"""
|
||||
self._usage = value
|
|
@ -12,7 +12,7 @@ class SubwayEntrance(CityObject):
|
|||
SubwayEntrance(CityObject) class
|
||||
"""
|
||||
def __init__(self, name, latitude, longitude):
|
||||
super().__init__(0, [], name, [])
|
||||
super().__init__(name, 0, [])
|
||||
self._name = name
|
||||
self._latitude = latitude
|
||||
self._longitude = longitude
|
||||
|
|
|
@ -11,5 +11,10 @@ comnet_occupancy_sensible_radiant = 0.1
|
|||
comnet_plugs_latent = 0
|
||||
comnet_plugs_convective = 0.75
|
||||
comnet_plugs_radiant = 0.25
|
||||
#W/m2K
|
||||
convective_heat_transfer_coefficient_interior = 3.5
|
||||
convective_heat_transfer_coefficient_exterior = 20
|
||||
#W/mK
|
||||
soil_conductivity = 3
|
||||
#m
|
||||
soil_thickness = 0.5
|
9
data/usage/nrcan.xml
Normal file
9
data/usage/nrcan.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<nrcan base_url="https://raw.githubusercontent.com/NREL/openstudio-standards/master/lib/openstudio-standards/standards/necb/">
|
||||
<standards>
|
||||
<usage>
|
||||
<space_types_location>NECB2020/data/space_types.json</space_types_location>
|
||||
<schedules_location>NECB2015/data/schedules.json</schedules_location>
|
||||
</usage>
|
||||
</standards>
|
||||
</nrcan>
|
|
@ -7,7 +7,6 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
|
|||
Soroush Samareh Abolhassani soroush.samarehabolhassani@mail.concordia.ca
|
||||
"""
|
||||
import copy
|
||||
import math
|
||||
from pathlib import Path
|
||||
from geomeppy import IDF
|
||||
import helpers.constants as cte
|
||||
|
@ -18,33 +17,37 @@ class Idf:
|
|||
"""
|
||||
Exports city to IDF
|
||||
"""
|
||||
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
|
||||
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
|
||||
_SURFACE = 'BUILDINGSURFACE:DETAILED'
|
||||
_CONSTRUCTION = 'CONSTRUCTION'
|
||||
_MATERIAL = 'MATERIAL'
|
||||
_MATERIAL_NOMASS = 'MATERIAL:NOMASS'
|
||||
_ROUGHNESS = 'MediumRough'
|
||||
_HOURLY_SCHEDULE = 'SCHEDULE:DAY:HOURLY'
|
||||
_COMPACT_SCHEDULE = 'SCHEDULE:COMPACT'
|
||||
_FILE_SCHEDULE = 'SCHEDULE:FILE'
|
||||
_BUILDING = 'BUILDING'
|
||||
_ZONE = 'ZONE'
|
||||
_LIGHTS = 'LIGHTS'
|
||||
_PEOPLE = 'PEOPLE'
|
||||
_INFILTRATION = 'ZONEINFILTRATION:DESIGNFLOWRATE'
|
||||
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
|
||||
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
|
||||
_SURFACE = 'BUILDINGSURFACE:DETAILED'
|
||||
_SHADING = 'SHADING:BUILDING:DETAILED'
|
||||
_SHADING_PROPERTY = 'SHADINGPROPERTY:REFLECTANCE'
|
||||
_BUILDING_SURFACE = 'BuildingSurfaceDetailed'
|
||||
_CONSTRUCTION = 'CONSTRUCTION'
|
||||
_MATERIAL = 'MATERIAL'
|
||||
_MATERIAL_NOMASS = 'MATERIAL:NOMASS'
|
||||
_MATERIAL_ROOFVEGETATION = 'MATERIAL:ROOFVEGETATION'
|
||||
|
||||
_WINDOW = 'WINDOW'
|
||||
_WINDOW_MATERIAL_SIMPLE = 'WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM'
|
||||
_ROUGHNESS = 'MediumRough'
|
||||
_INFILTRATION = 'ZONEINFILTRATION:DESIGNFLOWRATE'
|
||||
|
||||
_HOURLY_SCHEDULE = 'SCHEDULE:DAY:HOURLY'
|
||||
_COMPACT_SCHEDULE = 'SCHEDULE:COMPACT'
|
||||
_FILE_SCHEDULE = 'SCHEDULE:FILE'
|
||||
_SCHEDULE_LIMIT = 'SCHEDULETYPELIMITS'
|
||||
_ON_OFF = 'On/Off'
|
||||
_FRACTION = 'Fraction'
|
||||
_ANY_NUMBER = 'Any Number'
|
||||
_CONTINUOUS = 'Continuous'
|
||||
_DISCRETE = 'Discrete'
|
||||
_BUILDING = 'BUILDING'
|
||||
_SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY'
|
||||
_LOCATION = 'SITE:LOCATION'
|
||||
_WINDOW_MATERIAL_SIMPLE = 'WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM'
|
||||
_WINDOW = 'WINDOW'
|
||||
_MATERIAL_ROOFVEGETATION = 'MATERIAL:ROOFVEGETATION'
|
||||
_SIMPLE = 'Simple'
|
||||
|
||||
idf_surfaces = {
|
||||
|
@ -53,10 +56,6 @@ class Idf:
|
|||
cte.GROUND: 'floor',
|
||||
cte.ROOF: 'roof'
|
||||
}
|
||||
idf_usage = {
|
||||
# todo: make an enum for all the usage types
|
||||
cte.RESIDENTIAL: 'residential_building'
|
||||
}
|
||||
idf_type_limits = {
|
||||
cte.ON_OFF: 'on/off',
|
||||
cte.FRACTION: 'Fraction',
|
||||
|
@ -76,22 +75,9 @@ class Idf:
|
|||
cte.WINTER_DESIGN_DAY: 'WinterDesignDay',
|
||||
cte.SUMMER_DESIGN_DAY: 'SummerDesignDay'
|
||||
}
|
||||
idf_schedule_types = {
|
||||
'compact': 'Compact',
|
||||
cte.DAY: 'Day',
|
||||
cte.WEEK: 'Week',
|
||||
cte.YEAR: 'Year',
|
||||
'file': 'File'
|
||||
}
|
||||
idf_schedule_data_type = {
|
||||
'compact': 'Compact',
|
||||
'hourly': 'Hourly',
|
||||
'daily': 'Daily',
|
||||
'interval': 'Interval',
|
||||
'list': 'List',
|
||||
}
|
||||
|
||||
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, export_type="Surfaces"):
|
||||
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, export_type="Surfaces",
|
||||
target_buildings=None, adjacent_buildings=None):
|
||||
self._city = city
|
||||
self._output_path = str(output_path.resolve())
|
||||
self._output_file = str((output_path / f'{city.name}.idf').resolve())
|
||||
|
@ -106,8 +92,15 @@ class Idf:
|
|||
Numeric_Type=self._CONTINUOUS)
|
||||
self._idf.newidfobject(self._SCHEDULE_LIMIT, Name=self._ON_OFF, Lower_Limit_Value=0, Upper_Limit_Value=1,
|
||||
Numeric_Type=self._DISCRETE)
|
||||
self._target_buildings = target_buildings
|
||||
if target_buildings is None:
|
||||
self._target_buildings = [building.name for building in self._city.buildings]
|
||||
self._adjacent_buildings = adjacent_buildings
|
||||
if self._adjacent_buildings is None:
|
||||
self._adjacent_buildings = []
|
||||
self._export()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _matrix_to_list(points, lower_corner):
|
||||
lower_x = lower_corner[0]
|
||||
|
@ -255,8 +248,8 @@ class Idf:
|
|||
|
||||
def _add_construction(self, thermal_boundary):
|
||||
for construction in self._idf.idfobjects[self._CONSTRUCTION]:
|
||||
if thermal_boundary.vegetation is not None:
|
||||
if construction.Name == f'{thermal_boundary.construction_name}_{thermal_boundary.vegetation.name}':
|
||||
if thermal_boundary.parent_surface.vegetation is not None:
|
||||
if construction.Name == f'{thermal_boundary.construction_name}_{thermal_boundary.parent_surface.vegetation.name}':
|
||||
return
|
||||
else:
|
||||
if construction.Name == thermal_boundary.construction_name:
|
||||
|
@ -271,9 +264,9 @@ class Idf:
|
|||
self._add_material(layer)
|
||||
layers = thermal_boundary.layers
|
||||
# The constructions should have at least one layer
|
||||
if thermal_boundary.vegetation is not None:
|
||||
_kwargs = {'Name': f'{thermal_boundary.construction_name}_{thermal_boundary.vegetation.name}',
|
||||
'Outside_Layer': thermal_boundary.vegetation.name}
|
||||
if thermal_boundary.parent_surface.vegetation is not None:
|
||||
_kwargs = {'Name': f'{thermal_boundary.construction_name}_{thermal_boundary.parent_surface.vegetation.name}',
|
||||
'Outside_Layer': thermal_boundary.parent_surface.vegetation.name}
|
||||
for i in range(0, len(layers) - 1):
|
||||
_kwargs[f'Layer_{i + 2}'] = layers[i].material.name
|
||||
else:
|
||||
|
@ -285,7 +278,7 @@ class Idf:
|
|||
def _add_window_construction_and_material(self, thermal_opening):
|
||||
for window_material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]:
|
||||
if window_material['UFactor'] == thermal_opening.overall_u_value and \
|
||||
window_material['Solar_Heat_Gain_Coefficient'] == thermal_opening.g_value:
|
||||
window_material['Solar_Heat_Gain_Coefficient'] == thermal_opening.g_value:
|
||||
return
|
||||
|
||||
order = str(len(self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]) + 1)
|
||||
|
@ -298,13 +291,13 @@ class Idf:
|
|||
_kwargs = {'Name': window_construction_name, 'Outside_Layer': material_name}
|
||||
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
||||
|
||||
def _add_zone(self, thermal_zone):
|
||||
def _add_zone(self, thermal_zone, name):
|
||||
for zone in self._idf.idfobjects['ZONE']:
|
||||
if zone.Name == thermal_zone.id:
|
||||
if zone.Name == name:
|
||||
return
|
||||
# todo: what do we need to define a zone in energy plus?
|
||||
self._idf.newidfobject(self._ZONE, Name=thermal_zone.id, Volume=thermal_zone.volume)
|
||||
self._add_heating_system(thermal_zone)
|
||||
self._idf.newidfobject(self._ZONE, Name=name, Volume=thermal_zone.volume)
|
||||
self._add_heating_system(thermal_zone, name)
|
||||
|
||||
def _add_thermostat(self, thermal_zone):
|
||||
thermostat_name = f'Thermostat {thermal_zone.usage}'
|
||||
|
@ -317,26 +310,26 @@ class Idf:
|
|||
Heating_Setpoint_Schedule_Name=f'Heating thermostat schedules {thermal_zone.usage}',
|
||||
Cooling_Setpoint_Schedule_Name=f'Cooling thermostat schedules {thermal_zone.usage}')
|
||||
|
||||
def _add_heating_system(self, thermal_zone):
|
||||
def _add_heating_system(self, thermal_zone, zone_name):
|
||||
for air_system in self._idf.idfobjects[self._IDEAL_LOAD_AIR_SYSTEM]:
|
||||
if air_system.Zone_Name == thermal_zone.id:
|
||||
if air_system.Zone_Name == zone_name:
|
||||
return
|
||||
thermostat = self._add_thermostat(thermal_zone)
|
||||
self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM,
|
||||
Zone_Name=thermal_zone.id,
|
||||
Zone_Name=zone_name,
|
||||
System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}',
|
||||
Heating_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}',
|
||||
Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}',
|
||||
Template_Thermostat_Name=thermostat.Name)
|
||||
|
||||
def _add_occupancy(self, thermal_zone):
|
||||
def _add_occupancy(self, thermal_zone, zone_name):
|
||||
number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area
|
||||
fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / \
|
||||
(thermal_zone.occupancy.sensible_radiative_internal_gain +
|
||||
thermal_zone.occupancy.sensible_convective_internal_gain)
|
||||
self._idf.newidfobject(self._PEOPLE,
|
||||
Name=f'{thermal_zone.id}_occupancy',
|
||||
Zone_or_ZoneList_Name=thermal_zone.id,
|
||||
Name=f'{zone_name}_occupancy',
|
||||
Zone_or_ZoneList_Name=zone_name,
|
||||
Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}',
|
||||
Number_of_People_Calculation_Method="People",
|
||||
Number_of_People=number_of_people,
|
||||
|
@ -344,21 +337,22 @@ class Idf:
|
|||
Activity_Level_Schedule_Name=f'Activity Level schedules {thermal_zone.usage}'
|
||||
)
|
||||
|
||||
def _add_infiltration(self, thermal_zone):
|
||||
def _add_infiltration(self, thermal_zone, zone_name):
|
||||
for zone in self._idf.idfobjects["ZONE"]:
|
||||
if zone.Name == f'{thermal_zone.id}_infiltration':
|
||||
if zone.Name == f'{zone_name}_infiltration':
|
||||
return
|
||||
self._idf.newidfobject(self._INFILTRATION,
|
||||
Name=f'{thermal_zone.id}_infiltration',
|
||||
Zone_or_ZoneList_Name=thermal_zone.id,
|
||||
Name=f'{zone_name}_infiltration',
|
||||
Zone_or_ZoneList_Name=zone_name,
|
||||
Schedule_Name=f'Infiltration schedules {thermal_zone.usage}',
|
||||
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
|
||||
Air_Changes_per_Hour=thermal_zone.mechanical_air_change
|
||||
)
|
||||
|
||||
def _rename_building(self, b):
|
||||
def _rename_building(self, city_name):
|
||||
name = str(str(city_name.encode("utf-8")))
|
||||
for building in self._idf.idfobjects[self._BUILDING]:
|
||||
building.Name = b.name
|
||||
building.Name = f'Buildings in {name}'
|
||||
building['Solar_Distribution'] = 'FullExterior'
|
||||
|
||||
def _remove_sizing_periods(self):
|
||||
|
@ -370,48 +364,64 @@ class Idf:
|
|||
|
||||
def _export(self):
|
||||
"""
|
||||
Export the idf file into the given path
|
||||
Export the idf file into the given path.
|
||||
|
||||
If buildings to calculate are provided, only those will appear in the output variables, otherwise all the city
|
||||
buildings will be calculated.
|
||||
If adjacent buildings are provided those buildings will be calculated, but will not appear in the output variables.
|
||||
|
||||
export type = "Surfaces|Block"
|
||||
"""
|
||||
|
||||
self._remove_location()
|
||||
self._remove_sizing_periods()
|
||||
self._rename_building(self._city.name)
|
||||
self._lod = self._city.level_of_detail.geometry
|
||||
for building in self._city.buildings:
|
||||
self._rename_building(building)
|
||||
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
self._add_construction(thermal_boundary)
|
||||
if thermal_boundary.vegetation is not None:
|
||||
self._add_vegetation_material(thermal_boundary.vegetation)
|
||||
if thermal_boundary.parent_surface.vegetation is not None:
|
||||
self._add_vegetation_material(thermal_boundary.parent_surface.vegetation)
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
self._add_window_construction_and_material(thermal_opening)
|
||||
usage = thermal_zone.usage
|
||||
self._add_infiltration_schedules(thermal_zone)
|
||||
self._add_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules)
|
||||
self._add_schedules(usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules)
|
||||
self._add_schedules(usage, 'Heating thermostat', thermal_zone.thermal_control.heating_set_point_schedules)
|
||||
self._add_schedules(usage, 'Cooling thermostat', thermal_zone.thermal_control.cooling_set_point_schedules)
|
||||
self._add_people_activity_level_schedules(thermal_zone)
|
||||
if building.name in self._target_buildings or building.name in self._adjacent_buildings:
|
||||
self._add_infiltration_schedules(thermal_zone)
|
||||
self._add_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules)
|
||||
self._add_schedules(usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules)
|
||||
self._add_schedules(usage, 'Heating thermostat', thermal_zone.thermal_control.heating_set_point_schedules)
|
||||
self._add_schedules(usage, 'Cooling thermostat', thermal_zone.thermal_control.cooling_set_point_schedules)
|
||||
self._add_people_activity_level_schedules(thermal_zone)
|
||||
|
||||
self._add_zone(thermal_zone)
|
||||
self._add_heating_system(thermal_zone)
|
||||
self._add_infiltration(thermal_zone)
|
||||
self._add_occupancy(thermal_zone)
|
||||
self._add_zone(thermal_zone, building.name)
|
||||
self._add_heating_system(thermal_zone, building.name)
|
||||
self._add_infiltration(thermal_zone, building.name)
|
||||
self._add_occupancy(thermal_zone, building.name)
|
||||
if self._export_type == "Surfaces":
|
||||
self._add_surfaces(building)
|
||||
if building.name in self._target_buildings or building.name in self._adjacent_buildings:
|
||||
self._add_surfaces(building, building.name)
|
||||
else:
|
||||
self._add_shading(building)
|
||||
else:
|
||||
self._add_block(building)
|
||||
# todo: this should change to specific variables per zone to process only the ones in the buildings_to_calculate
|
||||
for building in self._target_buildings:
|
||||
continue
|
||||
|
||||
self._idf.newidfobject(
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Zone Ideal Loads Supply Air Total Heating Energy",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Zone Ideal Loads Supply Air Total Heating Energy",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
|
||||
self._idf.newidfobject(
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Zone Ideal Loads Supply Air Total Cooling Energy",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
"OUTPUT:VARIABLE",
|
||||
Variable_Name="Zone Ideal Loads Supply Air Total Cooling Energy",
|
||||
Reporting_Frequency="Hourly",
|
||||
)
|
||||
self._idf.match()
|
||||
try:
|
||||
self._idf.intersect_match()
|
||||
|
@ -445,7 +455,25 @@ class Idf:
|
|||
break
|
||||
self._idf.intersect_match()
|
||||
|
||||
def _add_surfaces(self, building):
|
||||
def _add_shading(self, building):
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
for boundary in thermal_zone.thermal_boundaries:
|
||||
shading = self._idf.newidfobject(self._SHADING, Name=f'{boundary.parent_surface.name}')
|
||||
coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates,
|
||||
self._city.lower_corner)
|
||||
shading.setcoords(coordinates)
|
||||
solar_reflectance = 1.0 - boundary.outside_solar_absorptance
|
||||
visible_reflectance = 1.0 - boundary.outside_visible_absorptance
|
||||
self._idf.newidfobject(self._SHADING_PROPERTY,
|
||||
Shading_Surface_Name=f'{boundary.parent_surface.name}',
|
||||
Diffuse_Solar_Reflectance_of_Unglazed_Part_of_Shading_Surface=solar_reflectance,
|
||||
Diffuse_Visible_Reflectance_of_Unglazed_Part_of_Shading_Surface=visible_reflectance,
|
||||
Fraction_of_Shading_Surface_That_Is_Glazed=0)
|
||||
|
||||
# todo: Add properties for that name
|
||||
|
||||
def _add_surfaces(self, building, zone_name):
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
for boundary in thermal_zone.thermal_boundaries:
|
||||
|
@ -457,13 +485,13 @@ class Idf:
|
|||
outside_boundary_condition = 'Ground'
|
||||
sun_exposure = 'NoSun'
|
||||
wind_exposure = 'NoWind'
|
||||
if boundary.vegetation is not None:
|
||||
construction_name = f'{boundary.construction_name}_{boundary.vegetation.name}'
|
||||
if boundary.parent_surface.vegetation is not None:
|
||||
construction_name = f'{boundary.construction_name}_{boundary.parent_surface.vegetation.name}'
|
||||
else:
|
||||
construction_name = boundary.construction_name
|
||||
surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.parent_surface.name}',
|
||||
Surface_Type=idf_surface_type,
|
||||
Zone_Name=thermal_zone.id,
|
||||
Zone_Name=zone_name,
|
||||
Construction_Name=construction_name,
|
||||
Outside_Boundary_Condition=outside_boundary_condition,
|
||||
Sun_Exposure=sun_exposure,
|
||||
|
@ -472,26 +500,29 @@ class Idf:
|
|||
self._city.lower_corner)
|
||||
|
||||
surface.setcoords(coordinates)
|
||||
self._add_windows(boundary)
|
||||
|
||||
def _add_windows(self, boundary):
|
||||
for opening in boundary.thermal_openings:
|
||||
for construction in self._idf.idfobjects[self._CONSTRUCTION]:
|
||||
if construction['Outside_Layer'].split('_')[0] == 'glazing':
|
||||
window_construction = construction
|
||||
if self._compare_window_constructions(window_construction, opening):
|
||||
opening_name = 'window_' + str(len(self._idf.idfobjects[self._WINDOW]) + 1)
|
||||
opening_length = math.sqrt(opening.area)
|
||||
self._idf.newidfobject(self._WINDOW, Name=f'{opening_name}', Construction_Name=window_construction['Name'],
|
||||
Building_Surface_Name=boundary.parent_surface.name, Multiplier='1',
|
||||
Length=opening_length, Height=opening_length)
|
||||
if self._lod >= 3:
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
for boundary in thermal_zone.thermal_boundaries:
|
||||
self._add_windows_by_vertices(boundary)
|
||||
else:
|
||||
# idf only allows setting wwr for external walls
|
||||
wwr = 0
|
||||
for surface in building.surfaces:
|
||||
if surface.type == cte.WALL:
|
||||
wwr = surface.associated_thermal_boundaries[0].window_ratio
|
||||
self._idf.set_wwr(wwr, construction='glazing_1')
|
||||
|
||||
def _add_windows_by_vertices(self, boundary):
|
||||
raise NotImplementedError
|
||||
|
||||
def _compare_window_constructions(self, window_construction, opening):
|
||||
glazing = window_construction['Outside_Layer']
|
||||
for material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]:
|
||||
if material['Name'] == glazing:
|
||||
if material['UFactor'] == opening.overall_u_value and \
|
||||
material['Solar_Heat_Gain_Coefficient'] == opening.g_value:
|
||||
material['Solar_Heat_Gain_Coefficient'] == opening.g_value:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -533,5 +564,4 @@ class Idf:
|
|||
soil.residual_volumetric_moisture_content,
|
||||
Initial_Volumetric_Moisture_Content_of_the_Soil_Layer=
|
||||
soil.initial_volumetric_moisture_content,
|
||||
Moisture_Diffusion_Calculation_Method=self._SIMPLE
|
||||
)
|
||||
Moisture_Diffusion_Calculation_Method=self._SIMPLE)
|
195
exports/building_energy/insel/insel_monthly_energy_balance.py
Normal file
195
exports/building_energy/insel/insel_monthly_energy_balance.py
Normal file
|
@ -0,0 +1,195 @@
|
|||
"""
|
||||
InselMonthlyEnergyBalance exports models to insel format
|
||||
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 numpy as np
|
||||
from pathlib import Path
|
||||
|
||||
from exports.formats.insel import Insel
|
||||
from imports.weather.helpers.weather import Weather
|
||||
import helpers.constants as cte
|
||||
|
||||
_CONSTRUCTION_CODE = {
|
||||
cte.WALL: '1',
|
||||
cte.GROUND: '2',
|
||||
cte.ROOF: '3',
|
||||
cte.INTERIOR_WALL: '5',
|
||||
cte.GROUND_WALL: '6',
|
||||
cte.ATTIC_FLOOR: '7',
|
||||
cte.INTERIOR_SLAB: '8'
|
||||
}
|
||||
|
||||
|
||||
class InselMonthlyEnergyBalance(Insel):
|
||||
|
||||
def __init__(self, city, path, radiation_calculation_method='sra', weather_format='epw'):
|
||||
super().__init__(city, path)
|
||||
self._radiation_calculation_method = radiation_calculation_method
|
||||
self._weather_format = weather_format
|
||||
self._contents = []
|
||||
self._insel_files_paths = []
|
||||
for building in city.buildings:
|
||||
self._insel_files_paths.append(building.name + '.insel')
|
||||
file_name_out = building.name + '.out'
|
||||
output_path = Path(self._path / file_name_out).resolve()
|
||||
self._contents.append(self.generate_meb_template(building, output_path, self._radiation_calculation_method,
|
||||
self._weather_format))
|
||||
self._export()
|
||||
|
||||
def _export(self):
|
||||
for i_file, content in enumerate(self._contents):
|
||||
file_name = self._insel_files_paths[i_file]
|
||||
with open(Path(self._path / file_name).resolve(), 'w') as insel_file:
|
||||
insel_file.write(content)
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def generate_meb_template(building, insel_outputs_path, radiation_calculation_method, weather_format):
|
||||
file = ""
|
||||
i_block = 1
|
||||
parameters = ["1", "12", "1"]
|
||||
file = Insel._add_block(file, i_block, 'DO', parameters=parameters)
|
||||
|
||||
i_block = 4
|
||||
inputs = ["1.1", "20.1", "21.1"]
|
||||
surfaces = building.surfaces
|
||||
for i in range(1, len(surfaces) + 1):
|
||||
inputs.append(f"{str(100 + i)}.1 % Radiation surface {str(i)}")
|
||||
|
||||
# BUILDING PARAMETERS
|
||||
parameters = [f'{0.85 * building.volume} % BP(1) Heated Volume (m3)',
|
||||
f'{building.average_storey_height} % BP(2) Average storey height (m)',
|
||||
f'{building.storeys_above_ground} % BP(3) Number of storeys above ground',
|
||||
f'{building.attic_heated} % BP(4) Attic heating type (0=no room, 1=unheated, 2=heated)',
|
||||
f'{building.basement_heated} % BP(5) Cellar heating type (0=no room, 1=unheated, 2=heated, '
|
||||
f'99=invalid)']
|
||||
|
||||
# todo: this method and the insel model have to be reviewed for more than one internal zone
|
||||
internal_zone = building.internal_zones[0]
|
||||
thermal_zone = internal_zone.thermal_zones[0]
|
||||
parameters.append(f'{thermal_zone.indirectly_heated_area_ratio} % BP(6) Indirectly heated area ratio')
|
||||
parameters.append(f'{thermal_zone.effective_thermal_capacity / 3600} % BP(7) Effective heat capacity (Wh/m2K)')
|
||||
parameters.append(f'{thermal_zone.additional_thermal_bridge_u_value} '
|
||||
f'% BP(8) Additional U-value for heat bridge (Wh/m2K)')
|
||||
parameters.append('1 % BP(9) Usage type (0=standard, 1=IWU)')
|
||||
|
||||
# ZONES AND SURFACES
|
||||
parameters.append(f'{len(internal_zone.usage_zones)} % BP(10) Number of zones')
|
||||
|
||||
for i, usage_zone in enumerate(internal_zone.usage_zones):
|
||||
percentage_usage = usage_zone.percentage
|
||||
parameters.append(f'{float(internal_zone.area) * percentage_usage} % BP(11) #1 Area of zone {i + 1} (m2)')
|
||||
total_internal_gain = 0
|
||||
for ig in usage_zone.internal_gains:
|
||||
total_internal_gain += float(ig.average_internal_gain) * \
|
||||
(float(ig.convective_fraction) + float(ig.radiative_fraction))
|
||||
parameters.append(f'{total_internal_gain} % BP(12) #2 Internal gains of zone {i + 1}')
|
||||
parameters.append(f'{usage_zone.thermal_control.mean_heating_set_point} % BP(13) #3 Heating setpoint temperature '
|
||||
f'zone {i + 1} (degree Celsius)')
|
||||
parameters.append(f'{usage_zone.thermal_control.heating_set_back} % BP(14) #4 Heating setback temperature '
|
||||
f'zone {i + 1} (degree Celsius)')
|
||||
parameters.append(f'{usage_zone.thermal_control.mean_cooling_set_point} % BP(15) #5 Cooling setpoint temperature '
|
||||
f'zone {i + 1} (degree Celsius)')
|
||||
parameters.append(f'{usage_zone.hours_day} % BP(16) #6 Usage hours per day zone {i + 1}')
|
||||
parameters.append(f'{usage_zone.days_year} % BP(17) #7 Usage days per year zone {i + 1}')
|
||||
parameters.append(f'{usage_zone.mechanical_air_change} % BP(18) #8 Minimum air change rate zone {i + 1} (ACH)')
|
||||
|
||||
parameters.append(f'{len(thermal_zone.thermal_boundaries)} % Number of surfaces = BP(11+8z) \n'
|
||||
f'% 1. Surface type (1=wall, 2=ground 3=roof, 4=flat roof)\n'
|
||||
f'% 2. Areas above ground (m2)\n'
|
||||
f'% 3. Areas below ground (m2)\n'
|
||||
f'% 4. U-value (W/m2K)\n'
|
||||
f'% 5. Window area (m2)\n'
|
||||
f'% 6. Window frame fraction\n'
|
||||
f'% 7. Window U-value (W/m2K)\n'
|
||||
f'% 8. Window g-value\n'
|
||||
f'% 9. Short-wave reflectance\n'
|
||||
f'% #1 #2 #3 #4 #5 #6 #7 #8 #9\n')
|
||||
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
type_code = _CONSTRUCTION_CODE[thermal_boundary.type]
|
||||
window_area = thermal_boundary.opaque_area * thermal_boundary.window_ratio / (1 - thermal_boundary.window_ratio)
|
||||
|
||||
parameters.append(type_code)
|
||||
if thermal_boundary.type != cte.GROUND:
|
||||
parameters.append(thermal_boundary.opaque_area + window_area)
|
||||
parameters.append('0.0')
|
||||
else:
|
||||
parameters.append('0.0')
|
||||
parameters.append(thermal_boundary.opaque_area + window_area)
|
||||
parameters.append(thermal_boundary.u_value)
|
||||
parameters.append(window_area)
|
||||
|
||||
if window_area <= 0.001:
|
||||
parameters.append(0.0)
|
||||
parameters.append(0.0)
|
||||
parameters.append(0.0)
|
||||
else:
|
||||
thermal_opening = thermal_boundary.thermal_openings[0]
|
||||
parameters.append(thermal_opening.frame_ratio)
|
||||
parameters.append(thermal_opening.overall_u_value)
|
||||
parameters.append(thermal_opening.g_value)
|
||||
if thermal_boundary.type is not cte.GROUND:
|
||||
parameters.append(thermal_boundary.parent_surface.short_wave_reflectance)
|
||||
else:
|
||||
parameters.append(0.0)
|
||||
|
||||
file = Insel._add_block(file, i_block, 'd18599', inputs=inputs, parameters=parameters)
|
||||
|
||||
i_block = 20
|
||||
inputs = ['1']
|
||||
parameters = ['12 % Monthly ambient temperature (degree Celsius)']
|
||||
|
||||
external_temperature = building.external_temperature[cte.MONTH]
|
||||
|
||||
for i in range(0, len(external_temperature)):
|
||||
parameters.append(f'{i + 1} {external_temperature.at[i, weather_format]}')
|
||||
|
||||
file = Insel._add_block(file, i_block, 'polyg', inputs=inputs, parameters=parameters)
|
||||
|
||||
i_block = 21
|
||||
inputs = ['1']
|
||||
parameters = ['12 % Monthly sky temperature']
|
||||
|
||||
sky_temperature = Weather.sky_temperature(external_temperature[[weather_format]].to_numpy().T[0])
|
||||
for i, temperature in enumerate(sky_temperature):
|
||||
parameters.append(f'{i + 1} {temperature}')
|
||||
|
||||
file = Insel._add_block(file, i_block, 'polyg', inputs=inputs, parameters=parameters)
|
||||
|
||||
for i, surface in enumerate(surfaces):
|
||||
i_block = 101 + i
|
||||
inputs = ['1 % Monthly surface radiation (W/m2)']
|
||||
parameters = [f'12 % Azimuth {np.rad2deg(surface.azimuth)}, '
|
||||
f'inclination {np.rad2deg(surface.inclination)} (degrees)']
|
||||
|
||||
if surface.type != 'Ground':
|
||||
global_irradiance = surface.global_irradiance[cte.MONTH]
|
||||
for j in range(0, len(global_irradiance)):
|
||||
parameters.append(f'{j + 1} {global_irradiance.at[j, radiation_calculation_method]}')
|
||||
else:
|
||||
for j in range(0, 12):
|
||||
parameters.append(f'{j + 1} 0.0')
|
||||
|
||||
file = Insel._add_block(file, i_block, 'polyg', inputs=inputs, parameters=parameters)
|
||||
|
||||
i_block = 300
|
||||
inputs = ['4.1', '4.2']
|
||||
file = Insel._add_block(file, i_block, 'cum', inputs=inputs)
|
||||
|
||||
i_block = 303
|
||||
inputs = ['300.1', '300.2']
|
||||
file = Insel._add_block(file, i_block, 'atend', inputs=inputs)
|
||||
|
||||
i_block = 310
|
||||
inputs = ['4.1', '4.2']
|
||||
parameters = ['1 % Mode',
|
||||
'0 % Suppress FNQ inputs',
|
||||
f"'{str(insel_outputs_path)}' % File name",
|
||||
"'*' % Fortran format"]
|
||||
file = Insel._add_block(file, i_block, 'WRITE', inputs=inputs, parameters=parameters)
|
||||
|
||||
return file
|
74
exports/energy_building_exports_factory.py
Normal file
74
exports/energy_building_exports_factory.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
"""
|
||||
EnergyBuildingsExportsFactory exports a city into several formats related to energy in buildings
|
||||
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 pathlib import Path
|
||||
from exports.building_energy.energy_ade import EnergyAde
|
||||
from exports.building_energy.idf import Idf
|
||||
from exports.building_energy.insel.insel_monthly_energy_balance import InselMonthlyEnergyBalance
|
||||
|
||||
|
||||
class EnergyBuildingsExportsFactory:
|
||||
"""
|
||||
Energy Buildings exports factory class
|
||||
"""
|
||||
def __init__(self, export_type, city, path, target_buildings=None, adjacent_buildings=None):
|
||||
self._city = city
|
||||
self._export_type = '_' + export_type.lower()
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
self._path = path
|
||||
self._target_buildings = target_buildings
|
||||
self._adjacent_buildings = adjacent_buildings
|
||||
|
||||
@property
|
||||
def _energy_ade(self):
|
||||
"""
|
||||
Export to citygml with application domain extensions
|
||||
:return: None
|
||||
"""
|
||||
return EnergyAde(self._city, self._path)
|
||||
|
||||
@property
|
||||
def _idf(self):
|
||||
"""
|
||||
Export the city to Energy+ idf format
|
||||
|
||||
When target_buildings is set, only those will be calculated and their energy consumption output, non adjacent
|
||||
buildings will be considered shading objects and adjacent buildings will be considered adiabatic.
|
||||
|
||||
Adjacent buildings are provided they will be considered heated so energy plus calculations are more precise but
|
||||
no results will be calculated to speed up the calculation process.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
idf_data_path = (Path(__file__).parent / './building_energy/idf_files/').resolve()
|
||||
# todo: create a get epw file function based on the city
|
||||
weather_path = (Path(__file__).parent / '../data/weather/epw/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve()
|
||||
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path,
|
||||
target_buildings=self._target_buildings, adjacent_buildings=self._adjacent_buildings)
|
||||
|
||||
@property
|
||||
def _insel_monthly_energy_balance(self):
|
||||
"""
|
||||
Export to Insel MonthlyEnergyBalance
|
||||
:return: None
|
||||
"""
|
||||
return InselMonthlyEnergyBalance(self._city, self._path)
|
||||
|
||||
def export(self):
|
||||
"""
|
||||
Export the city given to the class using the given export type handler
|
||||
:return: None
|
||||
"""
|
||||
return getattr(self, self._export_type, lambda: None)
|
||||
|
||||
def export_debug(self):
|
||||
"""
|
||||
Export the city given to the class using the given export type handler
|
||||
:return: None
|
||||
"""
|
||||
return getattr(self, self._export_type)
|
|
@ -194,7 +194,7 @@ class HeatPumpExport:
|
|||
self._input_data["HPDisactivationTemperature"] = self._input_data["HPSupTemp"] - 2
|
||||
self._input_data["HPReactivationTemperature"] = self._input_data["HPSupTemp"] - 4
|
||||
|
||||
# compute maximum demand. TODO: This should come from catalog in the future
|
||||
# compute maximum demand. todo: This should come from catalog in the future
|
||||
max_demand = self._compute_max_demand()
|
||||
# compute TESCapacity
|
||||
self._input_data["TESCapacity"] = self._input_data["HoursOfStorageAtMaxDemand"] * (max_demand * 3.6) / (
|
||||
|
|
|
@ -6,8 +6,6 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from exports.formats.energy_ade import EnergyAde
|
||||
from exports.formats.idf import Idf
|
||||
from exports.formats.obj import Obj
|
||||
from exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
|
||||
from exports.formats.stl import Stl
|
||||
|
@ -17,13 +15,14 @@ class ExportsFactory:
|
|||
"""
|
||||
Exports factory class
|
||||
"""
|
||||
def __init__(self, export_type, city, path, target_buildings=None):
|
||||
def __init__(self, export_type, city, path, target_buildings=None, adjacent_buildings=None):
|
||||
self._city = city
|
||||
self._export_type = '_' + export_type.lower()
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
self._path = path
|
||||
self._target_buildings = target_buildings
|
||||
self._adjacent_buildings = adjacent_buildings
|
||||
|
||||
@property
|
||||
def _citygml(self):
|
||||
|
@ -37,14 +36,6 @@ class ExportsFactory:
|
|||
def _collada(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def _energy_ade(self):
|
||||
"""
|
||||
Export to citygml with application domain extensions
|
||||
:return: None
|
||||
"""
|
||||
return EnergyAde(self._city, self._path)
|
||||
|
||||
@property
|
||||
def _stl(self):
|
||||
"""
|
||||
|
@ -69,17 +60,6 @@ class ExportsFactory:
|
|||
"""
|
||||
return Obj(self._city, self._path).to_ground_points()
|
||||
|
||||
@property
|
||||
def _idf(self):
|
||||
"""
|
||||
Export the city to Energy+ idf format
|
||||
:return: None
|
||||
"""
|
||||
idf_data_path = (Path(__file__).parent / './formats/idf_files/').resolve()
|
||||
# todo: create a get epw file function based on the city
|
||||
weather_path = (Path(__file__).parent / '../data/weather/epw/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve()
|
||||
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path)
|
||||
|
||||
@property
|
||||
def _sra(self):
|
||||
"""
|
||||
|
@ -101,4 +81,4 @@ class ExportsFactory:
|
|||
Export the city given to the class using the given export type handler
|
||||
:return: None
|
||||
"""
|
||||
return getattr(self, self._export_type)
|
||||
return Obj(self._city, self._path)
|
||||
|
|
30
exports/formats/insel.py
Normal file
30
exports/formats/insel.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""
|
||||
Insel export models to insel format
|
||||
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 abc import ABC
|
||||
|
||||
|
||||
class Insel(ABC):
|
||||
def __init__(self, city, path):
|
||||
self._city = city
|
||||
self._path = path
|
||||
self._results = None
|
||||
|
||||
@staticmethod
|
||||
def _add_block(file, block_number, block_type, inputs='', parameters=''):
|
||||
file += "S " + str(block_number) + " " + block_type + "\n"
|
||||
for block_input in inputs:
|
||||
file += str(block_input) + "\n"
|
||||
if len(parameters) > 0:
|
||||
file += "P " + str(block_number) + "\n"
|
||||
for block_parameter in parameters:
|
||||
file += str(block_parameter) + "\n"
|
||||
return file
|
||||
|
||||
def _export(self):
|
||||
raise NotImplementedError
|
||||
|
|
@ -26,7 +26,7 @@ class Obj(Triangular):
|
|||
file_name_out = self._city.name + '_ground.' + self._triangular_format
|
||||
file_path_in = (Path(self._path).resolve() / file_name_in).resolve()
|
||||
file_path_out = (Path(self._path).resolve() / file_name_out).resolve()
|
||||
scene = GeometryFactory('obj', file_path_in).scene
|
||||
scene = GeometryFactory('obj', path=file_path_in).scene
|
||||
scene.rezero()
|
||||
obj_file = trimesh.exchange.obj.export_obj(scene)
|
||||
with open(file_path_out, 'w') as file:
|
||||
|
|
|
@ -111,7 +111,7 @@ class ConfigurationHelper:
|
|||
def convective_heat_transfer_coefficient_interior(self) -> float:
|
||||
"""
|
||||
Get configured convective heat transfer coefficient for surfaces inside the building
|
||||
:return: 3.5
|
||||
:return: 3.5 W/m2K
|
||||
"""
|
||||
return self._config.getfloat('buildings', 'convective_heat_transfer_coefficient_interior').real
|
||||
|
||||
|
@ -119,6 +119,22 @@ class ConfigurationHelper:
|
|||
def convective_heat_transfer_coefficient_exterior(self) -> float:
|
||||
"""
|
||||
Get configured convective heat transfer coefficient for surfaces outside the building
|
||||
:return: 20
|
||||
:return: 20 W/m2K
|
||||
"""
|
||||
return self._config.getfloat('buildings', 'convective_heat_transfer_coefficient_exterior').real
|
||||
|
||||
@property
|
||||
def soil_conductivity(self) -> float:
|
||||
"""
|
||||
Get configured soil conductivity for surfaces touching the ground
|
||||
:return: 3 W/mK
|
||||
"""
|
||||
return self._config.getfloat('buildings', 'soil_conductivity').real
|
||||
|
||||
@property
|
||||
def soil_thickness(self) -> float:
|
||||
"""
|
||||
Get configured soil thickness for surfaces touching the ground
|
||||
:return: 0.5
|
||||
"""
|
||||
return self._config.getfloat('buildings', 'soil_thickness').real
|
||||
|
|
|
@ -6,7 +6,6 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|||
"""
|
||||
|
||||
# universal constants
|
||||
import sys
|
||||
|
||||
KELVIN = 273.15
|
||||
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
"""
|
||||
Enrich city
|
||||
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 imports.construction_factory import ConstructionFactory
|
||||
from imports.usage_factory import UsageFactory
|
||||
from imports.schedules_factory import SchedulesFactory
|
||||
|
||||
|
||||
class EnrichCity:
|
||||
"""
|
||||
Enrich city
|
||||
"""
|
||||
|
||||
def __init__(self, city):
|
||||
self._city = city
|
||||
self._enriched_city = None
|
||||
self._errors = []
|
||||
|
||||
@property
|
||||
def errors(self) -> [str]:
|
||||
"""
|
||||
Error list
|
||||
"""
|
||||
return self._errors
|
||||
|
||||
def enriched_city(self, construction_format=None, usage_format=None, schedules_format=None,
|
||||
pickle_construction=False, pickle_usage=False, pickle_schedules=False):
|
||||
"""
|
||||
Enrich the city with the given formats
|
||||
:return: City
|
||||
"""
|
||||
if self._enriched_city is None:
|
||||
self._errors = []
|
||||
print('original:', len(self._city.buildings))
|
||||
if not pickle_construction:
|
||||
if construction_format is not None:
|
||||
self._enriched_city = self._construction(construction_format)
|
||||
if len(self._errors) != 0:
|
||||
return self._enriched_city
|
||||
if not pickle_usage:
|
||||
if usage_format is not None:
|
||||
self._enriched_city = self._usage(usage_format)
|
||||
if len(self._errors) != 0:
|
||||
return self._enriched_city
|
||||
if not pickle_schedules:
|
||||
if schedules_format is not None:
|
||||
self._enriched_city = self._schedules(schedules_format)
|
||||
if len(self._errors) != 0:
|
||||
return self._enriched_city
|
||||
self._enriched_city = self._city
|
||||
return self._enriched_city
|
||||
|
||||
def _construction(self, construction_format):
|
||||
ConstructionFactory(construction_format, self._city).enrich()
|
||||
|
||||
for building in self._city.buildings:
|
||||
# infiltration_rate_system_off is a mandatory parameter.
|
||||
# If it is not returned, extract the building from the calculation list
|
||||
if len(building.thermal_zones) == 0:
|
||||
self._city.remove_city_object(building)
|
||||
elif building.thermal_zones[0].infiltration_rate_system_off is None:
|
||||
self._city.remove_city_object(building)
|
||||
if self._city.buildings is None:
|
||||
self._errors.append('no archetype found per construction')
|
||||
self._enriched_city = self._city
|
||||
return self._enriched_city
|
||||
print('enriched with construction:', len(self._city.buildings))
|
||||
return self._city
|
||||
|
||||
def _usage(self, usage_format):
|
||||
UsageFactory(usage_format, self._city).enrich()
|
||||
for building in self._city.buildings:
|
||||
# At least one thermal zone must be created.
|
||||
# If it is not created, extract the building from the calculation list
|
||||
if len(building.usage_zones) <= 0:
|
||||
self._city.remove_city_object(building)
|
||||
if self._city.buildings is None:
|
||||
self._errors.append('no archetype found per usage')
|
||||
self._enriched_city = self._city
|
||||
return self._enriched_city
|
||||
print('enriched with usage:', len(self._city.buildings))
|
||||
return self._city
|
||||
|
||||
def _schedules(self, schedules_format):
|
||||
SchedulesFactory(schedules_format, self._city).enrich()
|
||||
for building in self._city.buildings:
|
||||
counter_schedules = 0
|
||||
for usage_zone in building.usage_zones:
|
||||
# At least one schedule must be created at each thermal zone.
|
||||
# If it is not created, extract the building from the calculation list
|
||||
if len(usage_zone.schedules) > 0:
|
||||
counter_schedules += 1
|
||||
if counter_schedules < len(building.usage_zones):
|
||||
self._city.remove_city_object(building)
|
||||
if self._city.buildings is None:
|
||||
self._errors.append('no archetype found per usage')
|
||||
self._enriched_city = self._city
|
||||
return self._enriched_city
|
||||
print('enriched with occupancy:', len(self._city.buildings))
|
||||
return self._city
|
|
@ -20,6 +20,9 @@ class GeometryHelper:
|
|||
"""
|
||||
Geometry helper class
|
||||
"""
|
||||
srs_transformations = {
|
||||
'urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH': 'epsg:25832'
|
||||
}
|
||||
|
||||
def __init__(self, delta=0, area_delta=0):
|
||||
self._delta = delta
|
||||
|
|
47
helpers/monthly_values.py
Normal file
47
helpers/monthly_values.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
Monthly values
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2020 Project Author Pilar Monsalvete pilar_monsalvete@yahoo.es
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import calendar as cal
|
||||
|
||||
|
||||
class MonthlyValues:
|
||||
def __init__(self):
|
||||
self._month_hour = None
|
||||
|
||||
def get_mean_values(self, values):
|
||||
out = None
|
||||
if values is not None:
|
||||
if 'month' not in values.columns:
|
||||
values = pd.concat([self.month_hour, pd.DataFrame(values)], axis=1)
|
||||
out = values.groupby('month', as_index=False).mean()
|
||||
del out['month']
|
||||
return out
|
||||
|
||||
def get_total_month(self, values):
|
||||
out = None
|
||||
if values is not None:
|
||||
if 'month' not in values.columns:
|
||||
values = pd.concat([self.month_hour, pd.DataFrame(values)], axis=1)
|
||||
out = pd.DataFrame(values).groupby('month', as_index=False).sum()
|
||||
del out['month']
|
||||
return out
|
||||
|
||||
@property
|
||||
def month_hour(self):
|
||||
"""
|
||||
returns a DataFrame that has x values of the month number (January = 1, February = 2...),
|
||||
being x the number of hours of the corresponding month
|
||||
:return: DataFrame(int)
|
||||
"""
|
||||
array = []
|
||||
for i in range(0, 12):
|
||||
days_of_month = cal.monthrange(2015, i+1)[1]
|
||||
total_hours = days_of_month * 24
|
||||
array = np.concatenate((array, np.full(total_hours, i + 1)))
|
||||
self._month_hour = pd.DataFrame(array, columns=['month'])
|
||||
return self._month_hour
|
|
@ -60,7 +60,7 @@ class BuildingArchetype:
|
|||
@property
|
||||
def additional_thermal_bridge_u_value(self):
|
||||
"""
|
||||
Get archetype's additional U value due to thermal bridges in W/m2K
|
||||
Get archetype's additional U value due to thermal bridges per area of shell in W/m2K
|
||||
:return: float
|
||||
"""
|
||||
return self._additional_thermal_bridge_u_value
|
||||
|
|
|
@ -103,7 +103,7 @@ class ConstructionHelper:
|
|||
:param city: str
|
||||
:return: str
|
||||
"""
|
||||
# ToDo: Dummy function that needs to be implemented
|
||||
# todo: Dummy function that needs to be implemented
|
||||
reference_city = 'Baltimore'
|
||||
if city is not None:
|
||||
reference_city = 'Baltimore'
|
||||
|
|
|
@ -99,7 +99,7 @@ class StoreysGeneration:
|
|||
polygon = Polygon(coordinates)
|
||||
ceiling = Surface(polygon, polygon, surface_type=cte.INTERIOR_SLAB)
|
||||
surfaces_child.append(ceiling)
|
||||
volume = ceiling.area_above_ground * height
|
||||
volume = ceiling.perimeter_area * height
|
||||
total_volume += volume
|
||||
storeys.append(Storey(name, surfaces_child, neighbours, volume, self._internal_zone, self._floor_area))
|
||||
name = 'storey_' + str(number_of_storeys - 1)
|
||||
|
|
|
@ -29,7 +29,6 @@ class UsPhysicsParameters(NrelPhysicsInterface):
|
|||
"""
|
||||
Returns the city with the construction parameters assigned to the buildings
|
||||
"""
|
||||
# todo: erase
|
||||
city = self._city
|
||||
for building in city.buildings:
|
||||
try:
|
||||
|
@ -40,7 +39,8 @@ class UsPhysicsParameters(NrelPhysicsInterface):
|
|||
f'and climate zone reference norm {self._climate_zone}\n')
|
||||
return
|
||||
|
||||
# if building has no thermal zones defined from geometry, one thermal zone per storey is assigned
|
||||
# if building has no thermal zones defined from geometry, and the building will be divided in storeys,
|
||||
# one thermal zone per storey is assigned
|
||||
if len(building.internal_zones) == 1:
|
||||
if building.internal_zones[0].thermal_zones is None:
|
||||
self._create_storeys(building, archetype, self._divide_in_storeys)
|
||||
|
@ -74,7 +74,7 @@ class UsPhysicsParameters(NrelPhysicsInterface):
|
|||
if (str(function) == str(building_archetype.function)) and \
|
||||
(climate_zone == str(building_archetype.climate_zone)):
|
||||
return building_archetype
|
||||
return None
|
||||
raise KeyError('archetype not found')
|
||||
|
||||
@staticmethod
|
||||
def _search_construction_in_archetype(archetype, construction_type):
|
||||
|
@ -121,9 +121,13 @@ class UsPhysicsParameters(NrelPhysicsInterface):
|
|||
thermal_boundary.layers.append(layer)
|
||||
# The agreement is that the layers are defined from outside to inside
|
||||
external_layer = construction_archetype.layers[0]
|
||||
thermal_boundary.outside_solar_absorptance = external_layer.material.solar_absorptance
|
||||
thermal_boundary.outside_thermal_absorptance = external_layer.material.thermal_absorptance
|
||||
thermal_boundary.outside_visible_absorptance = external_layer.material.visible_absorptance
|
||||
external_surface = thermal_boundary.parent_surface
|
||||
external_surface.short_wave_reflectance = 1 - float(external_layer.material.solar_absorptance)
|
||||
external_surface.long_wave_emittance = 1 - float(external_layer.material.solar_absorptance)
|
||||
internal_layer = construction_archetype.layers[len(construction_archetype.layers) - 1]
|
||||
internal_surface = thermal_boundary.internal_surface
|
||||
internal_surface.short_wave_reflectance = 1 - float(internal_layer.material.solar_absorptance)
|
||||
internal_surface.long_wave_emittance = 1 - float(internal_layer.material.solar_absorptance)
|
||||
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
if construction_archetype.window is not None:
|
||||
|
|
|
@ -24,6 +24,7 @@ class ConstructionFactory:
|
|||
Enrich the city by using NREL information
|
||||
"""
|
||||
UsPhysicsParameters(self._city, self._base_path).enrich_buildings()
|
||||
self._city.level_of_detail.construction = 2
|
||||
|
||||
def enrich(self):
|
||||
"""
|
||||
|
@ -31,3 +32,10 @@ class ConstructionFactory:
|
|||
:return: None
|
||||
"""
|
||||
getattr(self, self._handler, lambda: None)()
|
||||
|
||||
def enrich_debug(self):
|
||||
"""
|
||||
Enrich the city given to the class using the class given handler
|
||||
:return: None
|
||||
"""
|
||||
UsPhysicsParameters(self._city, self._base_path).enrich_buildings()
|
|
@ -82,7 +82,7 @@ class AirSourceHeatPumpParameters:
|
|||
heat_pump.heating_comp_power = h_data[1]
|
||||
heat_pump.heating_capacity_coff = self._compute_coefficients(h_data)
|
||||
|
||||
energy_system = EnergySystem('{} capacity heat pump'.format(heat_pump.model), 0, [], None)
|
||||
energy_system = EnergySystem('{} capacity heat pump'.format(heat_pump.model), [])
|
||||
energy_system.air_source_hp = heat_pump
|
||||
self._city.add_city_object(energy_system)
|
||||
return self._city
|
||||
|
|
|
@ -129,7 +129,7 @@ class WaterToWaterHPParameters:
|
|||
heat_pump.entering_water_temp = data['ewt']
|
||||
heat_pump.leaving_water_temp = data['lwt']
|
||||
heat_pump.power_demand_coff = self._compute_coefficients(data)
|
||||
energy_system = EnergySystem(heat_pump.model, 0, [], None)
|
||||
energy_system = EnergySystem(heat_pump.model, [])
|
||||
energy_system.water_to_water_hp = heat_pump
|
||||
self._city.add_city_object(energy_system)
|
||||
return self._city
|
||||
|
|
|
@ -21,10 +21,14 @@ class CityGml:
|
|||
"""
|
||||
CityGml class
|
||||
"""
|
||||
def __init__(self, path):
|
||||
def __init__(self, path, extrusion_height_field=None, year_of_construction_field=None, function_field=None):
|
||||
self._city = None
|
||||
self._lod = None
|
||||
self._lod1_tags = ['lod1Solid', 'lod1MultiSurface']
|
||||
self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve']
|
||||
self._extrusion_height_field = extrusion_height_field
|
||||
self._year_of_construction_field = year_of_construction_field
|
||||
self._function_field = function_field
|
||||
self._lower_corner = None
|
||||
self._upper_corner = None
|
||||
with open(path) as gml:
|
||||
|
@ -64,6 +68,9 @@ class CityGml:
|
|||
self._upper_corner = np.fromstring(envelope['upperCorner'], dtype=float, sep=' ')
|
||||
if '@srsName' in envelope:
|
||||
self._srs_name = envelope['@srsName']
|
||||
else:
|
||||
# If not coordinate system given assuming hub standard
|
||||
self._srs_name = "EPSG:26911"
|
||||
else:
|
||||
# get the boundary from the city objects instead
|
||||
for city_object_member in self._gml['CityModel']['cityObjectMember']:
|
||||
|
@ -74,8 +81,6 @@ class CityGml:
|
|||
continue
|
||||
envelope = bound['Envelope']
|
||||
self._srs_name = envelope['@srsName']
|
||||
lower_corner = None
|
||||
upper_corner = None
|
||||
if '#text' in envelope['lowerCorner']:
|
||||
lower_corner = np.fromstring(envelope['lowerCorner']['#text'], dtype=float, sep=' ')
|
||||
upper_corner = np.fromstring(envelope['upperCorner']['#text'], dtype=float, sep=' ')
|
||||
|
@ -110,14 +115,16 @@ class CityGml:
|
|||
if 'function' in city_object:
|
||||
function = city_object['function']
|
||||
if any(key in city_object for key in self._lod1_tags):
|
||||
lod = 1
|
||||
if self._lod is None or self._lod > 1:
|
||||
self._lod = 1
|
||||
surfaces = CityGmlLod1(city_object).surfaces
|
||||
elif any(key in city_object for key in self._lod2_tags):
|
||||
lod = 2
|
||||
if self._lod is None or self._lod > 2:
|
||||
self._lod = 2
|
||||
surfaces = CityGmlLod2(city_object).surfaces
|
||||
else:
|
||||
raise NotImplementedError("Not supported level of detail")
|
||||
return Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None)
|
||||
return Building(name, surfaces, year_of_construction, function, terrains=None)
|
||||
|
||||
def _create_parts_consisting_building(self, city_object):
|
||||
name = city_object['@id']
|
||||
|
@ -143,4 +150,5 @@ class CityGml:
|
|||
self._city.add_city_objects_cluster(self._create_parts_consisting_building(city_object))
|
||||
else:
|
||||
self._city.add_city_object(self._create_building(city_object))
|
||||
self._city.level_of_detail.geometry = self._lod
|
||||
return self._city
|
||||
|
|
|
@ -24,18 +24,6 @@ class CityGmlBase(ABC):
|
|||
"""
|
||||
return self._surfaces
|
||||
|
||||
@staticmethod
|
||||
def _remove_last_point(points):
|
||||
array = points.split(' ')
|
||||
res = " "
|
||||
return res.join(array[0:len(array) - 3])
|
||||
|
||||
@staticmethod
|
||||
def _solid_points(coordinates) -> np.ndarray:
|
||||
solid_points = np.fromstring(coordinates, dtype=float, sep=' ')
|
||||
solid_points = GeometryHelper.to_points_matrix(solid_points)
|
||||
return solid_points
|
||||
|
||||
@classmethod
|
||||
def _solid(cls, city_object_member):
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -6,6 +6,7 @@ Copyright © 2022 Concordia CERC group
|
|||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
|
||||
from imports.geometry.helpers.geometry_helper import GeometryHelper
|
||||
from imports.geometry.citygml_classes.citygml_base import CityGmlBase
|
||||
from city_model_structure.building_demand.surface import Surface
|
||||
from city_model_structure.attributes.polygon import Polygon
|
||||
|
@ -37,19 +38,20 @@ class CityGmlLod1(CityGmlBase):
|
|||
def _solid(cls, city_object_member):
|
||||
try:
|
||||
solid_points = [
|
||||
CityGmlBase._solid_points(CityGmlBase._remove_last_point(s['Polygon']['exterior']['LinearRing']['posList']
|
||||
['#text']))
|
||||
GeometryHelper.points_from_string(GeometryHelper.remove_last_point_from_string(
|
||||
s['Polygon']['exterior']['LinearRing']['posList']['#text']))
|
||||
for s in city_object_member['lod1Solid']['Solid']['exterior']['CompositeSurface']['surfaceMember']]
|
||||
except TypeError:
|
||||
solid_points = [
|
||||
CityGmlBase._solid_points(CityGmlBase._remove_last_point(s['Polygon']['exterior']['LinearRing']['posList']))
|
||||
GeometryHelper.points_from_string(GeometryHelper.remove_last_point_from_string(
|
||||
s['Polygon']['exterior']['LinearRing']['posList']))
|
||||
for s in city_object_member['lod1Solid']['Solid']['exterior']['CompositeSurface']['surfaceMember']]
|
||||
|
||||
return [Surface(Polygon(sp), Polygon(sp)) for sp in solid_points]
|
||||
|
||||
@classmethod
|
||||
def _multi_surface(cls, city_object_member):
|
||||
solid_points = [CityGmlBase._solid_points(CityGmlBase._remove_last_point(s['Polygon']['exterior']['LinearRing']
|
||||
['posList']))
|
||||
solid_points = [GeometryHelper.points_from_string(GeometryHelper.remove_last_point_from_string(
|
||||
s['Polygon']['exterior']['LinearRing']['posList']))
|
||||
for s in city_object_member['Building']['lod1MultiSurface']['MultiSurface']['surfaceMember']]
|
||||
return [Surface(Polygon(sp), Polygon(sp)) for sp in solid_points]
|
||||
|
|
|
@ -60,11 +60,19 @@ class CityGmlLod2(CityGmlBase):
|
|||
|
||||
@classmethod
|
||||
def _add_member_surface(cls, member, surface_type):
|
||||
if '@srsDimension' in member['Polygon']['exterior']['LinearRing']['posList']:
|
||||
pos_name = 'posList'
|
||||
if pos_name not in member['Polygon']['exterior']['LinearRing']:
|
||||
pos_name = 'pos'
|
||||
if '@srsDimension' in member['Polygon']['exterior']['LinearRing'][pos_name]:
|
||||
gml_points = member['Polygon']['exterior']['LinearRing']['posList']["#text"]
|
||||
else:
|
||||
gml_points = member['Polygon']['exterior']['LinearRing']['posList']
|
||||
solid_points = cls._solid_points(cls._remove_last_point(gml_points))
|
||||
gml_points = member['Polygon']['exterior']['LinearRing'][pos_name]
|
||||
if pos_name == 'pos':
|
||||
gml_points_string = ''
|
||||
for gml_point in gml_points:
|
||||
gml_points_string = f'{gml_points_string} {gml_point}'
|
||||
gml_points = gml_points_string.lstrip(' ')
|
||||
solid_points = GeometryHelper.points_from_string(GeometryHelper.remove_last_point_from_string(gml_points))
|
||||
polygon = Polygon(solid_points)
|
||||
return Surface(polygon, polygon, surface_type=GeometryHelper.gml_surface_to_libs(surface_type))
|
||||
|
||||
|
|
149
imports/geometry/geojson.py
Normal file
149
imports/geometry/geojson.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
"""
|
||||
Geojson module parses geojson files and import the geometry into the city model structure
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guillermo Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||
"""
|
||||
import json
|
||||
|
||||
import trimesh.creation
|
||||
import numpy as np
|
||||
|
||||
from pyproj import Transformer
|
||||
from shapely.geometry import Polygon as ShapelyPolygon
|
||||
|
||||
import helpers.constants as cte
|
||||
from imports.geometry.helpers.geometry_helper import GeometryHelper
|
||||
from city_model_structure.attributes.polygon import Polygon
|
||||
from city_model_structure.building import Building
|
||||
from city_model_structure.building_demand.surface import Surface
|
||||
from city_model_structure.city import City
|
||||
|
||||
|
||||
class Geojson:
|
||||
"""
|
||||
Geojson class
|
||||
"""
|
||||
X = 0
|
||||
Y = 1
|
||||
|
||||
def __init__(self, path, extrusion_height_field=None, year_of_construction_field=None, function_field=None):
|
||||
# todo: destination epsg should change according actual the location
|
||||
self._transformer = Transformer.from_crs('epsg:4326', 'epsg:26911')
|
||||
self._min_x = cte.MAX_FLOAT
|
||||
self._min_y = cte.MAX_FLOAT
|
||||
self._max_x = cte.MIN_FLOAT
|
||||
self._max_y = cte.MIN_FLOAT
|
||||
self._max_z = 0
|
||||
self._city = None
|
||||
self._extrusion_height_field = extrusion_height_field
|
||||
self._year_of_construction_field = year_of_construction_field
|
||||
self._function_field = function_field
|
||||
with open(path) as json_file:
|
||||
self._geojson = json.loads(json_file.read())
|
||||
|
||||
def _save_bounds(self, x, y):
|
||||
if x > self._max_x:
|
||||
self._max_x = x
|
||||
if x < self._min_x:
|
||||
self._min_x = x
|
||||
if y > self._max_y:
|
||||
self._max_y = y
|
||||
if y < self._min_y:
|
||||
self._min_y = y
|
||||
|
||||
@staticmethod
|
||||
def _create_buildings_lod0(name, year_of_construction, function, surfaces_coordinates):
|
||||
surfaces = []
|
||||
buildings = []
|
||||
for zone, surface_coordinates in enumerate(surfaces_coordinates):
|
||||
points = GeometryHelper.points_from_string(GeometryHelper.remove_last_point_from_string(surface_coordinates))
|
||||
polygon = Polygon(points)
|
||||
surfaces.append(Surface(polygon, polygon))
|
||||
buildings.append(Building(f'{name}_zone_{zone}', surfaces, year_of_construction, function))
|
||||
return buildings
|
||||
|
||||
@staticmethod
|
||||
def _create_buildings_lod1(name, year_of_construction, function, height, surface_coordinates):
|
||||
lod0_buildings = Geojson._create_buildings_lod0(name, year_of_construction, function, surface_coordinates)
|
||||
surfaces = []
|
||||
buildings = []
|
||||
for zone, lod0_building in enumerate(lod0_buildings):
|
||||
for surface in lod0_building.surfaces:
|
||||
shapely_polygon = ShapelyPolygon(surface.solid_polygon.coordinates)
|
||||
if not shapely_polygon.is_valid:
|
||||
print(surface.solid_polygon.area)
|
||||
print('error?', name, surface_coordinates)
|
||||
continue
|
||||
mesh = trimesh.creation.extrude_polygon(shapely_polygon, height)
|
||||
for face in mesh.faces:
|
||||
points = []
|
||||
for vertex_index in face:
|
||||
points.append(mesh.vertices[vertex_index])
|
||||
polygon = Polygon(points)
|
||||
surface = Surface(polygon, polygon)
|
||||
surfaces.append(surface)
|
||||
buildings.append(Building(f'{name}_zone_{zone}', surfaces, year_of_construction, function))
|
||||
return buildings
|
||||
|
||||
def _get_polygons(self, polygons, coordinates):
|
||||
if type(coordinates[0][self.X]) != float:
|
||||
polygons = []
|
||||
for element in coordinates:
|
||||
polygons = self._get_polygons(polygons, element)
|
||||
return polygons
|
||||
else:
|
||||
transformed_coordinates = ''
|
||||
for coordinate in coordinates:
|
||||
transformed = self._transformer.transform(coordinate[self.Y], coordinate[self.X])
|
||||
self._save_bounds(transformed[self.X], transformed[self.Y])
|
||||
transformed_coordinates = f'{transformed_coordinates} {transformed[self.X]} {transformed[self.Y]} 0.0'
|
||||
polygons.append(transformed_coordinates.lstrip(' '))
|
||||
return polygons
|
||||
|
||||
@property
|
||||
def city(self) -> City:
|
||||
"""
|
||||
Get city out of a Geojson file
|
||||
"""
|
||||
if self._city is None:
|
||||
buildings = []
|
||||
building_id = 0
|
||||
for feature in self._geojson['features']:
|
||||
extrusion_height = 0
|
||||
if self._extrusion_height_field is not None:
|
||||
extrusion_height = float(feature['properties'][self._extrusion_height_field])
|
||||
year_of_construction = None
|
||||
if self._year_of_construction_field is not None:
|
||||
year_of_construction = int(feature['properties'][self._year_of_construction_field])
|
||||
function = None
|
||||
if self._function_field is not None:
|
||||
function = feature['properties'][self._function_field]
|
||||
geometry = feature['geometry']
|
||||
if 'id' in feature:
|
||||
building_name = feature['id']
|
||||
else:
|
||||
building_name = f'building_{building_id}'
|
||||
building_id += 1
|
||||
polygons = []
|
||||
for part, coordinates in enumerate(geometry['coordinates']):
|
||||
polygons = self._get_polygons(polygons, coordinates)
|
||||
for zone, polygon in enumerate(polygons):
|
||||
if extrusion_height == 0:
|
||||
buildings = buildings + Geojson._create_buildings_lod0(f'{building_name}_part_{part}_zone{zone}',
|
||||
year_of_construction,
|
||||
function,
|
||||
[polygon])
|
||||
else:
|
||||
if self._max_z < extrusion_height:
|
||||
self._max_z = extrusion_height
|
||||
buildings = buildings + Geojson._create_buildings_lod1(f'{building_name}_part_{part}',
|
||||
year_of_construction,
|
||||
function,
|
||||
extrusion_height,
|
||||
[polygon])
|
||||
|
||||
self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911')
|
||||
for building in buildings:
|
||||
self._city.add_city_object(building)
|
||||
return self._city
|
115
imports/geometry/gpandas.py
Normal file
115
imports/geometry/gpandas.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
"""
|
||||
gpandas module parses geopandas input table and import the geometry into the city model structure
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder: Milad Aghamohamadnia --- milad.aghamohamadnia@concordia.ca
|
||||
"""
|
||||
|
||||
import trimesh
|
||||
import trimesh.exchange.load
|
||||
import trimesh.geometry
|
||||
import trimesh.creation
|
||||
import trimesh.repair
|
||||
from shapely.geometry import Point
|
||||
from shapely.geometry import Polygon as ShapelyPoly
|
||||
from trimesh import Scene
|
||||
|
||||
from city_model_structure.attributes.polygon import Polygon
|
||||
from city_model_structure.building import Building
|
||||
from city_model_structure.building_demand.surface import Surface
|
||||
from city_model_structure.city import City
|
||||
|
||||
import helpers.constants as cte
|
||||
|
||||
|
||||
class GPandas:
|
||||
"""
|
||||
GeoPandas class
|
||||
"""
|
||||
|
||||
def __init__(self, dataframe, srs_name='EPSG:26911'):
|
||||
"""_summary_
|
||||
Arguments:
|
||||
dataframe {Geopandas.Dataframe} -- input geometry data in geopandas table
|
||||
Keyword Arguments:
|
||||
srs_name {str} -- coordinate system of coordinate system (default: {'EPSG:26911'})
|
||||
"""
|
||||
|
||||
self._srs_name = srs_name
|
||||
self._city = None
|
||||
self._scene = dataframe
|
||||
self._scene = self._scene.to_crs(self._srs_name)
|
||||
min_x, min_y, max_x, max_y = self._scene.total_bounds
|
||||
self._lower_corner = [min_x, min_y, 0]
|
||||
self._upper_corner = [max_x, max_y, 0]
|
||||
|
||||
@property
|
||||
def scene(self) -> Scene:
|
||||
"""
|
||||
Get GeoPandas scene
|
||||
"""
|
||||
return self._scene
|
||||
|
||||
@property
|
||||
def city(self) -> City:
|
||||
"""
|
||||
Get city out of a GeoPandas Table
|
||||
"""
|
||||
if self._city is None:
|
||||
self._city = City(self._lower_corner, self._upper_corner, self._srs_name)
|
||||
for scene_index, bldg in self._scene.iterrows():
|
||||
geometry = bldg.geom
|
||||
polygon = ShapelyPoly(geometry['coordinates'][0])
|
||||
height = float(bldg['height'])
|
||||
building_mesh = trimesh.creation.extrude_polygon(polygon, height)
|
||||
trimesh.repair.fill_holes(building_mesh)
|
||||
trimesh.repair.fix_winding(building_mesh)
|
||||
year_of_construction = int(bldg['year_built'])
|
||||
name = str(scene_index)
|
||||
lod = 1
|
||||
if year_of_construction > 2000:
|
||||
function = cte.RESIDENTIAL
|
||||
else:
|
||||
function = cte.INDUSTRY
|
||||
|
||||
surfaces = []
|
||||
for face_index, face in enumerate(building_mesh.faces):
|
||||
points = []
|
||||
for vertex_index in face:
|
||||
points.append(building_mesh.vertices[vertex_index])
|
||||
solid_polygon = Polygon(points)
|
||||
perimeter_polygon = solid_polygon
|
||||
surface = Surface(solid_polygon, perimeter_polygon)
|
||||
surfaces.append(surface)
|
||||
building = Building(name, surfaces, year_of_construction, function, terrains=None)
|
||||
self._city.add_city_object(building)
|
||||
self._city.level_of_detail.geometry = lod
|
||||
return self._city
|
||||
|
||||
@staticmethod
|
||||
def resize_polygon(poly, factor=0.10, expand=False) -> ShapelyPoly:
|
||||
"""
|
||||
returns the shapely polygon which is smaller or bigger by passed factor.
|
||||
Arguments:
|
||||
poly {shapely.geometry.Polygon} -- an input geometry in shapely polygon format
|
||||
|
||||
Keyword Arguments:
|
||||
factor {float} -- factor of expansion (default: {0.10})
|
||||
expand {bool} -- If expand = True , then it returns bigger polygon, else smaller (default: {False})
|
||||
|
||||
Returns:
|
||||
{shapely.geometry.Polygon} -- output geometry in shapely polygon format
|
||||
"""
|
||||
xs = list(poly.exterior.coords.xy[0])
|
||||
ys = list(poly.exterior.coords.xy[1])
|
||||
x_center = 0.5 * min(xs) + 0.5 * max(xs)
|
||||
y_center = 0.5 * min(ys) + 0.5 * max(ys)
|
||||
min_corner = Point(min(xs), min(ys))
|
||||
center = Point(x_center, y_center)
|
||||
shrink_distance = center.distance(min_corner) * factor
|
||||
|
||||
if expand:
|
||||
poly_resized = poly.buffer(shrink_distance) # expand
|
||||
else:
|
||||
poly_resized = poly.buffer(-shrink_distance) # shrink
|
||||
return poly_resized
|
|
@ -6,6 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|||
"""
|
||||
|
||||
import helpers.constants as cte
|
||||
import numpy as np
|
||||
|
||||
|
||||
class GeometryHelper:
|
||||
|
@ -303,3 +304,15 @@ class GeometryHelper:
|
|||
if surface == 'GroundSurface':
|
||||
return 'Ground'
|
||||
return 'Roof'
|
||||
|
||||
@staticmethod
|
||||
def points_from_string(coordinates) -> np.ndarray:
|
||||
points = np.fromstring(coordinates, dtype=float, sep=' ')
|
||||
points = GeometryHelper.to_points_matrix(points)
|
||||
return points
|
||||
|
||||
@staticmethod
|
||||
def remove_last_point_from_string(points):
|
||||
array = points.split(' ')
|
||||
res = " "
|
||||
return res.join(array[0:len(array) - 3])
|
||||
|
|
|
@ -77,6 +77,7 @@ class Obj:
|
|||
perimeter_polygon = solid_polygon
|
||||
surface = Surface(solid_polygon, perimeter_polygon)
|
||||
surfaces.append(surface)
|
||||
building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None)
|
||||
building = Building(name, surfaces, year_of_construction, function, terrains=None)
|
||||
self._city.add_city_object(building)
|
||||
self._city.level_of_detail.geometry = lod
|
||||
return self._city
|
||||
|
|
|
@ -101,7 +101,7 @@ class Rhino:
|
|||
if face is None:
|
||||
break
|
||||
hub_surfaces = hub_surfaces + self._add_face(face)
|
||||
building = Building(name, 3, hub_surfaces, 'unknown', 'unknown', (self._min_x, self._min_y, self._min_z), [])
|
||||
building = Building(name, hub_surfaces, 'unknown', 'unknown', [])
|
||||
city_objects.append(building)
|
||||
lower_corner = (self._min_x, self._min_y, self._min_z)
|
||||
upper_corner = (self._max_x, self._max_y, self._max_z)
|
||||
|
@ -133,4 +133,5 @@ class Rhino:
|
|||
|
||||
for building in buildings:
|
||||
city.add_city_object(building)
|
||||
city.level_of_detail.geometry = 3
|
||||
return city
|
||||
|
|
|
@ -5,20 +5,32 @@ Copyright © 2022 Concordia CERC group
|
|||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
|
||||
import geopandas
|
||||
from city_model_structure.city import City
|
||||
from imports.geometry.citygml import CityGml
|
||||
from imports.geometry.obj import Obj
|
||||
from imports.geometry.osm_subway import OsmSubway
|
||||
from imports.geometry.rhino import Rhino
|
||||
from imports.geometry.gpandas import GPandas
|
||||
from imports.geometry.geojson import Geojson
|
||||
|
||||
|
||||
class GeometryFactory:
|
||||
"""
|
||||
GeometryFactory class
|
||||
"""
|
||||
def __init__(self, file_type, path):
|
||||
def __init__(self, file_type,
|
||||
path=None,
|
||||
data_frame=None,
|
||||
height_field=None,
|
||||
year_of_construction_field=None,
|
||||
function_field=None):
|
||||
self._file_type = '_' + file_type.lower()
|
||||
self._path = path
|
||||
self._data_frame = data_frame
|
||||
self._height_field = height_field
|
||||
self._year_of_construction_field = year_of_construction_field
|
||||
self._function_field = function_field
|
||||
|
||||
@property
|
||||
def _citygml(self) -> City:
|
||||
|
@ -26,7 +38,7 @@ class GeometryFactory:
|
|||
Enrich the city by using CityGML information as data source
|
||||
:return: City
|
||||
"""
|
||||
return CityGml(self._path).city
|
||||
return CityGml(self._path, self._height_field, self._year_of_construction_field, self._function_field).city
|
||||
|
||||
@property
|
||||
def _obj(self) -> City:
|
||||
|
@ -35,6 +47,24 @@ class GeometryFactory:
|
|||
:return: City
|
||||
"""
|
||||
return Obj(self._path).city
|
||||
|
||||
@property
|
||||
def _gpandas(self) -> City:
|
||||
"""
|
||||
Enrich the city by using GeoPandas information as data source
|
||||
:return: City
|
||||
"""
|
||||
if self._data_frame is None:
|
||||
self._data_frame = geopandas.read_file(self._path)
|
||||
return GPandas(self._data_frame).city
|
||||
|
||||
@property
|
||||
def _geojson(self) -> City:
|
||||
"""
|
||||
Enrich the city by using Geojson information as data source
|
||||
:return: City
|
||||
"""
|
||||
return Geojson(self._path, self._height_field, self._year_of_construction_field, self._function_field).city
|
||||
|
||||
@property
|
||||
def _osm_subway(self) -> City:
|
||||
|
@ -66,4 +96,4 @@ class GeometryFactory:
|
|||
Enrich the city given to the class using the class given handler
|
||||
:return: City
|
||||
"""
|
||||
return CityGml(self._path).city
|
||||
return Geojson(self._path, self._height_field, self._year_of_construction_field, self._function_field).city
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
"""
|
||||
Schedules retrieve the specific usage schedules module for the given standard
|
||||
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 pandas as pd
|
||||
from imports.schedules.helpers.schedules_helper import SchedulesHelper
|
||||
from city_model_structure.attributes.schedule import Schedule
|
||||
import helpers.constants as cte
|
||||
|
||||
|
||||
class ComnetSchedules:
|
||||
"""
|
||||
Comnet based schedules
|
||||
"""
|
||||
def __init__(self, city, base_path):
|
||||
self._city = city
|
||||
self._comnet_schedules_path = base_path / 'comnet_archetypes.xlsx'
|
||||
xls = pd.ExcelFile(self._comnet_schedules_path)
|
||||
for building in city.buildings:
|
||||
for usage_zone in building.usage_zones:
|
||||
schedules = []
|
||||
usage_schedules = pd.read_excel(xls,
|
||||
sheet_name=SchedulesHelper.comnet_from_usage(usage_zone.usage),
|
||||
skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA")
|
||||
number_of_schedule_types = 13
|
||||
schedules_per_schedule_type = 3
|
||||
day_types = dict({'week_day': 0, 'saturday': 1, 'sunday': 2})
|
||||
for schedule_types in range(0, number_of_schedule_types):
|
||||
name = ''
|
||||
data_type = ''
|
||||
for schedule_day in range(0, schedules_per_schedule_type):
|
||||
schedule = Schedule()
|
||||
schedule.time_step = cte.HOUR
|
||||
schedule.time_range = cte.DAY
|
||||
row_cells = usage_schedules.iloc[schedules_per_schedule_type*schedule_types + schedule_day]
|
||||
if schedule_day == day_types['week_day']:
|
||||
name = row_cells[0]
|
||||
data_type = row_cells[1]
|
||||
schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY]
|
||||
elif schedule_day == day_types['saturday']:
|
||||
schedule.day_types = [cte.SATURDAY]
|
||||
else:
|
||||
schedule.day_types = [cte.SUNDAY]
|
||||
schedule.type = name
|
||||
schedule.data_type = SchedulesHelper.data_type_from_comnet(data_type)
|
||||
if schedule.data_type == cte.ANY_NUMBER:
|
||||
values = []
|
||||
for cell in row_cells[schedules_per_schedule_type:].to_numpy():
|
||||
values.append((float(cell) - 32.) * 5 / 9)
|
||||
schedule.values = values
|
||||
else:
|
||||
schedule.values = row_cells[schedules_per_schedule_type:].to_numpy()
|
||||
schedules.append(schedule)
|
||||
usage_zone.schedules = schedules
|
|
@ -1,149 +0,0 @@
|
|||
"""
|
||||
Building test
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Gutierrez Morote Guillermo.GutierrezMorote@concordia.ca
|
||||
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import parseidf
|
||||
import xmltodict
|
||||
from imports.schedules.helpers.schedules_helper import SchedulesHelper
|
||||
from city_model_structure.attributes.schedule import Schedule
|
||||
from city_model_structure.building_demand.occupancy import Occupancy
|
||||
from city_model_structure.building_demand.lighting import Lighting
|
||||
from city_model_structure.building_demand.thermal_control import ThermalControl
|
||||
import helpers.constants as cte
|
||||
|
||||
|
||||
class DoeIdf:
|
||||
"""
|
||||
Idf factory to import schedules into the data model
|
||||
"""
|
||||
# todo: shouldn't this be in the schedule helper class????
|
||||
idf_schedule_to_standard_schedule = {'BLDG_LIGHT_SCH': cte.LIGHTING,
|
||||
'BLDG_OCC_SCH_wo_SB': cte.OCCUPANCY,
|
||||
'BLDG_EQUIP_SCH': cte.EQUIPMENT,
|
||||
'ACTIVITY_SCH': cte.ACTIVITY,
|
||||
'INFIL_QUARTER_ON_SCH': cte.INFILTRATION}
|
||||
|
||||
# todo: @Guille -> in idf the types can be written in capital letters or low case, but both should be accepted.
|
||||
# How is that solved?? Of course not like this
|
||||
idf_data_type_to_standard_data_type = {'Fraction': cte.FRACTION,
|
||||
'fraction': cte.FRACTION,
|
||||
'Any Number': cte.ANY_NUMBER,
|
||||
'ON/OFF': cte.ON_OFF,
|
||||
'On/Off': cte.ON_OFF,
|
||||
'Temperature': cte.TEMPERATURE,
|
||||
'Humidity': cte.HUMIDITY,
|
||||
'Control Type': cte.CONTROL_TYPE}
|
||||
|
||||
_SCHEDULE_COMPACT_TYPE = 'SCHEDULE:COMPACT'
|
||||
_SCHEDULE_TYPE_NAME = 1
|
||||
_SCHEDULE_TYPE_DATA_TYPE = 2
|
||||
|
||||
def __init__(self, city, base_path, doe_idf_file):
|
||||
self._hours = []
|
||||
panda_hours = pd.timedelta_range(0, periods=24, freq='H')
|
||||
for _, hour in enumerate(panda_hours):
|
||||
self._hours.append(str(hour).replace('0 days ', '').replace(':00:00', ':00'))
|
||||
self._city = city
|
||||
|
||||
path = str(base_path / doe_idf_file)
|
||||
with open(path) as xml:
|
||||
self._schedule_library = xmltodict.parse(xml.read())
|
||||
|
||||
for building in self._city.buildings:
|
||||
for internal_zone in building.internal_zones:
|
||||
for usage_zone in internal_zone.usage_zones:
|
||||
for schedule_archetype in self._schedule_library['archetypes']['archetypes']:
|
||||
function = schedule_archetype['@building_type']
|
||||
if SchedulesHelper.usage_from_function(function) == usage_zone.usage:
|
||||
self._idf_schedules_path = (base_path / schedule_archetype['idf']['path']).resolve()
|
||||
with open(self._idf_schedules_path, 'r') as file:
|
||||
idf = parseidf.parse(file.read())
|
||||
self._load_schedule(idf, usage_zone)
|
||||
break
|
||||
|
||||
def _load_schedule(self, idf, usage_zone):
|
||||
schedules_day = {}
|
||||
schedules = []
|
||||
for compact_schedule in idf[self._SCHEDULE_COMPACT_TYPE]:
|
||||
schedule = Schedule()
|
||||
schedule.time_step = cte.HOUR
|
||||
schedule.time_range = cte.DAY
|
||||
if compact_schedule[self._SCHEDULE_TYPE_NAME] in self.idf_schedule_to_standard_schedule:
|
||||
schedule.type = self.idf_schedule_to_standard_schedule[compact_schedule[self._SCHEDULE_TYPE_NAME]]
|
||||
schedule.data_type = self.idf_data_type_to_standard_data_type[compact_schedule[self._SCHEDULE_TYPE_DATA_TYPE]]
|
||||
else:
|
||||
continue
|
||||
|
||||
days_index = []
|
||||
days_schedules = []
|
||||
for position, elements in enumerate(compact_schedule):
|
||||
element_title = elements.title().replace('For: ', '')
|
||||
if elements.title() != element_title:
|
||||
days_index.append(position)
|
||||
# store a cleaned version of the compact schedule
|
||||
days_schedules.append(element_title)
|
||||
days_index.append(len(days_schedules))
|
||||
|
||||
# save schedule
|
||||
values = []
|
||||
for i, day_index in enumerate(days_index):
|
||||
if day_index == len(days_schedules):
|
||||
break
|
||||
schedules_day[f'{days_schedules[day_index]}'] = []
|
||||
hour_index = 0
|
||||
for hours_values in range(day_index + 1, days_index[i + 1] - 1, 2):
|
||||
# Create 24h sequence
|
||||
for index, hour in enumerate(self._hours[hour_index:]):
|
||||
hour_formatted = days_schedules[hours_values].replace("Until: ", "")
|
||||
if len(hour_formatted) == 4:
|
||||
hour_formatted = f'0{hour_formatted}'
|
||||
if hour == hour_formatted:
|
||||
hour_index += index
|
||||
break
|
||||
entry = days_schedules[hours_values + 1]
|
||||
schedules_day[f'{days_schedules[day_index]}'].append(entry)
|
||||
|
||||
for entry in schedules_day[f'{days_schedules[day_index]}']:
|
||||
values.append(entry)
|
||||
|
||||
schedule.values = values
|
||||
|
||||
if 'Weekdays' in days_schedules[day_index]:
|
||||
schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY]
|
||||
|
||||
elif 'Alldays' in days_schedules[day_index]:
|
||||
schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY,
|
||||
cte.SUNDAY]
|
||||
|
||||
elif 'Weekends' in days_schedules[day_index]:
|
||||
# Weekends schedule present so let's re set the values
|
||||
schedule.day_types = [cte.SATURDAY, cte.SUNDAY]
|
||||
|
||||
elif 'Saturday' in days_schedules[day_index]:
|
||||
schedule.day_types = [cte.SATURDAY]
|
||||
|
||||
elif 'Sunday' in days_schedules[day_index]:
|
||||
schedule.day_types = [cte.SUNDAY]
|
||||
|
||||
else:
|
||||
continue
|
||||
schedules.append(schedule)
|
||||
|
||||
for schedule in schedules:
|
||||
if schedule.type == cte.OCCUPANCY:
|
||||
if usage_zone.occupancy is None:
|
||||
usage_zone.occupancy = Occupancy()
|
||||
usage_zone.occupancy.occupancy_schedules = [schedule]
|
||||
elif schedule.type == cte.LIGHTING:
|
||||
if usage_zone.lighting is None:
|
||||
usage_zone.lighting = Lighting()
|
||||
usage_zone.lighting.schedules = [schedule]
|
||||
elif schedule.type == cte.HVAC_AVAILABILITY:
|
||||
if usage_zone.thermal_control is None:
|
||||
usage_zone.thermal_control = ThermalControl()
|
||||
usage_zone.thermal_control.hvac_availability_schedules = [schedule]
|
|
@ -1,39 +0,0 @@
|
|||
"""
|
||||
SchedulesFactory retrieve the specific schedules module for the given standard
|
||||
This factory can only be called after calling the usage factory so the usage zones are created.
|
||||
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 imports.schedules.doe_idf import DoeIdf
|
||||
|
||||
|
||||
class SchedulesFactory:
|
||||
"""
|
||||
SchedulesFactor class
|
||||
"""
|
||||
def __init__(self, handler, city, base_path=Path(Path(__file__).parent.parent / 'data/schedules')):
|
||||
self._handler = '_' + handler.lower().replace(' ', '_')
|
||||
self._city = city
|
||||
self._base_path = base_path
|
||||
for building in city.buildings:
|
||||
for internal_zone in building.internal_zones:
|
||||
if len(internal_zone.usage_zones) == 0:
|
||||
raise Exception('It seems that the schedule factory is being called before the usage factory. '
|
||||
'Please ensure that the usage factory is called first as the usage zones must be '
|
||||
'firstly generated.')
|
||||
|
||||
def _doe_idf(self):
|
||||
"""
|
||||
Enrich the city by using DOE IDF schedules as data source
|
||||
"""
|
||||
DoeIdf(self._city, self._base_path, 'doe_idf.xml')
|
||||
|
||||
def enrich(self):
|
||||
"""
|
||||
Enrich the city given to the class using the given schedule handler
|
||||
:return: None
|
||||
"""
|
||||
getattr(self, self._handler, lambda: None)()
|
|
@ -14,7 +14,7 @@ import helpers.constants as cte
|
|||
from helpers.configuration_helper import ConfigurationHelper as ch
|
||||
from imports.geometry.helpers.geometry_helper import GeometryHelper
|
||||
from imports.usage.helpers.usage_helper import UsageHelper
|
||||
from imports.schedules.helpers.schedules_helper import SchedulesHelper
|
||||
from imports.usage.helpers.schedules_helper import SchedulesHelper
|
||||
from city_model_structure.building_demand.usage_zone import UsageZone
|
||||
from city_model_structure.building_demand.lighting import Lighting
|
||||
from city_model_structure.building_demand.occupancy import Occupancy
|
||||
|
@ -42,8 +42,8 @@ class ComnetUsageParameters:
|
|||
"""
|
||||
number_usage_types = 33
|
||||
xl_file = pd.ExcelFile(self._base_path)
|
||||
file_data = pd.read_excel(xl_file, sheet_name="Modeling Data", skiprows=[0, 1, 2],
|
||||
nrows=number_usage_types, usecols="A:AB")
|
||||
file_data = pd.read_excel(xl_file, sheet_name="Modeling Data", usecols="A:AB", skiprows=[0, 1, 2],
|
||||
nrows=number_usage_types)
|
||||
|
||||
lighting_data = {}
|
||||
plug_loads_data = {}
|
||||
|
@ -109,8 +109,8 @@ class ComnetUsageParameters:
|
|||
|
||||
schedules_usage = UsageHelper.schedules_key(data['schedules_key'][comnet_usage][0])
|
||||
|
||||
_extracted_data = pd.read_excel(schedules_data, sheet_name=schedules_usage,
|
||||
skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA")
|
||||
_extracted_data = pd.read_excel(schedules_data, sheet_name=schedules_usage, usecols="A:AA", skiprows=[0, 1, 2, 3],
|
||||
nrows=39)
|
||||
schedules = []
|
||||
number_of_schedule_types = 13
|
||||
schedules_per_schedule_type = 3
|
||||
|
|
|
@ -26,12 +26,14 @@ class UsageFactory:
|
|||
"""
|
||||
Enrich the city with HFT usage library
|
||||
"""
|
||||
self._city.level_of_detail.usage = 2
|
||||
return HftUsageParameters(self._city, self._base_path).enrich_buildings()
|
||||
|
||||
def _comnet(self):
|
||||
"""
|
||||
Enrich the city with COMNET usage library
|
||||
"""
|
||||
self._city.level_of_detail.usage = 2
|
||||
return ComnetUsageParameters(self._city, self._base_path).enrich_buildings()
|
||||
|
||||
def enrich(self):
|
||||
|
|
|
@ -19,3 +19,6 @@ pyecore==0.12.2
|
|||
python-dotenv
|
||||
SQLAlchemy
|
||||
bcrypt==4.0.1
|
||||
shapely
|
||||
geopandas
|
||||
triangle
|
||||
|
|
|
@ -23,7 +23,7 @@ class TestCityMerge(TestCase):
|
|||
|
||||
def _get_citygml(self, file):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
city = GeometryFactory('citygml', file_path).city
|
||||
city = GeometryFactory('citygml', path=file_path).city
|
||||
self.assertIsNotNone(city, 'city is none')
|
||||
return city
|
||||
|
||||
|
|
|
@ -26,114 +26,20 @@ class TestConstructionFactory(TestCase):
|
|||
|
||||
def _get_citygml(self, file):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
self._city = GeometryFactory('citygml', file_path).city
|
||||
self._city = GeometryFactory('citygml', path=file_path).city
|
||||
self.assertIsNotNone(self._city, 'city is none')
|
||||
self.assertIsNotNone(self._city.level_of_detail.geometry, 'wrong construction level of detail')
|
||||
return self._city
|
||||
|
||||
def _check_buildings(self, city):
|
||||
for building in city.buildings:
|
||||
self.assertIsNotNone(building.name, 'building name is none')
|
||||
self.assertIsNotNone(building.lod, 'building lod is none')
|
||||
self.assertIsNotNone(building.type, 'building type is none')
|
||||
self.assertIsNotNone(building.volume, 'building volume is none')
|
||||
self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none')
|
||||
self.assertIsNotNone(building.simplified_polyhedron, 'building simplified polyhedron is none')
|
||||
self.assertIsNotNone(building.surfaces, 'building surfaces is none')
|
||||
self.assertIsNotNone(building.centroid, 'building centroid is none')
|
||||
self.assertIsNotNone(building.max_height, 'building max_height is none')
|
||||
self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated')
|
||||
self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated')
|
||||
self.assertEqual(len(building.diffuse), 0, 'building diffuse is calculated')
|
||||
self.assertEqual(len(building.beam), 0, 'building beam is calculated')
|
||||
self.assertIsNotNone(building.lower_corner, 'building lower corner is none')
|
||||
self.assertEqual(len(building.sensors), 0, 'building sensors are assigned')
|
||||
self.assertIsNotNone(building.internal_zones, 'no internal zones created')
|
||||
self.assertIsNotNone(building.grounds, 'building grounds is none')
|
||||
self.assertIsNotNone(building.walls, 'building walls is none')
|
||||
self.assertIsNotNone(building.roofs, 'building roofs is none')
|
||||
for internal_zone in building.internal_zones:
|
||||
self.assertIsNone(internal_zone.usage_zones, 'usage zones are defined')
|
||||
self.assertTrue(len(internal_zone.thermal_zones) > 0, 'thermal zones are not defined')
|
||||
self.assertIsNone(building.basement_heated, 'building basement_heated is not none')
|
||||
self.assertIsNone(building.attic_heated, 'building attic_heated is not none')
|
||||
self.assertIsNone(building.terrains, 'building terrains is not none')
|
||||
self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none')
|
||||
self.assertIsNotNone(building.function, 'building function is none')
|
||||
self.assertIsNotNone(building.average_storey_height, 'building average_storey_height is none')
|
||||
self.assertIsNotNone(building.storeys_above_ground, 'building storeys_above_ground is none')
|
||||
self.assertEqual(len(building.heating), 0, 'building heating is not none')
|
||||
self.assertEqual(len(building.cooling), 0, 'building cooling is not none')
|
||||
self.assertIsNotNone(building.eave_height, 'building eave height is none')
|
||||
self.assertIsNotNone(building.roof_type, 'building roof type is none')
|
||||
self.assertIsNotNone(building.floor_area, 'building floor_area is none')
|
||||
self.assertIsNone(building.households, 'building households is not none')
|
||||
self.assertFalse(building.is_conditioned, 'building is conditioned')
|
||||
|
||||
def _check_thermal_zones(self, internal_zone):
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
self.assertIsNotNone(thermal_zone.id, 'thermal_zone id is none')
|
||||
self.assertIsNotNone(thermal_zone.footprint_area, 'thermal_zone floor area is none')
|
||||
self.assertTrue(len(thermal_zone.thermal_boundaries) > 0, 'thermal_zone thermal_boundaries not defined')
|
||||
self.assertIsNotNone(thermal_zone.additional_thermal_bridge_u_value, 'additional_thermal_bridge_u_value is none')
|
||||
self.assertIsNotNone(thermal_zone.effective_thermal_capacity, 'thermal_zone effective_thermal_capacity is none')
|
||||
self.assertIsNotNone(thermal_zone.indirectly_heated_area_ratio,
|
||||
'thermal_zone indirectly_heated_area_ratio is none')
|
||||
self.assertIsNotNone(thermal_zone.infiltration_rate_system_off,
|
||||
'thermal_zone infiltration_rate_system_off is none')
|
||||
self.assertIsNotNone(thermal_zone.infiltration_rate_system_on, 'thermal_zone infiltration_rate_system_on is none')
|
||||
self.assertIsNotNone(thermal_zone.volume, 'thermal_zone volume is none')
|
||||
self.assertIsNone(thermal_zone.ordinate_number, 'thermal_zone ordinate number is not none')
|
||||
self.assertIsNotNone(thermal_zone.view_factors_matrix, 'thermal_zone view factors matrix is none')
|
||||
self.assertIsNone(thermal_zone.hvac_system, 'thermal_zone hvac_system is not none')
|
||||
self.assertIsNone(thermal_zone.usage, 'thermal_zone usage is not none')
|
||||
self.assertIsNone(thermal_zone.hours_day, 'thermal_zone hours a day is not none')
|
||||
self.assertIsNone(thermal_zone.days_year, 'thermal_zone days a year is not none')
|
||||
self.assertIsNone(thermal_zone.mechanical_air_change, 'thermal_zone mechanical air change is not none')
|
||||
self.assertIsNone(thermal_zone.occupancy, 'thermal_zone occupancy is not none')
|
||||
self.assertIsNone(thermal_zone.lighting, 'thermal_zone lighting is not none')
|
||||
self.assertIsNone(thermal_zone.appliances, 'thermal_zone appliances is not none')
|
||||
self.assertIsNone(thermal_zone.thermal_control, 'thermal_zone thermal control is not none')
|
||||
self.assertIsNone(thermal_zone.internal_gains, 'thermal_zone internal gains not returns none')
|
||||
|
||||
def _check_thermal_boundaries(self, thermal_zone):
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
self.assertIsNotNone(thermal_boundary.id, 'thermal_boundary id is none')
|
||||
self.assertIsNotNone(thermal_boundary.parent_surface, 'thermal_boundary surface is none')
|
||||
self.assertIsNotNone(thermal_boundary.thermal_zones, 'thermal_boundary delimits no thermal zone')
|
||||
self.assertIsNotNone(thermal_boundary.opaque_area, 'thermal_boundary area is none')
|
||||
self.assertIsNotNone(thermal_boundary.azimuth, 'thermal_boundary azimuth is none')
|
||||
self.assertIsNotNone(thermal_boundary.inclination, 'thermal_boundary inclination is none')
|
||||
self.assertIsNotNone(thermal_boundary.thickness, 'thermal_boundary thickness is none')
|
||||
self.assertIsNotNone(thermal_boundary.type, 'thermal_boundary type is none')
|
||||
self.assertIsNotNone(thermal_boundary.outside_solar_absorptance, 'outside_solar_absorptance is none')
|
||||
self.assertIsNotNone(thermal_boundary.outside_shortwave_reflectance, 'shortwave_reflectance is none')
|
||||
self.assertIsNotNone(thermal_boundary.thermal_openings, 'thermal_openings is none')
|
||||
self.assertIsNotNone(thermal_boundary.construction_name, 'construction_name is none')
|
||||
self.assertIsNotNone(thermal_boundary.window_ratio, 'window_ratio is none')
|
||||
self.assertIsNone(thermal_boundary.windows_areas, 'windows_areas is not none')
|
||||
self.assertIsNotNone(thermal_boundary.u_value, 'u_value is none')
|
||||
self.assertIsNotNone(thermal_boundary.hi, 'hi is none')
|
||||
self.assertIsNotNone(thermal_boundary.he, 'he is none')
|
||||
self.assertIsNotNone(thermal_boundary.virtual_internal_surface, 'virtual_internal_surface is none')
|
||||
self.assertIsNone(thermal_boundary.inside_emissivity, 'inside_emissivity is not none')
|
||||
self.assertIsNone(thermal_boundary.alpha_coefficient, 'alpha_coefficient is not none')
|
||||
self.assertIsNone(thermal_boundary.radiative_coefficient, 'radiative_coefficient is not none')
|
||||
|
||||
def _check_thermal_openings(self, thermal_boundary):
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
self.assertIsNotNone(thermal_opening.id, 'thermal opening id is not none')
|
||||
self.assertIsNotNone(thermal_opening.construction_name, 'thermal opening construction is not none')
|
||||
self.assertIsNotNone(thermal_opening.area, 'thermal opening area is not none')
|
||||
self.assertRaises(Exception, lambda: thermal_opening.openable_ratio,
|
||||
'thermal_opening openable_ratio is not raising an exception')
|
||||
self.assertIsNotNone(thermal_opening.frame_ratio, 'thermal opening frame_ratio is none')
|
||||
self.assertIsNotNone(thermal_opening.g_value, 'thermal opening g_value is none')
|
||||
self.assertIsNotNone(thermal_opening.overall_u_value, 'thermal opening overall_u_value is none')
|
||||
self.assertIsNotNone(thermal_opening.hi, 'thermal opening hi is none')
|
||||
self.assertIsNotNone(thermal_opening.he, 'thermal opening he is none')
|
||||
self.assertIsNone(thermal_opening.inside_emissivity, 'thermal opening inside_emissivity is not none')
|
||||
self.assertIsNone(thermal_opening.alpha_coefficient, 'thermal opening alpha_coefficient is not none')
|
||||
self.assertIsNone(thermal_opening.radiative_coefficient, 'thermal opening radiative_coefficient is not none')
|
||||
@staticmethod
|
||||
def _internal_function(function_format, original_function):
|
||||
if function_format == 'hft':
|
||||
new_function = GeometryHelper.libs_function_from_hft(original_function)
|
||||
elif function_format == 'pluto':
|
||||
new_function = GeometryHelper.libs_function_from_pluto(original_function)
|
||||
else:
|
||||
raise Exception('Function key not recognized. Implemented only "hft" and "pluto"')
|
||||
return new_function
|
||||
|
||||
def test_citygml_function(self):
|
||||
"""
|
||||
|
@ -162,6 +68,110 @@ class TestConstructionFactory(TestCase):
|
|||
for building in city.buildings:
|
||||
self.assertRaises(Exception, lambda: self._internal_function(function_format, building.function))
|
||||
|
||||
def _check_buildings(self, city):
|
||||
for building in city.buildings:
|
||||
self.assertIsNotNone(building.name, 'building name is none')
|
||||
self.assertIsNotNone(building.type, 'building type is none')
|
||||
self.assertIsNotNone(building.volume, 'building volume is none')
|
||||
self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none')
|
||||
self.assertIsNotNone(building.simplified_polyhedron, 'building simplified polyhedron is none')
|
||||
self.assertIsNotNone(building.surfaces, 'building surfaces is none')
|
||||
self.assertIsNotNone(building.centroid, 'building centroid is none')
|
||||
self.assertIsNotNone(building.max_height, 'building max_height is none')
|
||||
self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated')
|
||||
self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated')
|
||||
self.assertEqual(len(building.diffuse), 0, 'building diffuse is calculated')
|
||||
self.assertEqual(len(building.beam), 0, 'building beam is calculated')
|
||||
self.assertIsNotNone(building.lower_corner, 'building lower corner is none')
|
||||
self.assertEqual(len(building.sensors), 0, 'building sensors are assigned')
|
||||
self.assertIsNotNone(building.internal_zones, 'no internal zones created')
|
||||
self.assertIsNotNone(building.grounds, 'building grounds is none')
|
||||
self.assertIsNotNone(building.walls, 'building walls is none')
|
||||
self.assertIsNotNone(building.roofs, 'building roofs is none')
|
||||
self.assertIsNotNone(building.internal_walls, 'building internal walls is none')
|
||||
self.assertIsNone(building.basement_heated, 'building basement_heated is not none')
|
||||
self.assertIsNone(building.attic_heated, 'building attic_heated is not none')
|
||||
self.assertIsNone(building.terrains, 'building terrains is not none')
|
||||
self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none')
|
||||
self.assertIsNotNone(building.function, 'building function is none')
|
||||
self.assertIsNotNone(building.average_storey_height, 'building average_storey_height is none')
|
||||
self.assertIsNotNone(building.storeys_above_ground, 'building storeys_above_ground is none')
|
||||
self.assertEqual(len(building.heating), 0, 'building heating is not none')
|
||||
self.assertEqual(len(building.cooling), 0, 'building cooling is not none')
|
||||
self.assertIsNotNone(building.eave_height, 'building eave height is none')
|
||||
self.assertIsNotNone(building.roof_type, 'building roof type is none')
|
||||
self.assertIsNotNone(building.floor_area, 'building floor_area is none')
|
||||
self.assertIsNone(building.households, 'building households is not none')
|
||||
self.assertFalse(building.is_conditioned, 'building is conditioned')
|
||||
self.assertIsNotNone(building.shell, 'building shell is none')
|
||||
self.assertIsNone(building.human_readable_name, 'building human_readable_name is not none')
|
||||
|
||||
def _check_thermal_zones(self, internal_zone):
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
self.assertIsNotNone(thermal_zone.id, 'thermal_zone id is none')
|
||||
self.assertIsNotNone(thermal_zone.footprint_area, 'thermal_zone floor area is none')
|
||||
self.assertTrue(len(thermal_zone.thermal_boundaries) > 0, 'thermal_zone thermal_boundaries not defined')
|
||||
self.assertIsNotNone(thermal_zone.additional_thermal_bridge_u_value, 'additional_thermal_bridge_u_value is none')
|
||||
self.assertIsNotNone(thermal_zone.effective_thermal_capacity, 'thermal_zone effective_thermal_capacity is none')
|
||||
self.assertIsNotNone(thermal_zone.indirectly_heated_area_ratio,
|
||||
'thermal_zone indirectly_heated_area_ratio is none')
|
||||
self.assertIsNotNone(thermal_zone.infiltration_rate_system_off,
|
||||
'thermal_zone infiltration_rate_system_off is none')
|
||||
self.assertIsNotNone(thermal_zone.infiltration_rate_system_on, 'thermal_zone infiltration_rate_system_on is none')
|
||||
self.assertIsNotNone(thermal_zone.volume, 'thermal_zone volume is none')
|
||||
self.assertIsNone(thermal_zone.ordinate_number, 'thermal_zone ordinate number is not none')
|
||||
self.assertIsNotNone(thermal_zone.view_factors_matrix, 'thermal_zone view factors matrix is none')
|
||||
self.assertIsNotNone(thermal_zone.total_floor_area, 'thermal zone total_floor_area is none')
|
||||
self.assertIsNone(thermal_zone.usage, 'thermal_zone usage is not none')
|
||||
self.assertIsNone(thermal_zone.hours_day, 'thermal_zone hours a day is not none')
|
||||
self.assertIsNone(thermal_zone.days_year, 'thermal_zone days a year is not none')
|
||||
self.assertIsNone(thermal_zone.mechanical_air_change, 'thermal_zone mechanical air change is not none')
|
||||
self.assertIsNone(thermal_zone.occupancy, 'thermal_zone occupancy is not none')
|
||||
self.assertIsNone(thermal_zone.lighting, 'thermal_zone lighting is not none')
|
||||
self.assertIsNone(thermal_zone.appliances, 'thermal_zone appliances is not none')
|
||||
self.assertIsNone(thermal_zone.thermal_control, 'thermal_zone thermal control is not none')
|
||||
self.assertIsNone(thermal_zone.internal_gains, 'thermal_zone internal gains not returns none')
|
||||
|
||||
def _check_thermal_boundaries(self, thermal_zone):
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
self.assertIsNotNone(thermal_boundary.id, 'thermal_boundary id is none')
|
||||
self.assertIsNotNone(thermal_boundary.parent_surface, 'thermal_boundary surface is none')
|
||||
self.assertIsNotNone(thermal_boundary.thermal_zones, 'thermal_boundary delimits no thermal zone')
|
||||
self.assertIsNotNone(thermal_boundary.opaque_area, 'thermal_boundary area is none')
|
||||
self.assertIsNotNone(thermal_boundary.thickness, 'thermal_boundary thickness is none')
|
||||
self.assertIsNotNone(thermal_boundary.type, 'thermal_boundary type is none')
|
||||
self.assertIsNotNone(thermal_boundary.thermal_openings, 'thermal_openings is none')
|
||||
self.assertIsNotNone(thermal_boundary.construction_name, 'construction_name is none')
|
||||
self.assertIsNotNone(thermal_boundary.window_ratio, 'window_ratio is none')
|
||||
self.assertIsNone(thermal_boundary.windows_areas, 'windows_areas is not none')
|
||||
self.assertIsNotNone(thermal_boundary.u_value, 'u_value is none')
|
||||
self.assertIsNotNone(thermal_boundary.hi, 'hi is none')
|
||||
self.assertIsNotNone(thermal_boundary.he, 'he is none')
|
||||
self.assertIsNotNone(thermal_boundary.internal_surface, 'virtual_internal_surface is none')
|
||||
self.assertIsNotNone(thermal_boundary.layers, 'layers is not none')
|
||||
|
||||
def _check_thermal_openings(self, thermal_boundary):
|
||||
for thermal_opening in thermal_boundary.thermal_openings:
|
||||
self.assertIsNotNone(thermal_opening.id, 'thermal opening id is not none')
|
||||
self.assertIsNotNone(thermal_opening.construction_name, 'thermal opening construction is not none')
|
||||
self.assertIsNotNone(thermal_opening.area, 'thermal opening area is not none')
|
||||
self.assertIsNotNone(thermal_opening.frame_ratio, 'thermal opening frame_ratio is none')
|
||||
self.assertIsNotNone(thermal_opening.g_value, 'thermal opening g_value is none')
|
||||
self.assertIsNotNone(thermal_opening.overall_u_value, 'thermal opening overall_u_value is none')
|
||||
self.assertIsNotNone(thermal_opening.hi, 'thermal opening hi is none')
|
||||
self.assertIsNotNone(thermal_opening.he, 'thermal opening he is none')
|
||||
self.assertIsNotNone(thermal_opening.construction_name, 'thermal opening construction_name is none')
|
||||
|
||||
def _check_surfaces(self, thermal_boundary):
|
||||
external_surface = thermal_boundary.parent_surface
|
||||
internal_surface = thermal_boundary.internal_surface
|
||||
self.assertIsNotNone(external_surface.short_wave_reflectance,
|
||||
'external surface short_wave_reflectance id is not none')
|
||||
self.assertIsNotNone(external_surface.long_wave_emittance, 'external surface long_wave_emittance id is not none')
|
||||
self.assertIsNotNone(internal_surface.short_wave_reflectance,
|
||||
'external surface short_wave_reflectance id is not none')
|
||||
self.assertIsNotNone(internal_surface.long_wave_emittance, 'external surface long_wave_emittance id is not none')
|
||||
|
||||
def test_city_with_construction_extended_library(self):
|
||||
"""
|
||||
Enrich the city with the construction information and verify it
|
||||
|
@ -180,17 +190,15 @@ class TestConstructionFactory(TestCase):
|
|||
for thermal_zone in internal_zone.thermal_zones:
|
||||
self._check_thermal_boundaries(thermal_zone)
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
self.assertIsNotNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is none')
|
||||
self.assertIsNotNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is none')
|
||||
self.assertIsNotNone(thermal_boundary.layers, 'layers is none')
|
||||
self._check_thermal_openings(thermal_boundary)
|
||||
self._check_surfaces(thermal_boundary)
|
||||
|
||||
def test_archetype_not_found(self):
|
||||
file = 'pluto_building.gml'
|
||||
city = self._get_citygml(file)
|
||||
for building in city.buildings:
|
||||
building.year_of_construction = 1990
|
||||
building.function = 'office'
|
||||
ConstructionFactory('nrel', city).enrich()
|
||||
|
||||
@staticmethod
|
||||
def _internal_function(function_format, original_function):
|
||||
if function_format == 'hft':
|
||||
new_function = GeometryHelper.libs_function_from_hft(original_function)
|
||||
elif function_format == 'pluto':
|
||||
new_function = GeometryHelper.libs_function_from_pluto(original_function)
|
||||
else:
|
||||
raise Exception('Function key not recognized. Implemented only "hft" and "pluto"')
|
||||
return new_function
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
"""
|
||||
Building test
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Gutierrez Morote Guillermo.GutierrezMorote@concordia.ca
|
||||
"""
|
||||
from pathlib import Path
|
||||
from unittest import TestCase
|
||||
from imports.geometry_factory import GeometryFactory
|
||||
from imports.usage_factory import UsageFactory
|
||||
from imports.construction_factory import ConstructionFactory
|
||||
from exports.exports_factory import ExportsFactory
|
||||
|
||||
|
||||
class TestBuildings(TestCase):
|
||||
"""
|
||||
TestBuilding TestCase 1
|
||||
"""
|
||||
def setUp(self) -> None:
|
||||
"""
|
||||
Test setup
|
||||
:return: None
|
||||
"""
|
||||
self._city_gml = None
|
||||
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
|
||||
|
||||
def test_doe_idf(self):
|
||||
city_file = "../unittests/tests_data/one_building_in_kelowna.gml"
|
||||
output_path = Path('../unittests/tests_outputs/').resolve()
|
||||
city = GeometryFactory('citygml', city_file).city
|
||||
for building in city.buildings:
|
||||
building.year_of_construction = 2006
|
||||
ConstructionFactory('nrel', city).enrich()
|
||||
UsageFactory('comnet', city).enrich()
|
||||
ExportsFactory('idf', city, output_path).export()
|
||||
|
||||
self.assertEqual(1, len(city.buildings))
|
||||
for building in city.buildings:
|
||||
for internal_zone in building.internal_zones:
|
||||
self.assertTrue(len(internal_zone.usage_zones) > 0)
|
||||
for usage_zone in internal_zone.usage_zones:
|
||||
self.assertIsNot(len(usage_zone.occupancy.occupancy_schedules), 0, 'no occupancy schedules defined')
|
||||
for schedule in usage_zone.occupancy.occupancy_schedules:
|
||||
self.assertIsNotNone(schedule.type)
|
||||
self.assertIsNotNone(schedule.values)
|
||||
self.assertIsNotNone(schedule.data_type)
|
||||
self.assertIsNotNone(schedule.time_step)
|
||||
self.assertIsNotNone(schedule.time_range)
|
||||
self.assertIsNotNone(schedule.day_types)
|
||||
self.assertIsNot(len(usage_zone.lighting.schedules), 0, 'no lighting schedules defined')
|
||||
for schedule in usage_zone.lighting.schedules:
|
||||
self.assertIsNotNone(schedule.type)
|
||||
self.assertIsNotNone(schedule.values)
|
||||
self.assertIsNotNone(schedule.data_type)
|
||||
self.assertIsNotNone(schedule.time_step)
|
||||
self.assertIsNotNone(schedule.time_range)
|
||||
self.assertIsNotNone(schedule.day_types)
|
|
@ -40,7 +40,7 @@ class TestEnergySystemsFactory(TestCase):
|
|||
"""
|
||||
city_file = "../unittests/tests_data/C40_Final.gml"
|
||||
self._output_path = "../unittests/tests_data/as_user_output.csv"
|
||||
self._city = GeometryFactory('citygml', city_file).city
|
||||
self._city = GeometryFactory('citygml', path=city_file).city
|
||||
EnergySystemsFactory('air source hp', self._city).enrich()
|
||||
|
||||
def test_air_source_heat_pump_import(self):
|
||||
|
|
|
@ -41,7 +41,7 @@ class TestEnergySystemsFactory(TestCase):
|
|||
"""
|
||||
city_file = "../unittests/tests_data/C40_Final.gml"
|
||||
self._output_path = "../unittests/tests_data/w2w_user_output.csv"
|
||||
self._city = GeometryFactory('citygml', city_file).city
|
||||
self._city = GeometryFactory('citygml', path=city_file).city
|
||||
EnergySystemsFactory('water to water hp', self._city).enrich()
|
||||
|
||||
def test_water_to_water_heat_pump_import(self):
|
||||
|
@ -50,15 +50,32 @@ class TestEnergySystemsFactory(TestCase):
|
|||
self.assertEqual(self._city.energy_systems[0].water_to_water_hp.model, 'ClimateMaster 156 kW')
|
||||
self.assertEqual(self._city.energy_systems[2].water_to_water_hp.model, 'ClimateMaster 335 kW')
|
||||
|
||||
def test_water_to_water_series_heat_pump_export(self):
|
||||
|
||||
EnergySystemsExportFactory(city=self._city, user_input=user_input, hp_model='ClimateMaster 156 kW',
|
||||
output_path=self._output_path).export('water')
|
||||
df = pd.read_csv(self._output_path)
|
||||
self.assertEqual(df.shape, (13, 3))
|
||||
self.assertEqual(df.iloc[0, 1], 1162387.5)
|
||||
|
||||
def test_water_to_water_parallel_heat_pump_export(self):
|
||||
def test_water_to_water_heat_pump_export(self):
|
||||
# User defined paramenters
|
||||
user_input = {
|
||||
'StartYear': 2020,
|
||||
'EndYear': 2021,
|
||||
'MaximumHPEnergyInput': 8000,
|
||||
'HoursOfStorageAtMaxDemand': 1,
|
||||
'BuildingSuppTemp': 40,
|
||||
'TemperatureDifference': 15,
|
||||
'FuelLHV': 47100,
|
||||
'FuelPrice': 0.12,
|
||||
'FuelEF': 1887,
|
||||
'FuelDensity': 0.717,
|
||||
'HPSupTemp': 60,
|
||||
'b1': 10,
|
||||
'b2': 10,
|
||||
'b3': 10,
|
||||
'b4': 10,
|
||||
'b5': 10,
|
||||
'b6': 10,
|
||||
'b7': 10,
|
||||
'b8': 10,
|
||||
'b9': 10,
|
||||
'b10': 10,
|
||||
'b11': 10
|
||||
}
|
||||
|
||||
EnergySystemsExportFactory(city=self._city, user_input=user_input, hp_model='ClimateMaster 256 kW',
|
||||
output_path=self._output_path, sim_type=1).export('water')
|
||||
|
|
|
@ -27,7 +27,7 @@ class TestGeometryFactory(TestCase):
|
|||
|
||||
def _get_citygml(self, file):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
self._city = GeometryFactory('citygml', file_path).city
|
||||
self._city = GeometryFactory('citygml', path=file_path).city
|
||||
self.assertIsNotNone(self._city, 'city is none')
|
||||
return self._city
|
||||
|
||||
|
@ -47,8 +47,8 @@ class TestGeometryFactory(TestCase):
|
|||
for internal_zone in building.internal_zones:
|
||||
self.assertIsNotNone(internal_zone.usage_zones, 'usage zones are not defined')
|
||||
self.assertIsNotNone(internal_zone.thermal_zones, 'thermal zones are not defined')
|
||||
self.assertIsNotNone(building.basement_heated, 'building basement_heated is none')
|
||||
self.assertIsNotNone(building.attic_heated, 'building attic_heated is none')
|
||||
self.assertIsNone(building.basement_heated, 'building basement_heated is not none')
|
||||
self.assertIsNone(building.attic_heated, 'building attic_heated is not none')
|
||||
self.assertIsNotNone(building.average_storey_height, 'building average_storey_height is none')
|
||||
self.assertIsNotNone(building.storeys_above_ground, 'building storeys_above_ground is none')
|
||||
self.assertTrue(building.is_conditioned, 'building is_conditioned is not conditioned')
|
||||
|
@ -91,7 +91,7 @@ class TestGeometryFactory(TestCase):
|
|||
|
||||
def _test_hft(self, file):
|
||||
_construction_keys = ['nrel']
|
||||
_usage_keys = ['ca', 'comnet', 'hft']
|
||||
_usage_keys = ['comnet', 'hft']
|
||||
for construction_key in _construction_keys:
|
||||
for usage_key in _usage_keys:
|
||||
# construction factory called first
|
||||
|
|
|
@ -14,6 +14,7 @@ from imports.geometry.helpers.geometry_helper import GeometryHelper
|
|||
from imports.construction_factory import ConstructionFactory
|
||||
from imports.usage_factory import UsageFactory
|
||||
from exports.exports_factory import ExportsFactory
|
||||
from exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
|
||||
import helpers.constants as cte
|
||||
from city_model_structure.city import City
|
||||
|
||||
|
@ -34,7 +35,7 @@ class TestExports(TestCase):
|
|||
|
||||
def _get_citygml(self, file):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
self._city = GeometryFactory('citygml', file_path).city
|
||||
self._city = GeometryFactory('citygml', path=file_path).city
|
||||
self.assertIsNotNone(self._city, 'city is none')
|
||||
return self._city
|
||||
|
||||
|
@ -51,7 +52,7 @@ class TestExports(TestCase):
|
|||
building.year_of_construction = 2006
|
||||
ConstructionFactory('nrel', self._complete_city).enrich()
|
||||
UsageFactory('ca', self._complete_city).enrich()
|
||||
cli = 'C:\\Users\\Pilar\\PycharmProjects\\monthlyenergybalance\\tests_data\\weather\\inseldb_Summerland.cli'
|
||||
cli = (self._example_path / 'weather' / 'inseldb_Summerland.cli').resolve()
|
||||
self._complete_city.climate_file = Path(cli)
|
||||
self._complete_city.climate_reference_city = 'Summerland'
|
||||
dummy_measures = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
|
@ -89,3 +90,23 @@ class TestExports(TestCase):
|
|||
export to SRA
|
||||
"""
|
||||
self._export('sra')
|
||||
|
||||
def test_idf_export(self):
|
||||
"""
|
||||
export to IDF
|
||||
"""
|
||||
city = self._get_citygml('EV_GM_MB_LoD2.gml')
|
||||
for building in city.buildings:
|
||||
building.year_of_construction = 2006
|
||||
if building.function is None:
|
||||
building.function = 'large office'
|
||||
ConstructionFactory('nrel', city).enrich()
|
||||
UsageFactory('comnet', city).enrich()
|
||||
try:
|
||||
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
|
||||
EnergyBuildingsExportsFactory('idf', city, self._output_path,
|
||||
target_buildings=['gml_1066158', 'gml_1066159']).export()
|
||||
except Exception:
|
||||
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
|
||||
|
||||
|
||||
|
|
|
@ -9,8 +9,9 @@ from unittest import TestCase
|
|||
|
||||
from numpy import inf
|
||||
|
||||
from imports.geometry_factory import GeometryFactory
|
||||
import exports.exports_factory
|
||||
from imports.construction_factory import ConstructionFactory
|
||||
from imports.geometry_factory import GeometryFactory
|
||||
|
||||
|
||||
class TestGeometryFactory(TestCase):
|
||||
|
@ -25,36 +26,26 @@ class TestGeometryFactory(TestCase):
|
|||
"""
|
||||
self._city = None
|
||||
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
|
||||
self._output_path = (Path(__file__).parent / 'tests_outputs').resolve()
|
||||
|
||||
def _get_citygml(self, file):
|
||||
def _get_city(self, file, file_type, height_field=None, year_of_construction_field=None, function_field=None):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
self._city = GeometryFactory('citygml', file_path).city
|
||||
self.assertIsNotNone(self._city, 'city is none')
|
||||
return self._city
|
||||
|
||||
def _get_obj(self, file):
|
||||
# todo: solve the incongruities between city and city_debug
|
||||
file_path = (self._example_path / file).resolve()
|
||||
self._city = GeometryFactory('obj', file_path).city
|
||||
self.assertIsNotNone(self._city, 'city is none')
|
||||
return self._city
|
||||
|
||||
def _get_rhino(self, file):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
self._city = GeometryFactory('rhino', file_path).city
|
||||
self._city = GeometryFactory(file_type,
|
||||
path=file_path,
|
||||
height_field=height_field,
|
||||
year_of_construction_field=year_of_construction_field,
|
||||
function_field=function_field).city
|
||||
self.assertIsNotNone(self._city, 'city is none')
|
||||
return self._city
|
||||
|
||||
def _check_buildings(self, city):
|
||||
for building in city.buildings:
|
||||
self.assertIsNotNone(building.name, 'building name is none')
|
||||
self.assertIsNotNone(building.lod, 'building lod is none')
|
||||
self.assertIsNotNone(building.type, 'building type is none')
|
||||
self.assertIsNotNone(building.volume, 'building volume is none')
|
||||
self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none')
|
||||
self.assertIsNotNone(building.simplified_polyhedron, 'building simplified polyhedron is none')
|
||||
self.assertIsNotNone(building.surfaces, 'building surfaces is none')
|
||||
self.assertIsNotNone(building.centroid, 'building centroid is none')
|
||||
self.assertIsNotNone(building.max_height, 'building max_height is none')
|
||||
self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated')
|
||||
self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated')
|
||||
|
@ -73,8 +64,6 @@ class TestGeometryFactory(TestCase):
|
|||
self.assertIsNone(building.basement_heated, 'building basement_heated is not none')
|
||||
self.assertIsNone(building.attic_heated, 'building attic_heated is not none')
|
||||
self.assertIsNone(building.terrains, 'building terrains is not none')
|
||||
self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none')
|
||||
self.assertIsNotNone(building.function, 'building function is none')
|
||||
self.assertIsNone(building.average_storey_height, 'building average_storey_height is not none')
|
||||
self.assertIsNone(building.storeys_above_ground, 'building storeys_above_ground is not none')
|
||||
self.assertEqual(len(building.heating), 0, 'building heating is not none')
|
||||
|
@ -89,18 +78,21 @@ class TestGeometryFactory(TestCase):
|
|||
for surface in building.surfaces:
|
||||
self.assertIsNotNone(surface.name, 'surface name is none')
|
||||
self.assertIsNotNone(surface.id, 'surface id is none')
|
||||
self.assertIsNone(surface.swr, 'surface swr is not none')
|
||||
self.assertIsNotNone(surface.lower_corner, 'surface envelope_lower_corner is none')
|
||||
self.assertIsNotNone(surface.upper_corner, 'surface envelope_upper_corner is none')
|
||||
self.assertIsNotNone(surface.area_below_ground, 'surface area_below_ground is none')
|
||||
self.assertIsNotNone(surface.area_above_ground, 'surface area_above_ground is none')
|
||||
self.assertIsNotNone(surface.perimeter_area, 'surface area_above_ground is none')
|
||||
self.assertIsNotNone(surface.azimuth, 'surface azimuth is none')
|
||||
self.assertIsNotNone(surface.type, 'surface type is none')
|
||||
self.assertIsNotNone(surface.inclination, 'surface inclination is none')
|
||||
self.assertIsNotNone(surface.type, 'surface type is none')
|
||||
self.assertEqual(len(surface.global_irradiance), 0, 'global irradiance is calculated')
|
||||
self.assertIsNotNone(surface.perimeter_polygon, 'surface perimeter_polygon is none')
|
||||
self.assertIsNone(surface.holes_polygons, 'surface hole_polygons is not none')
|
||||
self.assertIsNotNone(surface.solid_polygon, 'surface solid_polygon is none')
|
||||
self.assertIsNone(surface.short_wave_reflectance, 'surface short_wave_reflectance is not none')
|
||||
self.assertIsNone(surface.long_wave_emittance, 'surface long_wave_emittance is not none')
|
||||
self.assertIsNotNone(surface.inverse, 'surface inverse is none')
|
||||
self.assertEqual(len(surface.associated_thermal_boundaries), 0, 'associated_thermal_boundaries are assigned')
|
||||
self.assertIsNone(surface.vegetation, 'surface vegetation is not none')
|
||||
|
||||
# citygml_classes
|
||||
def test_import_citygml(self):
|
||||
|
@ -108,8 +100,8 @@ class TestGeometryFactory(TestCase):
|
|||
Test city objects in the city
|
||||
:return: None
|
||||
"""
|
||||
file = 'one_building_in_kelowna.gml'
|
||||
city = self._get_citygml(file)
|
||||
file = 'FZK_Haus_LoD_2.gml'
|
||||
city = self._get_city(file, 'citygml', year_of_construction_field='yearOfConstruction')
|
||||
self.assertTrue(len(city.buildings) == 1)
|
||||
self._check_buildings(city)
|
||||
for building in city.buildings:
|
||||
|
@ -117,13 +109,12 @@ class TestGeometryFactory(TestCase):
|
|||
building.year_of_construction = 2006
|
||||
city = ConstructionFactory('nrel', city).enrich()
|
||||
|
||||
# rhino
|
||||
def test_import_rhino(self):
|
||||
"""
|
||||
Test rhino import
|
||||
"""
|
||||
file = 'dompark.3dm'
|
||||
city = self._get_rhino(file)
|
||||
city = self._get_city(file, 'rhino')
|
||||
self.assertIsNotNone(city, 'city is none')
|
||||
self.assertTrue(len(city.buildings) == 36)
|
||||
i = 0
|
||||
|
@ -131,28 +122,40 @@ class TestGeometryFactory(TestCase):
|
|||
self.assertIsNot(building.volume, inf, 'open volume')
|
||||
i += 1
|
||||
|
||||
# obj
|
||||
def test_import_obj(self):
|
||||
"""
|
||||
Test obj import
|
||||
"""
|
||||
file = 'kelowna.obj'
|
||||
city = self._get_obj(file)
|
||||
self.assertIsNotNone(city, 'city is none')
|
||||
city = self._get_city(file, 'obj')
|
||||
self.assertTrue(len(city.buildings) == 1)
|
||||
self._check_buildings(city)
|
||||
for building in city.buildings:
|
||||
self._check_surfaces(building)
|
||||
|
||||
# osm
|
||||
def test_subway(self):
|
||||
def test_import_geopandas(self):
|
||||
"""
|
||||
Test subway parsing
|
||||
:return:
|
||||
Test geopandas import
|
||||
"""
|
||||
file_path = (self._example_path / 'subway.osm').resolve()
|
||||
file = 'sample.geojson'
|
||||
city = self._get_city(file, 'gpandas')
|
||||
self.assertTrue(len(city.buildings) == 1)
|
||||
self._check_buildings(city)
|
||||
for building in city.buildings:
|
||||
self._check_surfaces(building)
|
||||
self.assertEqual(1912.0898135701814, building.volume)
|
||||
self.assertEqual(146.19493345171213, building.floor_area)
|
||||
|
||||
city = GeometryFactory('osm_subway', file_path).city
|
||||
def test_import_geojson(self):
|
||||
"""
|
||||
Test geojson import
|
||||
"""
|
||||
file = 'sample.geojson'
|
||||
city = self._get_city(file, 'geojson',
|
||||
height_field='citygml_me',
|
||||
year_of_construction_field='ANNEE_CONS',
|
||||
function_field='LIBELLE_UT')
|
||||
|
||||
self.assertIsNotNone(city, 'subway entrances is none')
|
||||
self.assertEqual(len(city.city_objects), 20, 'Wrong number of subway entrances')
|
||||
exports.exports_factory.ExportsFactory('obj', city, self._output_path).export_debug()
|
||||
self.assertEqual(207, len(city.buildings), 'wrong number of buildings')
|
||||
self._check_buildings(city)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
TestGeometryFactory test and validate the city model structure geometric parameters
|
||||
Test greenery factory test and validate the greenery construction
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2022 Concordia CERC group
|
||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
|
|
|
@ -11,7 +11,7 @@ from unittest import TestCase
|
|||
from imports.geometry_factory import GeometryFactory
|
||||
from imports.usage_factory import UsageFactory
|
||||
from imports.construction_factory import ConstructionFactory
|
||||
from exports.exports_factory import ExportsFactory
|
||||
from exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
|
||||
from city_model_structure.greenery.vegetation import Vegetation
|
||||
from city_model_structure.greenery.soil import Soil
|
||||
from city_model_structure.greenery.plant import Plant
|
||||
|
@ -27,7 +27,7 @@ class GreeneryInIdf(TestCase):
|
|||
city_file = "../unittests/tests_data/one_building_in_kelowna.gml"
|
||||
output_path = Path('../unittests/tests_outputs/').resolve()
|
||||
|
||||
city = GeometryFactory('citygml', city_file).city
|
||||
city = GeometryFactory('citygml', path=city_file).city
|
||||
for building in city.buildings:
|
||||
building.year_of_construction = 2006
|
||||
ConstructionFactory('nrel', city).enrich()
|
||||
|
@ -62,13 +62,11 @@ class GreeneryInIdf(TestCase):
|
|||
plants = [plant]
|
||||
vegetation = Vegetation(vegetation_name, soil, soil_thickness, plants)
|
||||
for building in city.buildings:
|
||||
for internal_zone in building.internal_zones:
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
if thermal_boundary.type == cte.ROOF:
|
||||
thermal_boundary.vegetation = vegetation
|
||||
for surface in building.surfaces:
|
||||
if surface.type == cte.ROOF:
|
||||
surface.vegetation = vegetation
|
||||
|
||||
_idf_2 = ExportsFactory('idf', city, output_path).export_debug()
|
||||
_idf_2 = EnergyBuildingsExportsFactory('idf', city, output_path).export_debug()
|
||||
_idf_2.run()
|
||||
with open((output_path / f'{city.name}_out.csv').resolve()) as f:
|
||||
reader = csv.reader(f, delimiter=',')
|
||||
|
@ -82,12 +80,12 @@ class GreeneryInIdf(TestCase):
|
|||
print('With greenery')
|
||||
print(f'heating: {heating} MWh/yr, cooling: {cooling} MWh/yr')
|
||||
|
||||
city = GeometryFactory('citygml', city_file).city
|
||||
city = GeometryFactory('citygml', path=city_file).city
|
||||
for building in city.buildings:
|
||||
building.year_of_construction = 2006
|
||||
ConstructionFactory('nrel', city).enrich()
|
||||
UsageFactory('comnet', city).enrich()
|
||||
_idf = ExportsFactory('idf', city, output_path).export()
|
||||
_idf = EnergyBuildingsExportsFactory('idf', city, output_path).export()
|
||||
_idf.run()
|
||||
with open((output_path / f'{city.name}_out.csv').resolve()) as f:
|
||||
reader = csv.reader(f, delimiter=',')
|
||||
|
|
148
unittests/test_insel_exports.py
Normal file
148
unittests/test_insel_exports.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
"""
|
||||
TestInselExports test
|
||||
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 pathlib import Path
|
||||
from unittest import TestCase
|
||||
import pandas as pd
|
||||
import helpers.constants as cte
|
||||
from helpers.monthly_values import MonthlyValues
|
||||
from imports.geometry_factory import GeometryFactory
|
||||
from imports.construction_factory import ConstructionFactory
|
||||
from imports.usage_factory import UsageFactory
|
||||
from exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
|
||||
from imports.weather_factory import WeatherFactory
|
||||
|
||||
|
||||
class TestExports(TestCase):
|
||||
"""
|
||||
TestExports class contains the unittest for export functionality
|
||||
"""
|
||||
def setUp(self) -> None:
|
||||
"""
|
||||
Test setup
|
||||
:return: None
|
||||
"""
|
||||
self._city = None
|
||||
self._complete_city = None
|
||||
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
|
||||
self._output_path = (Path(__file__).parent / 'tests_outputs').resolve()
|
||||
|
||||
def _get_citygml(self, file):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
self._city = GeometryFactory('citygml', path=file_path).city
|
||||
self.assertIsNotNone(self._city, 'city is none')
|
||||
return self._city
|
||||
|
||||
@property
|
||||
def _read_sra_file(self) -> []:
|
||||
path = (self._example_path / "one_building_in_kelowna_sra_SW.out").resolve()
|
||||
_results = pd.read_csv(path, sep='\s+', header=0)
|
||||
id_building = ''
|
||||
header_building = []
|
||||
_radiation = []
|
||||
for column in _results.columns.values:
|
||||
if id_building != column.split(':')[1]:
|
||||
id_building = column.split(':')[1]
|
||||
if len(header_building) > 0:
|
||||
_radiation.append(pd.concat([MonthlyValues().month_hour, _results[header_building]], axis=1))
|
||||
header_building = [column]
|
||||
else:
|
||||
header_building.append(column)
|
||||
_radiation.append(pd.concat([MonthlyValues().month_hour, _results[header_building]], axis=1))
|
||||
return _radiation
|
||||
|
||||
def _set_irradiance_surfaces(self, city):
|
||||
"""
|
||||
saves in building surfaces the correspondent irradiance at different time-scales depending on the mode
|
||||
if building is None, it saves all buildings' surfaces in file, if building is specified, it saves only that
|
||||
specific building values
|
||||
:parameter city: city
|
||||
:return: none
|
||||
"""
|
||||
for radiation in self._read_sra_file:
|
||||
city_object_name = radiation.columns.values.tolist()[1].split(':')[1]
|
||||
building = city.city_object(city_object_name)
|
||||
for column in radiation.columns.values:
|
||||
if column == cte.MONTH:
|
||||
continue
|
||||
header_id = column
|
||||
surface_id = header_id.split(':')[2]
|
||||
surface = building.surface_by_id(surface_id)
|
||||
new_value = pd.DataFrame(radiation[[header_id]].to_numpy(), columns=['sra'])
|
||||
surface.global_irradiance[cte.HOUR] = new_value
|
||||
month_new_value = MonthlyValues().get_mean_values(new_value)
|
||||
surface.global_irradiance[cte.MONTH] = month_new_value
|
||||
|
||||
def test_insel_monthly_energy_balance_export(self):
|
||||
"""
|
||||
export to Insel MonthlyEnergyBalance
|
||||
"""
|
||||
city = self._get_citygml('one_building_in_kelowna.gml')
|
||||
WeatherFactory('epw', city, file_name='CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').enrich()
|
||||
for building in city.buildings:
|
||||
building.external_temperature[cte.MONTH] = MonthlyValues().\
|
||||
get_mean_values(building.external_temperature[cte.HOUR][['epw']])
|
||||
self._set_irradiance_surfaces(city)
|
||||
|
||||
for building in city.buildings:
|
||||
self.assertIsNotNone(building.external_temperature[cte.MONTH], f'building {building.name} '
|
||||
f'external_temperature is none')
|
||||
for surface in building.surfaces:
|
||||
if surface.type != 'Ground':
|
||||
self.assertIsNotNone(surface.global_irradiance[cte.MONTH], f'surface in building {building.name} '
|
||||
f'global_irradiance is none')
|
||||
|
||||
for building in city.buildings:
|
||||
building.year_of_construction = 2006
|
||||
if building.function is None:
|
||||
building.function = 'large office'
|
||||
building.attic_heated = 0
|
||||
building.basement_heated = 0
|
||||
ConstructionFactory('nrel', city).enrich()
|
||||
UsageFactory('comnet', city).enrich()
|
||||
|
||||
# parameters written:
|
||||
for building in city.buildings:
|
||||
self.assertIsNotNone(building.volume, f'building {building.name} volume is none')
|
||||
self.assertIsNotNone(building.average_storey_height, f'building {building.name} average_storey_height is none')
|
||||
self.assertIsNotNone(building.storeys_above_ground, f'building {building.name} storeys_above_ground is none')
|
||||
self.assertIsNotNone(building.attic_heated, f'building {building.name} attic_heated is none')
|
||||
self.assertIsNotNone(building.basement_heated, f'building {building.name} basement_heated is none')
|
||||
for internal_zone in building.internal_zones:
|
||||
self.assertIsNotNone(internal_zone.area, f'internal zone {internal_zone.id} area is none')
|
||||
for thermal_zone in internal_zone.thermal_zones:
|
||||
self.assertIsNotNone(thermal_zone.indirectly_heated_area_ratio, f'thermal zone {thermal_zone.id} '
|
||||
f'indirectly_heated_area_ratio is none')
|
||||
self.assertIsNotNone(thermal_zone.effective_thermal_capacity, f'thermal zone {thermal_zone.id} '
|
||||
f'effective_thermal_capacity is none')
|
||||
self.assertIsNotNone(thermal_zone.additional_thermal_bridge_u_value, f'thermal zone {thermal_zone.id} '
|
||||
f'additional_thermal_bridge_u_value '
|
||||
f'is none')
|
||||
self.assertIsNotNone(thermal_zone.total_floor_area, f'thermal zone {thermal_zone.id} '
|
||||
f'total_floor_area is none')
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
self.assertIsNotNone(thermal_boundary.type)
|
||||
self.assertIsNotNone(thermal_boundary.opaque_area)
|
||||
self.assertIsNotNone(thermal_boundary.window_ratio)
|
||||
self.assertIsNotNone(thermal_boundary.u_value)
|
||||
self.assertIsNotNone(thermal_boundary.thermal_openings)
|
||||
if thermal_boundary.type is not cte.GROUND:
|
||||
self.assertIsNotNone(thermal_boundary.parent_surface.short_wave_reflectance)
|
||||
|
||||
for usage_zone in internal_zone.usage_zones:
|
||||
self.assertIsNotNone(usage_zone.percentage, f'usage zone {usage_zone.usage} percentage is none')
|
||||
self.assertIsNotNone(usage_zone.internal_gains, f'usage zone {usage_zone.usage} internal_gains is none')
|
||||
self.assertIsNotNone(usage_zone.thermal_control, f'usage zone {usage_zone.usage} thermal_control is none')
|
||||
self.assertIsNotNone(usage_zone.hours_day, f'usage zone {usage_zone.usage} hours_day is none')
|
||||
self.assertIsNotNone(usage_zone.days_year, f'usage zone {usage_zone.usage} days_year is none')
|
||||
self.assertIsNotNone(usage_zone.mechanical_air_change, f'usage zone {usage_zone.usage} '
|
||||
f'mechanical_air_change is none')
|
||||
# export files
|
||||
try:
|
||||
EnergyBuildingsExportsFactory('insel_monthly_energy_balance', city, self._output_path).export()
|
||||
except Exception:
|
||||
self.fail("Insel MonthlyEnergyBalance ExportsFactory raised ExceptionType unexpectedly!")
|
|
@ -24,28 +24,28 @@ class TestLifeCycleAssessment(TestCase):
|
|||
|
||||
def test_fuel(self):
|
||||
city_file = "../unittests/tests_data/C40_Final.gml"
|
||||
city = GeometryFactory('citygml', city_file).city
|
||||
city = GeometryFactory('citygml', path=city_file).city
|
||||
LifeCycleAssessment('fuel', city).enrich()
|
||||
for fuel in city.fuels:
|
||||
self.assertTrue(len(city.fuels) > 0)
|
||||
|
||||
def test_vehicle(self):
|
||||
city_file = "../unittests/tests_data/C40_Final.gml"
|
||||
city = GeometryFactory('citygml', city_file).city
|
||||
city = GeometryFactory('citygml', path=city_file).city
|
||||
LifeCycleAssessment('vehicle', city).enrich()
|
||||
for vehicle in city.vehicles:
|
||||
self.assertTrue(len(city.vehicles) > 0)
|
||||
|
||||
def test_machine(self):
|
||||
city_file = "../unittests/tests_data/C40_Final.gml"
|
||||
city = GeometryFactory('citygml', city_file).city
|
||||
city = GeometryFactory('citygml', path=city_file).city
|
||||
LifeCycleAssessment('machine', city).enrich()
|
||||
for machine in city.machines:
|
||||
self.assertTrue(len(city.machines) > 0)
|
||||
|
||||
def test_material(self):
|
||||
city_file = "../unittests/tests_data/C40_Final.gml"
|
||||
city = GeometryFactory('citygml', city_file).city
|
||||
city = GeometryFactory('citygml', path=city_file).city
|
||||
LifeCycleAssessment('material', city).enrich()
|
||||
for material in city.lca_materials:
|
||||
self.assertTrue(len(city.lca_materials) > 0)
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
"""
|
||||
TestSchedulesFactory test and validate the city model structure schedules
|
||||
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 pathlib import Path
|
||||
from unittest import TestCase
|
||||
|
||||
from imports.geometry_factory import GeometryFactory
|
||||
from imports.usage_factory import UsageFactory
|
||||
from imports.construction_factory import ConstructionFactory
|
||||
from imports.schedules_factory import SchedulesFactory
|
||||
from imports.geometry.helpers.geometry_helper import GeometryHelper
|
||||
|
||||
|
||||
class TestSchedulesFactory(TestCase):
|
||||
"""
|
||||
TestSchedulesFactory TestCase
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
"""
|
||||
Configure test environment
|
||||
:return:
|
||||
"""
|
||||
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
|
||||
|
||||
def _get_citygml(self, file):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
_city = GeometryFactory('citygml', file_path).city
|
||||
for building in _city.buildings:
|
||||
building.year_of_construction = 2006
|
||||
ConstructionFactory('nrel', _city).enrich()
|
||||
self.assertIsNotNone(_city, 'city is none')
|
||||
for building in _city.buildings:
|
||||
building.function = GeometryHelper.libs_function_from_hft(building.function)
|
||||
building.year_of_construction = 2005
|
||||
UsageFactory('hft', _city).enrich()
|
||||
return _city
|
||||
|
||||
def test_doe_idf_archetypes(self):
|
||||
"""
|
||||
Enrich the city with doe_idf schedule archetypes and verify it
|
||||
"""
|
||||
file = (self._example_path / 'C40_Final.gml').resolve()
|
||||
city = self._get_citygml(file)
|
||||
occupancy_handler = 'doe_idf'
|
||||
SchedulesFactory(occupancy_handler, city).enrich()
|
||||
for building in city.buildings:
|
||||
for internal_zone in building.internal_zones:
|
||||
self.assertTrue(len(internal_zone.usage_zones) > 0)
|
||||
for usage_zone in internal_zone.usage_zones:
|
||||
self.assertIsNot(len(usage_zone.occupancy.occupancy_schedules), 0, 'no occupancy schedules defined')
|
||||
for schedule in usage_zone.occupancy.occupancy_schedules:
|
||||
self.assertIsNotNone(schedule.type)
|
||||
self.assertIsNotNone(schedule.values)
|
||||
self.assertIsNotNone(schedule.data_type)
|
||||
self.assertIsNotNone(schedule.time_step)
|
||||
self.assertIsNotNone(schedule.time_range)
|
||||
self.assertIsNotNone(schedule.day_types)
|
||||
self.assertIsNot(len(usage_zone.lighting.schedules), 0, 'no lighting schedules defined')
|
||||
for schedule in usage_zone.lighting.schedules:
|
||||
self.assertIsNotNone(schedule.type)
|
||||
self.assertIsNotNone(schedule.values)
|
||||
self.assertIsNotNone(schedule.data_type)
|
||||
self.assertIsNotNone(schedule.time_step)
|
||||
self.assertIsNotNone(schedule.time_range)
|
||||
self.assertIsNotNone(schedule.day_types)
|
|
@ -14,4 +14,10 @@ class TestConstructionCatalog(TestCase):
|
|||
catalog = UsageCatalogFactory('comnet').catalog
|
||||
self.assertIsNotNone(catalog, 'catalog is none')
|
||||
content = catalog.entries()
|
||||
self.assertEqual(len(content.usages), 32, 'Wrong number of usages')
|
||||
self.assertEqual(32, len(content.usages), 'Wrong number of usages')
|
||||
|
||||
def test_nrcan_catalog(self):
|
||||
catalog = UsageCatalogFactory('nrcan').catalog
|
||||
self.assertIsNotNone(catalog, 'catalog is none')
|
||||
content = catalog.entries()
|
||||
self.assertEqual(274, len(content.usages), 'Wrong number of usages')
|
||||
|
|
|
@ -26,14 +26,13 @@ class TestUsageFactory(TestCase):
|
|||
|
||||
def _get_citygml(self, file):
|
||||
file_path = (self._example_path / file).resolve()
|
||||
self._city = GeometryFactory('citygml', file_path).city
|
||||
self._city = GeometryFactory('citygml', path=file_path).city
|
||||
self.assertIsNotNone(self._city, 'city is none')
|
||||
return self._city
|
||||
|
||||
def _check_buildings(self, city):
|
||||
for building in city.buildings:
|
||||
self.assertIsNotNone(building.name, 'building name is none')
|
||||
self.assertIsNotNone(building.lod, 'building lod is none')
|
||||
self.assertIsNotNone(building.type, 'building type is none')
|
||||
self.assertIsNotNone(building.volume, 'building volume is none')
|
||||
self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none')
|
||||
|
|
9749
unittests/tests_data/EV_GM_MB_LoD2.gml
Normal file
9749
unittests/tests_data/EV_GM_MB_LoD2.gml
Normal file
File diff suppressed because it is too large
Load Diff
191088
unittests/tests_data/FZK-Haus-LoD4-KIT-IAI-KHH-B36-V1.gml
Normal file
191088
unittests/tests_data/FZK-Haus-LoD4-KIT-IAI-KHH-B36-V1.gml
Normal file
File diff suppressed because it is too large
Load Diff
80
unittests/tests_data/FZK_Haus_LoD_0.gml
Normal file
80
unittests/tests_data/FZK_Haus_LoD_0.gml
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Generated by: --><!-- IFC -> cityGML Converter --><!-- (C) - Institute for Applied Computer Science --><!-- Forschungszentrum Karlsruhe --><!-- Not for commercial use --><!-- Generated by: IfcExplorer--><!-- cityGML Schema: 1.0.0 --><!-- Level of Detail 1--><!-- Creation Date: Tuesday, 23 November 2010 - 10:37:59--><!-- Edited Manually in Oxygen 8.2 --><!-- Modified by GMLOffset.xslt at Mon Dec 6 2010 --><!-- Version 2 Building located in the area of KIT Campus North)--><!-- Modified by GMLOffset.xslt at Wed Dec 8 2010 --><!-- Modified by GMLOffset.xslt at Wed Mar 29 2017 --><core:CityModel xsi:schemaLocation="http://www.opengis.net/citygml/2.0 http://schemas.opengis.net/citygml/2.0/cityGMLBase.xsd http://www.opengis.net/citygml/appearance/2.0 http://schemas.opengis.net/citygml/appearance/2.0/appearance.xsd http://www.opengis.net/citygml/building/2.0 http://schemas.opengis.net/citygml/building/2.0/building.xsd http://www.opengis.net/citygml/generics/2.0 http://schemas.opengis.net/citygml/generics/2.0/generics.xsd" xmlns:core="http://www.opengis.net/citygml/2.0" xmlns="http://www.opengis.net/citygml/profiles/base/2.0" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:grp="http://www.opengis.net/citygml/cityobjectgroup/2.0" xmlns:app="http://www.opengis.net/citygml/appearance/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xAL="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<!-- Manually edited by KHH 23.01.2017, Address added, roof edge added -->
|
||||
<gml:name>AC14-FZK-Haus</gml:name>
|
||||
<gml:boundedBy>
|
||||
<gml:Envelope srsDimension="3" srsName="urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH">
|
||||
<gml:lowerCorner srsDimension="3">457842 5439083 111.8 </gml:lowerCorner>
|
||||
<gml:upperCorner srsDimension="3">457854 5439093 118.317669 </gml:upperCorner>
|
||||
</gml:Envelope>
|
||||
</gml:boundedBy>
|
||||
<core:cityObjectMember>
|
||||
<bldg:Building gml:id="UUID_d281adfc-4901-0f52-540b-4cc1a9325f82">
|
||||
<gml:description>FZK-Haus (Forschungszentrum Karlsruhe, now KIT), created by Karl-Heinz
|
||||
Haefele </gml:description>
|
||||
<gml:name>AC14-FZK-Haus</gml:name>
|
||||
<core:creationDate>2017-01-23</core:creationDate>
|
||||
<core:relativeToTerrain>entirelyAboveTerrain</core:relativeToTerrain>
|
||||
<gen:measureAttribute name="GrossPlannedArea">
|
||||
<gen:value uom="m2">120.00</gen:value>
|
||||
</gen:measureAttribute>
|
||||
<gen:stringAttribute name="ConstructionMethod">
|
||||
<gen:value>New Building</gen:value>
|
||||
</gen:stringAttribute>
|
||||
<gen:stringAttribute name="IsLandmarked">
|
||||
<gen:value>NO</gen:value>
|
||||
</gen:stringAttribute>
|
||||
<bldg:class codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_class.xml">1000</bldg:class>
|
||||
<bldg:function codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_function.xml">1000</bldg:function>
|
||||
<bldg:usage codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_usage.xml">1000</bldg:usage>
|
||||
<bldg:yearOfConstruction>2020</bldg:yearOfConstruction>
|
||||
<bldg:roofType codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_roofType.xml">1030</bldg:roofType>
|
||||
<bldg:measuredHeight uom="m">6.52</bldg:measuredHeight>
|
||||
<bldg:storeysAboveGround>2</bldg:storeysAboveGround>
|
||||
<bldg:storeysBelowGround>0</bldg:storeysBelowGround>
|
||||
<bldg:lod0FootPrint>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon>
|
||||
<gml:exterior>
|
||||
<gml:LinearRing>
|
||||
<gml:posList srsDimension="3">457842 5439083 111.8 457842 5439093 111.8 457854 5439093 111.8 457854 5439083 111.8 457842 5439083 111.8 </gml:posList>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod0FootPrint>
|
||||
<bldg:lod0RoofEdge>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon>
|
||||
<gml:exterior>
|
||||
<gml:LinearRing>
|
||||
<gml:posList srsDimension="3">457841.5 5439082.5 111.8 457841.5 5439093.5 111.8 457854.5 5439093.5 111.8 457854.5 5439082.5 111.8 457841.5 5439082.5 111.8 </gml:posList>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod0RoofEdge>
|
||||
<bldg:address>
|
||||
<core:Address>
|
||||
<core:xalAddress>
|
||||
<xAL:AddressDetails>
|
||||
<xAL:Locality Type="Town">
|
||||
<xAL:LocalityName>Eggenstein-Leopoldshafen</xAL:LocalityName>
|
||||
<xAL:Thoroughfare Type="Street">
|
||||
<xAL:ThoroughfareNumber>4711</xAL:ThoroughfareNumber>
|
||||
<xAL:ThoroughfareName>Spöcker Straße</xAL:ThoroughfareName>
|
||||
</xAL:Thoroughfare>
|
||||
<xAL:PostalCode>
|
||||
<xAL:PostalCodeNumber>76344</xAL:PostalCodeNumber>
|
||||
</xAL:PostalCode>
|
||||
</xAL:Locality>
|
||||
</xAL:AddressDetails>
|
||||
</core:xalAddress>
|
||||
</core:Address>
|
||||
</bldg:address>
|
||||
</bldg:Building>
|
||||
</core:cityObjectMember>
|
||||
</core:CityModel>
|
116
unittests/tests_data/FZK_Haus_LoD_1.gml
Normal file
116
unittests/tests_data/FZK_Haus_LoD_1.gml
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Generated by: --><!-- IFC -> cityGML Converter --><!-- (C) - Institute for Applied Computer Science --><!-- Forschungszentrum Karlsruhe --><!-- Not for commercial use --><!-- Generated by: IfcExplorer--><!-- cityGML Schema: 1.0.0 --><!-- Level of Detail 1--><!-- Creation Date: Tuesday, 23 November 2010 - 10:37:59--><!-- Edited Manually in Oxygen 8.2 --><!-- Modified by GMLOffset.xslt at Mon Dec 6 2010 --><!-- Version 2 Building located in the area of KIT Campus North)--><!-- Modified by GMLOffset.xslt at Wed Dec 8 2010 --><!-- Modified by GMLOffset.xslt at Wed Mar 29 2017 --><core:CityModel xsi:schemaLocation="http://www.opengis.net/citygml/2.0 http://schemas.opengis.net/citygml/2.0/cityGMLBase.xsd http://www.opengis.net/citygml/appearance/2.0 http://schemas.opengis.net/citygml/appearance/2.0/appearance.xsd http://www.opengis.net/citygml/building/2.0 http://schemas.opengis.net/citygml/building/2.0/building.xsd http://www.opengis.net/citygml/generics/2.0 http://schemas.opengis.net/citygml/generics/2.0/generics.xsd" xmlns:core="http://www.opengis.net/citygml/2.0" xmlns="http://www.opengis.net/citygml/profiles/base/2.0" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:grp="http://www.opengis.net/citygml/cityobjectgroup/2.0" xmlns:app="http://www.opengis.net/citygml/appearance/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xAL="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<!-- Manually edited by KHH 23.01.2017, CityGML 2.0, Address added, roof edge added -->
|
||||
<gml:name>AC14-FZK-Haus</gml:name>
|
||||
<gml:boundedBy>
|
||||
<gml:Envelope srsDimension="3" srsName="urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH">
|
||||
<gml:lowerCorner srsDimension="3">457842 5439083 111.8 </gml:lowerCorner>
|
||||
<gml:upperCorner srsDimension="3">457854 5439093 118.317669 </gml:upperCorner>
|
||||
</gml:Envelope>
|
||||
</gml:boundedBy>
|
||||
<core:cityObjectMember>
|
||||
<bldg:Building gml:id="UUID_d281adfc-4901-0f52-540b-4cc1a9325f82">
|
||||
<gml:description>FZK-Haus (Forschungszentrum Karlsruhe, now KIT), created by Karl-Heinz
|
||||
Haefele </gml:description>
|
||||
<gml:name>AC14-FZK-Haus</gml:name>
|
||||
<core:creationDate>2017-01-23</core:creationDate>
|
||||
<core:relativeToTerrain>entirelyAboveTerrain</core:relativeToTerrain>
|
||||
<gen:measureAttribute name="GrossPlannedArea">
|
||||
<gen:value uom="m2">120.00</gen:value>
|
||||
</gen:measureAttribute>
|
||||
<gen:stringAttribute name="ConstructionMethod">
|
||||
<gen:value>New Building</gen:value>
|
||||
</gen:stringAttribute>
|
||||
<gen:stringAttribute name="IsLandmarked">
|
||||
<gen:value>NO</gen:value>
|
||||
</gen:stringAttribute>
|
||||
<bldg:class codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_class.xml">1000</bldg:class>
|
||||
<bldg:function codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_function.xml">1000</bldg:function>
|
||||
<bldg:usage codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_usage.xml">1000</bldg:usage>
|
||||
<bldg:yearOfConstruction>2020</bldg:yearOfConstruction>
|
||||
<bldg:roofType codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_roofType.xml">1030</bldg:roofType>
|
||||
<bldg:measuredHeight uom="m">6.52</bldg:measuredHeight>
|
||||
<bldg:storeysAboveGround>2</bldg:storeysAboveGround>
|
||||
<bldg:storeysBelowGround>0</bldg:storeysBelowGround>
|
||||
<bldg:lod1Solid>
|
||||
<gml:Solid>
|
||||
<gml:exterior>
|
||||
<gml:CompositeSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon>
|
||||
<gml:exterior>
|
||||
<gml:LinearRing>
|
||||
<gml:posList srsDimension="3">457842 5439083 111.8 457842 5439093 111.8 457854 5439093 111.8 457854 5439083 111.8 457842 5439083 111.8 </gml:posList>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon>
|
||||
<gml:exterior>
|
||||
<gml:LinearRing>
|
||||
<gml:posList srsDimension="3">457842 5439083 118.31769 457854 5439083 118.31769 457854 5439093 118.31769 457842 5439093 118.31769 457842 5439083 118.31769 </gml:posList>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon>
|
||||
<gml:exterior>
|
||||
<gml:LinearRing>
|
||||
<gml:posList srsDimension="3">457842 5439083 111.8 457842 5439083 118.31769 457842 5439093 118.31769 457842 5439093 111.8 457842 5439083 111.8 </gml:posList>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon>
|
||||
<gml:exterior>
|
||||
<gml:LinearRing>
|
||||
<gml:posList srsDimension="3">457842 5439093 111.8 457842 5439093 118.31769 457854 5439093 118.31769 457854 5439093 111.8 457842 5439093 111.8 </gml:posList>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon>
|
||||
<gml:exterior>
|
||||
<gml:LinearRing>
|
||||
<gml:posList srsDimension="3">457854 5439093 111.8 457854 5439093 118.31769 457854 5439083 118.31769 457854 5439083 111.8 457854 5439093 111.8 </gml:posList>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon>
|
||||
<gml:exterior>
|
||||
<gml:LinearRing>
|
||||
<gml:posList srsDimension="3">457854 5439083 111.8 457854 5439083 118.31769 457842 5439083 118.31769 457842 5439083 111.8 457854 5439083 111.8 </gml:posList>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:CompositeSurface>
|
||||
</gml:exterior>
|
||||
</gml:Solid>
|
||||
</bldg:lod1Solid>
|
||||
<bldg:address>
|
||||
<core:Address>
|
||||
<core:xalAddress>
|
||||
<xAL:AddressDetails>
|
||||
<xAL:Locality Type="Town">
|
||||
<xAL:LocalityName>Eggenstein-Leopoldshafen</xAL:LocalityName>
|
||||
<xAL:Thoroughfare Type="Street">
|
||||
<xAL:ThoroughfareNumber>4711</xAL:ThoroughfareNumber>
|
||||
<xAL:ThoroughfareName>Spöcker Straße</xAL:ThoroughfareName>
|
||||
</xAL:Thoroughfare>
|
||||
<xAL:PostalCode>
|
||||
<xAL:PostalCodeNumber>76344</xAL:PostalCodeNumber>
|
||||
</xAL:PostalCode>
|
||||
</xAL:Locality>
|
||||
</xAL:AddressDetails>
|
||||
</core:xalAddress>
|
||||
</core:Address>
|
||||
</bldg:address>
|
||||
</bldg:Building>
|
||||
</core:cityObjectMember>
|
||||
</core:CityModel>
|
240
unittests/tests_data/FZK_Haus_LoD_2.gml
Normal file
240
unittests/tests_data/FZK_Haus_LoD_2.gml
Normal file
|
@ -0,0 +1,240 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- IFC to CityGML by IFCExplorer KIT --><!-- CityGML to Sketchup by Sketchup CityGML Plugin FH GelsenKirchen --><!--CityGML Dataset produced with CityGML Export Plugin for Sketchup by GeoRES --><!--http://www.geores.de --><!-- Edited Manually in Oxygen 8.2 --><!-- Modified by GMLOffset.xslt at Mon Dec 6 2010 --><!-- Version 2 Building located in the area of KIT Campus North)--><!-- Modified by GMLOffset.xslt at Wed Dec 8 2010 --><!-- Modified by GMLOffset.xslt at Wed Mar 29 2017 --><core:CityModel xsi:schemaLocation="http://www.opengis.net/citygml/2.0 http://schemas.opengis.net/citygml/2.0/cityGMLBase.xsd http://www.opengis.net/citygml/appearance/2.0 http://schemas.opengis.net/citygml/appearance/2.0/appearance.xsd http://www.opengis.net/citygml/building/2.0 http://schemas.opengis.net/citygml/building/2.0/building.xsd http://www.opengis.net/citygml/generics/2.0 http://schemas.opengis.net/citygml/generics/2.0/generics.xsd" xmlns:core="http://www.opengis.net/citygml/2.0" xmlns="http://www.opengis.net/citygml/profiles/base/2.0" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:grp="http://www.opengis.net/citygml/cityobjectgroup/2.0" xmlns:app="http://www.opengis.net/citygml/appearance/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xAL="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<!-- Manually edited by KHH 23.01.2017, CityGML 2.0, Address added, Codespaces added -->
|
||||
<gml:name>AC14-FZK-Haus</gml:name>
|
||||
<gml:boundedBy>
|
||||
<gml:Envelope srsDimension="3" srsName="urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH">
|
||||
<gml:lowerCorner srsDimension="3">457842 5439083 111.8 </gml:lowerCorner>
|
||||
<gml:upperCorner srsDimension="3">457854 5439093 118.317669 </gml:upperCorner>
|
||||
</gml:Envelope>
|
||||
</gml:boundedBy>
|
||||
<core:cityObjectMember>
|
||||
<bldg:Building gml:id="UUID_d281adfc-4901-0f52-540b-4cc1a9325f82">
|
||||
<gml:description>FZK-Haus (Forschungszentrum Karlsruhe, now KIT), created by Karl-Heinz
|
||||
Haefele </gml:description>
|
||||
<gml:name>AC14-FZK-Haus</gml:name>
|
||||
<core:creationDate>2017-01-23</core:creationDate>
|
||||
<core:relativeToTerrain>entirelyAboveTerrain</core:relativeToTerrain>
|
||||
<gen:measureAttribute name="GrossPlannedArea">
|
||||
<gen:value uom="m2">120.00</gen:value>
|
||||
</gen:measureAttribute>
|
||||
<gen:stringAttribute name="ConstructionMethod">
|
||||
<gen:value>New Building</gen:value>
|
||||
</gen:stringAttribute>
|
||||
<gen:stringAttribute name="IsLandmarked">
|
||||
<gen:value>NO</gen:value>
|
||||
</gen:stringAttribute>
|
||||
<bldg:class codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_class.xml">1000</bldg:class>
|
||||
<bldg:function codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_function.xml">1000</bldg:function>
|
||||
<bldg:usage codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_usage.xml">1000</bldg:usage>
|
||||
<bldg:yearOfConstruction>2020</bldg:yearOfConstruction>
|
||||
<bldg:roofType codeSpace="http://www.sig3d.org/codelists/citygml/2.0/building/2.0/_AbstractBuilding_roofType.xml">1030</bldg:roofType>
|
||||
<bldg:measuredHeight uom="m">6.52</bldg:measuredHeight>
|
||||
<bldg:storeysAboveGround>2</bldg:storeysAboveGround>
|
||||
<bldg:storeysBelowGround>0</bldg:storeysBelowGround>
|
||||
<bldg:lod2Solid>
|
||||
<gml:Solid>
|
||||
<gml:exterior>
|
||||
<gml:CompositeSurface>
|
||||
<!--Outer Wall 1 (West) -->
|
||||
<gml:surfaceMember xlink:href="#PolyID7350_878_759628_120742"> </gml:surfaceMember>
|
||||
<!--Outer Wall 1 (West) -->
|
||||
<!--Outer Wall 2 (South) -->
|
||||
<gml:surfaceMember xlink:href="#PolyID7351_1722_416019_316876" />
|
||||
<!--Outer Wall 2 (South) -->
|
||||
<!--Outer Wall 3 (East) -->
|
||||
<gml:surfaceMember xlink:href="#PolyID7352_230_209861_355851" />
|
||||
<!--Outer Wall 3 (East) -->
|
||||
<!--Roof 1 (North) -->
|
||||
<gml:surfaceMember xlink:href="#PolyID7353_166_774155_320806" />
|
||||
<!--Roof 1 (North) -->
|
||||
<!--Outer Wall 4 (North) -->
|
||||
<gml:surfaceMember xlink:href="#PolyID7354_1362_450904_410226" />
|
||||
<!--Outer Wall 2 (North) -->
|
||||
<!--Roof 2 (South) -->
|
||||
<gml:surfaceMember xlink:href="#PolyID7355_537_416207_260034" />
|
||||
<!--Roof 2 (South) -->
|
||||
<!--Base Surface -->
|
||||
<gml:surfaceMember xlink:href="#PolyID7356_612_880782_415367" />
|
||||
<!--Base Surface -->
|
||||
</gml:CompositeSurface>
|
||||
</gml:exterior>
|
||||
</gml:Solid>
|
||||
</bldg:lod2Solid>
|
||||
<bldg:boundedBy>
|
||||
<bldg:WallSurface gml:id="GML_5856d7ad-5e34-498a-817b-9544bfbb1475">
|
||||
<gml:name>Outer Wall 1 (West)</gml:name>
|
||||
<bldg:lod2MultiSurface>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon gml:id="PolyID7350_878_759628_120742">
|
||||
<gml:exterior>
|
||||
<gml:LinearRing gml:id="PolyID7350_878_759628_120742_0">
|
||||
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||
<gml:pos>457842 5439093 115.430940107676 </gml:pos>
|
||||
<gml:pos>457842 5439093 111.8 </gml:pos>
|
||||
<gml:pos>457842 5439083 111.8 </gml:pos>
|
||||
<gml:pos>457842 5439083 115.430940107676 </gml:pos>
|
||||
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod2MultiSurface>
|
||||
</bldg:WallSurface>
|
||||
</bldg:boundedBy>
|
||||
<bldg:boundedBy>
|
||||
<bldg:WallSurface gml:id="GML_d38cf762-c29d-4491-88c9-bdc89e141978">
|
||||
<gml:name>Outer Wall 2 (South)</gml:name>
|
||||
<bldg:lod2MultiSurface>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon gml:id="PolyID7351_1722_416019_316876">
|
||||
<gml:exterior>
|
||||
<gml:LinearRing gml:id="PolyID7351_1722_416019_316876_0">
|
||||
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||
<gml:pos>457842 5439083 115.430940107676 </gml:pos>
|
||||
<gml:pos>457842 5439083 111.8 </gml:pos>
|
||||
<gml:pos>457854 5439083 111.8 </gml:pos>
|
||||
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod2MultiSurface>
|
||||
</bldg:WallSurface>
|
||||
</bldg:boundedBy>
|
||||
<bldg:boundedBy>
|
||||
<bldg:WallSurface gml:id="GML_8e5db638-e46a-4739-a98a-2fc2d39c9069">
|
||||
<gml:name>Outer Wall 3 (East)</gml:name>
|
||||
<bldg:lod2MultiSurface>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon gml:id="PolyID7352_230_209861_355851">
|
||||
<gml:exterior>
|
||||
<gml:LinearRing gml:id="PolyID7352_230_209861_355851_0">
|
||||
<gml:pos>457854 5439088 118.317691453624 </gml:pos>
|
||||
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||
<gml:pos>457854 5439083 111.8 </gml:pos>
|
||||
<gml:pos>457854 5439093 111.8 </gml:pos>
|
||||
<gml:pos>457854 5439093 115.430940107676 </gml:pos>
|
||||
<gml:pos>457854 5439088 118.317691453624 </gml:pos>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod2MultiSurface>
|
||||
</bldg:WallSurface>
|
||||
</bldg:boundedBy>
|
||||
<bldg:boundedBy>
|
||||
<bldg:RoofSurface gml:id="GML_875d470b-32b4-4985-a4c8-0f02caa342a2">
|
||||
<gml:name>Roof 1 (North)</gml:name>
|
||||
<bldg:lod2MultiSurface>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon gml:id="PolyID7353_166_774155_320806">
|
||||
<gml:exterior>
|
||||
<gml:LinearRing gml:id="PolyID7353_166_774155_320806_0">
|
||||
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||
<gml:pos>457854 5439088 118.317691453624 </gml:pos>
|
||||
<gml:pos>457854 5439093 115.430940107676 </gml:pos>
|
||||
<gml:pos>457842 5439093 115.430940107676 </gml:pos>
|
||||
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod2MultiSurface>
|
||||
</bldg:RoofSurface>
|
||||
</bldg:boundedBy>
|
||||
<bldg:boundedBy>
|
||||
<bldg:WallSurface gml:id="GML_0f30f604-e70d-4dfe-ba35-853bc69609cc">
|
||||
<gml:name>Outer Wall 4 (North)</gml:name>
|
||||
<bldg:lod2MultiSurface>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon gml:id="PolyID7354_1362_450904_410226">
|
||||
<gml:exterior>
|
||||
<gml:LinearRing gml:id="PolyID7354_1362_450904_410226_0">
|
||||
<gml:pos>457842 5439093 115.430940107676 </gml:pos>
|
||||
<gml:pos>457854 5439093 115.430940107676 </gml:pos>
|
||||
<gml:pos>457854 5439093 111.8 </gml:pos>
|
||||
<gml:pos>457842 5439093 111.8 </gml:pos>
|
||||
<gml:pos>457842 5439093 115.430940107676 </gml:pos>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod2MultiSurface>
|
||||
</bldg:WallSurface>
|
||||
</bldg:boundedBy>
|
||||
<bldg:boundedBy>
|
||||
<bldg:RoofSurface gml:id="GML_eeb6796a-e261-4d3b-a6f2-475940cca80a">
|
||||
<gml:name>Roof 2 (South)</gml:name>
|
||||
<bldg:lod2MultiSurface>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon gml:id="PolyID7355_537_416207_260034">
|
||||
<gml:exterior>
|
||||
<gml:LinearRing gml:id="PolyID7355_537_416207_260034_0">
|
||||
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||
<gml:pos>457854 5439088 118.317691453624 </gml:pos>
|
||||
<gml:pos>457842 5439088 118.317691453624 </gml:pos>
|
||||
<gml:pos>457842 5439083 115.430940107676 </gml:pos>
|
||||
<gml:pos>457854 5439083 115.430940107676 </gml:pos>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod2MultiSurface>
|
||||
</bldg:RoofSurface>
|
||||
</bldg:boundedBy>
|
||||
<bldg:boundedBy>
|
||||
<bldg:GroundSurface gml:id="GML_257a8dde-8194-4ca3-b581-abd591dcd6a3">
|
||||
<gml:description>Bodenplatte</gml:description>
|
||||
<gml:name>Base Surface</gml:name>
|
||||
<bldg:lod2MultiSurface>
|
||||
<gml:MultiSurface>
|
||||
<gml:surfaceMember>
|
||||
<gml:Polygon gml:id="PolyID7356_612_880782_415367">
|
||||
<gml:exterior>
|
||||
<gml:LinearRing gml:id="PolyID7356_612_880782_415367_0">
|
||||
<gml:pos>457854 5439083 111.8 </gml:pos>
|
||||
<gml:pos>457842 5439083 111.8 </gml:pos>
|
||||
<gml:pos>457842 5439093 111.8 </gml:pos>
|
||||
<gml:pos>457854 5439093 111.8 </gml:pos>
|
||||
<gml:pos>457854 5439083 111.8 </gml:pos>
|
||||
</gml:LinearRing>
|
||||
</gml:exterior>
|
||||
</gml:Polygon>
|
||||
</gml:surfaceMember>
|
||||
</gml:MultiSurface>
|
||||
</bldg:lod2MultiSurface>
|
||||
</bldg:GroundSurface>
|
||||
</bldg:boundedBy>
|
||||
<bldg:address>
|
||||
<core:Address>
|
||||
<core:xalAddress>
|
||||
<xAL:AddressDetails>
|
||||
<xAL:Locality Type="Town">
|
||||
<xAL:LocalityName>Eggenstein-Leopoldshafen</xAL:LocalityName>
|
||||
<xAL:Thoroughfare Type="Street">
|
||||
<xAL:ThoroughfareNumber>4711</xAL:ThoroughfareNumber>
|
||||
<xAL:ThoroughfareName>Spöcker Straße</xAL:ThoroughfareName>
|
||||
</xAL:Thoroughfare>
|
||||
<xAL:PostalCode>
|
||||
<xAL:PostalCodeNumber>76344</xAL:PostalCodeNumber>
|
||||
</xAL:PostalCode>
|
||||
</xAL:Locality>
|
||||
</xAL:AddressDetails>
|
||||
</core:xalAddress>
|
||||
</core:Address>
|
||||
</bldg:address>
|
||||
</bldg:Building>
|
||||
</core:cityObjectMember>
|
||||
</core:CityModel>
|
3451
unittests/tests_data/FZK_Haus_LoD_3.gml
Normal file
3451
unittests/tests_data/FZK_Haus_LoD_3.gml
Normal file
File diff suppressed because it is too large
Load Diff
52243
unittests/tests_data/concordia.geojson
Normal file
52243
unittests/tests_data/concordia.geojson
Normal file
File diff suppressed because it is too large
Load Diff
1408
unittests/tests_data/custom.geojson
Normal file
1408
unittests/tests_data/custom.geojson
Normal file
File diff suppressed because it is too large
Load Diff
8761
unittests/tests_data/one_building_in_kelowna_sra_SW.out
Normal file
8761
unittests/tests_data/one_building_in_kelowna_sra_SW.out
Normal file
File diff suppressed because it is too large
Load Diff
18
unittests/tests_data/sample.geojson
Normal file
18
unittests/tests_data/sample.geojson
Normal file
|
@ -0,0 +1,18 @@
|
|||
{ "type": "FeatureCollection",
|
||||
"features": [
|
||||
{ "type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[ [[-73.5027962600162, 45.6572759731914], [-73.5027463586105, 45.6572669735158], [-73.5027513584185, 45.6572530729948], [-73.5026715592026, 45.6572412737672], [-73.5026410593539, 45.6573430727752], [-73.5027703584728, 45.6573621728624], [-73.5027962600162, 45.6572759731914]] ]
|
||||
]
|
||||
|
||||
},
|
||||
"properties": {
|
||||
"geom": {"type": "Polygon", "crs": {"type": "name", "properties": {"name": "urn:ogc:def:crs:EPSG::4326"}}, "coordinates": [[[3849322.0855625975, 6060583.24800576], [3849326.3956304314, 6060584.796717078], [3849327.0180495544, 6060583.089519385], [3849333.725799462, 6060585.837955164], [3849328.71788522, 6060598.03498192], [3849317.850609142, 6060593.57976506], [3849322.0855625975, 6060583.24800576]]]},
|
||||
"height": 13.0790429485,
|
||||
"year_built": 2000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user