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:
Guillermo Gutierrez Morote 2023-01-09 21:36:44 +00:00
commit 4bf2ff4ff0
93 changed files with 269071 additions and 1228 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@
.DS_Store .DS_Store
.env .env
logs logs
**/__pycache__/

View File

@ -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 Add the following code to your main.py
from imports.geometry_factory import GeometryFactory 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! 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 First, commit your changes by clicking on the green check in the top-right corner of Pycharm. Add a comment

View File

@ -167,9 +167,9 @@ Attributes with known units should be explicit in method's comment.
#### To do's. #### 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 ```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
``` ```

View File

@ -237,7 +237,7 @@ Add the following code to your main.py
from imports.geometry_factory import GeometryFactory 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! 9. Always remember to push your own project changes as the last thing you do before ending your working day!

View File

@ -116,7 +116,7 @@ class NrelCatalog(Catalog):
climate_zone = archetype['@climate_zone'] climate_zone = archetype['@climate_zone']
construction_period = reference_standard_to_construction_period[archetype['@reference_standard']] construction_period = reference_standard_to_construction_period[archetype['@reference_standard']]
average_storey_height = archetype['average_storey_height']['#text'] 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'] extra_loses_due_to_thermal_bridges = archetype['extra_loses_due_to_thermal_bridges']['#text']
indirect_heated_ratio = archetype['indirect_heated_ratio']['#text'] indirect_heated_ratio = archetype['indirect_heated_ratio']['#text']
infiltration_rate_for_ventilation_system_off = archetype['infiltration_rate_for_ventilation_system_off']['#text'] infiltration_rate_for_ventilation_system_off = archetype['infiltration_rate_for_ventilation_system_off']['#text']

View File

@ -12,7 +12,7 @@ Catalog = TypeVar('Catalog')
class GreeneryCatalogFactory: class GreeneryCatalogFactory:
""" """
GeometryFactory class GreeneryCatalogFactory class
""" """
def __init__(self, file_type, base_path=None): def __init__(self, file_type, base_path=None):
if base_path is None: if base_path is None:

View File

@ -193,7 +193,6 @@ class ComnetCatalog(Catalog):
process_data[usage_type] = usage_parameters[24:26].values.tolist() process_data[usage_type] = usage_parameters[24:26].values.tolist()
schedules_key[usage_type] = usage_parameters[27:28].item() schedules_key[usage_type] = usage_parameters[27:28].item()
return {'lighting': lighting_data, return {'lighting': lighting_data,
'plug loads': plug_loads_data, 'plug loads': plug_loads_data,
'occupancy': occupancy_data, 'occupancy': occupancy_data,

View 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")

View File

@ -13,6 +13,51 @@ class UsageHelper:
""" """
Usage helper class 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 = { _usage_to_hft = {
cte.RESIDENTIAL: 'residential', cte.RESIDENTIAL: 'residential',
cte.SINGLE_FAMILY_HOUSE: 'Single family house', cte.SINGLE_FAMILY_HOUSE: 'Single family house',
@ -71,7 +116,7 @@ class UsageHelper:
cte.GREEN_HOUSE: cte.GREEN_HOUSE, cte.GREEN_HOUSE: cte.GREEN_HOUSE,
cte.NON_HEATED: cte.NON_HEATED cte.NON_HEATED: cte.NON_HEATED
} }
_comnet_data_type_to_hub_data_type = { _comnet_data_type_to_hub_data_type = {
'Fraction': cte.FRACTION, 'Fraction': cte.FRACTION,
'OnOff': cte.ON_OFF, 'OnOff': cte.ON_OFF,
@ -93,6 +138,22 @@ class UsageHelper:
'C-14 Gymnasium': 'C-14 Gymnasium' '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 @property
def comnet_data_type_to_hub_data_type(self): def comnet_data_type_to_hub_data_type(self):
return self._comnet_data_type_to_hub_data_type return self._comnet_data_type_to_hub_data_type

View File

@ -8,6 +8,7 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
from pathlib import Path from pathlib import Path
from typing import TypeVar from typing import TypeVar
from catalog_factories.usage.comnet_catalog import ComnetCatalog from catalog_factories.usage.comnet_catalog import ComnetCatalog
from catalog_factories.usage.nrcan_catalog import NrcanCatalog
Catalog = TypeVar('Catalog') Catalog = TypeVar('Catalog')
@ -25,6 +26,14 @@ class UsageCatalogFactory:
""" """
return ComnetCatalog(self._path) return ComnetCatalog(self._path)
@property
def _nrcan(self):
"""
Retrieve NRCAN catalog
"""
# nrcan retrieves the data directly from github
return NrcanCatalog(self._path)
@property @property
def catalog(self) -> Catalog: def catalog(self) -> Catalog:
""" """

View File

@ -8,6 +8,7 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
from typing import List, Union from typing import List, Union
import numpy as np import numpy as np
import helpers.constants as cte
from city_model_structure.building_demand.surface import Surface from city_model_structure.building_demand.surface import Surface
from city_model_structure.city_object import CityObject from city_model_structure.city_object import CityObject
from city_model_structure.building_demand.household import Household from city_model_structure.building_demand.household import Household
@ -19,8 +20,8 @@ class Building(CityObject):
""" """
Building(CityObject) class Building(CityObject) class
""" """
def __init__(self, name, lod, surfaces, year_of_construction, function, city_lower_corner, terrains=None): def __init__(self, name, surfaces, year_of_construction, function, terrains=None):
super().__init__(name, lod, surfaces, city_lower_corner) super().__init__(name, surfaces)
self._households = None self._households = None
self._basement_heated = None self._basement_heated = None
self._attic_heated = None self._attic_heated = None
@ -37,6 +38,9 @@ class Building(CityObject):
self._type = 'building' self._type = 'building'
self._heating = dict() self._heating = dict()
self._cooling = 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._eave_height = None
self._grounds = [] self._grounds = []
self._roofs = [] self._roofs = []
@ -48,11 +52,11 @@ class Building(CityObject):
self._min_z = min(self._min_z, surface.lower_corner[2]) self._min_z = min(self._min_z, surface.lower_corner[2])
surface.id = surface_id surface.id = surface_id
# todo: consider all type of surfaces, not only these four # todo: consider all type of surfaces, not only these four
if surface.type == 'Ground': if surface.type == cte.GROUND:
self._grounds.append(surface) self._grounds.append(surface)
elif surface.type == 'Wall': elif surface.type == cte.WALL:
self._walls.append(surface) self._walls.append(surface)
elif surface.type == 'Roof': elif surface.type == cte.ROOF:
self._roofs.append(surface) self._roofs.append(surface)
else: else:
self._internal_walls.append(surface) self._internal_walls.append(surface)
@ -60,11 +64,18 @@ class Building(CityObject):
@property @property
def shell(self) -> Polyhedron: def shell(self) -> Polyhedron:
""" """
Get building shell Get building's external polyhedron
:return: [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: if self._shell is None:
self._shell = Polyhedron(self.surfaces) self._shell = Polyhedron(polygons)
return self._shell return self._shell
@property @property
@ -103,6 +114,14 @@ class Building(CityObject):
""" """
return self._walls return self._walls
@property
def internal_walls(self) -> List[Surface]:
"""
Get building internal wall surfaces
:return: [Surface]
"""
return self._internal_walls
@property @property
def terrains(self) -> Union[None, List[Surface]]: def terrains(self) -> Union[None, List[Surface]]:
""" """
@ -115,6 +134,9 @@ class Building(CityObject):
def attic_heated(self) -> Union[None, int]: def attic_heated(self) -> Union[None, int]:
""" """
Get if the city object attic is heated 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: None or int
""" """
return self._attic_heated return self._attic_heated
@ -123,6 +145,9 @@ class Building(CityObject):
def attic_heated(self, value): def attic_heated(self, value):
""" """
Set if the city object attic is heated 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 :param value: int
""" """
if value is not None: if value is not None:
@ -132,6 +157,9 @@ class Building(CityObject):
def basement_heated(self) -> Union[None, int]: def basement_heated(self) -> Union[None, int]:
""" """
Get if the city object basement is heated 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: None or int
""" """
return self._basement_heated return self._basement_heated
@ -140,6 +168,9 @@ class Building(CityObject):
def basement_heated(self, value): def basement_heated(self, value):
""" """
Set if the city object basement is heated 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 :param value: int
""" """
if value is not None: if value is not None:
@ -150,7 +181,7 @@ class Building(CityObject):
""" """
Raises not implemented error 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 raise NotImplementedError
@property @property
@ -253,6 +284,54 @@ class Building(CityObject):
""" """
self._cooling = value 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 @property
def eave_height(self): def eave_height(self):
""" """
@ -327,7 +406,7 @@ class Building(CityObject):
@property @property
def human_readable_name(self): def human_readable_name(self):
""" """
Get the human readable name for the building Get the human-readable name for the building
:return: str :return: str
""" """
return self._human_readable_name return self._human_readable_name
@ -335,6 +414,6 @@ class Building(CityObject):
@human_readable_name.setter @human_readable_name.setter
def human_readable_name(self, value): 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 self._human_readable_name = value

View File

@ -66,7 +66,9 @@ class Storey:
windows_areas = [] windows_areas = []
for hole in surface.holes_polygons: for hole in surface.holes_polygons:
windows_areas.append(hole.area) 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 return self._thermal_boundaries
@property @property
@ -78,7 +80,7 @@ class Storey:
if self._virtual_surfaces is None: if self._virtual_surfaces is None:
self._virtual_surfaces = [] self._virtual_surfaces = []
for thermal_boundary in self.thermal_boundaries: 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 return self._virtual_surfaces
@property @property

View File

@ -13,6 +13,8 @@ from typing import List, Union
from city_model_structure.attributes.polygon import Polygon from city_model_structure.attributes.polygon import Polygon
from city_model_structure.attributes.plane import Plane from city_model_structure.attributes.plane import Plane
from city_model_structure.attributes.point import Point 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 import helpers.constants as cte
@ -21,15 +23,13 @@ class Surface:
Surface class 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._type = surface_type
self._swr = swr
self._name = name self._name = name
self._id = None self._id = None
self._azimuth = None self._azimuth = None
self._inclination = None self._inclination = None
self._area_above_ground = None self._area = None
self._area_below_ground = None
self._lower_corner = None self._lower_corner = None
self._upper_corner = None self._upper_corner = None
self._shared_surfaces = [] self._shared_surfaces = []
@ -37,9 +37,11 @@ class Surface:
self._perimeter_polygon = perimeter_polygon self._perimeter_polygon = perimeter_polygon
self._holes_polygons = holes_polygons self._holes_polygons = holes_polygons
self._solid_polygon = solid_polygon self._solid_polygon = solid_polygon
self._pv_system_installed = None self._short_wave_reflectance = None
self._long_wave_emittance = None
self._inverse = 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 @property
def name(self): def name(self):
@ -78,23 +80,6 @@ class Surface:
""" """
raise NotImplementedError 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): def _max_coord(self, axis):
if axis == 'x': if axis == 'x':
axis = 0 axis = 0
@ -146,23 +131,13 @@ class Surface:
return self._upper_corner return self._upper_corner
@property @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 :return: float
""" """
if self._area_above_ground is None: self._area = self.perimeter_polygon.area
self._area_above_ground = self.perimeter_polygon.area - self.area_below_ground return self._area
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
@property @property
def azimuth(self): def azimuth(self):
@ -262,6 +237,42 @@ class Surface:
""" """
self._holes_polygons = value 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 @property
def inverse(self) -> Surface: def inverse(self) -> Surface:
""" """
@ -302,3 +313,35 @@ class Surface:
surface_child = Surface(part_1, part_1, name=self.name, surface_type=self.type) 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) rest_surface = Surface(part_2, part_2, name=self.name, surface_type=self.type)
return surface_child, rest_surface, intersection 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

View File

@ -7,13 +7,14 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
""" """
import uuid import uuid
from typing import List, Union from typing import List, Union, TypeVar
from helpers.configuration_helper import ConfigurationHelper as ch 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.layer import Layer
from city_model_structure.building_demand.thermal_opening import ThermalOpening 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.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: class ThermalBoundary:
@ -28,22 +29,14 @@ class ThermalBoundary:
self._thermal_zones = None self._thermal_zones = None
self._thermal_openings = None self._thermal_openings = None
self._layers = 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._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._thickness = None
self._virtual_internal_surface = None self._internal_surface = None
self._inside_emissivity = None
self._alpha_coefficient = None
self._radiative_coefficient = None
self._window_ratio = None self._window_ratio = None
self._window_ratio_is_calculated = False self._window_ratio_is_calculated = False
self._vegetation = None
@property @property
def id(self): def id(self):
@ -79,23 +72,6 @@ class ThermalBoundary:
""" """
self._thermal_zones = value 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 @property
def opaque_area(self): def opaque_area(self):
""" """
@ -118,58 +94,6 @@ class ThermalBoundary:
self._thickness += layer.thickness self._thickness += layer.thickness
return self._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 @property
def thermal_openings(self) -> Union[None, List[ThermalOpening]]: def thermal_openings(self) -> Union[None, List[ThermalOpening]]:
""" """
@ -290,13 +214,16 @@ class ThermalBoundary:
if self._u_value is None: if self._u_value is None:
h_i = self.hi h_i = self.hi
h_e = self.he 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: try:
for layer in self.layers: for layer in self.layers:
if layer.material.no_mass: if layer.material.no_mass:
r_value += float(layer.material.thermal_resistance) r_value += float(layer.material.thermal_resistance)
else: 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 self._u_value = 1.0/r_value
except TypeError: except TypeError:
raise Exception('Constructions layers are not initialized') from TypeError raise Exception('Constructions layers are not initialized') from TypeError
@ -311,24 +238,6 @@ class ThermalBoundary:
if value is not None: if value is not None:
self._u_value = float(value) 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 @property
def hi(self) -> Union[None, float]: def hi(self) -> Union[None, float]:
""" """
@ -364,78 +273,11 @@ class ThermalBoundary:
self._he = value self._he = value
@property @property
def virtual_internal_surface(self) -> Surface: def internal_surface(self) -> Surface:
""" """
Get the internal surface of the thermal boundary Get the internal surface of the thermal boundary
:return: Surface :return: Surface
""" """
if self._virtual_internal_surface is None: if self._internal_surface is None:
self._virtual_internal_surface = self.parent_surface.inverse self._internal_surface = self.parent_surface.inverse
return self._virtual_internal_surface return self._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

View File

@ -19,19 +19,13 @@ class ThermalOpening:
def __init__(self): def __init__(self):
self._id = None self._id = None
self._area = None self._area = None
self._openable_ratio = None
self._conductivity = None self._conductivity = None
self._frame_ratio = None self._frame_ratio = None
self._g_value = None self._g_value = None
self._thickness = 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._overall_u_value = None
self._hi = ch().convective_heat_transfer_coefficient_interior self._hi = ch().convective_heat_transfer_coefficient_interior
self._he = ch().convective_heat_transfer_coefficient_exterior self._he = ch().convective_heat_transfer_coefficient_exterior
self._inside_emissivity = None
self._alpha_coefficient = None
self._radiative_coefficient = None
self._construction_name = None self._construction_name = None
@property @property
@ -61,20 +55,6 @@ class ThermalOpening:
if value is not None: if value is not None:
self._area = float(value) 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 @property
def conductivity(self) -> Union[None, float]: 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: if self._overall_u_value is None and self.thickness is not None:
h_i = self.hi h_i = self.hi
h_e = self.he 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 self._overall_u_value = 1 / r_value
@property @property
@ -119,7 +99,7 @@ class ThermalOpening:
@property @property
def g_value(self) -> Union[None, float]: def g_value(self) -> Union[None, float]:
""" """
Get thermal opening g-value Get thermal opening transmittance at normal incidence
:return: None or float :return: None or float
""" """
return self._g_value return self._g_value
@ -127,7 +107,7 @@ class ThermalOpening:
@g_value.setter @g_value.setter
def g_value(self, value): def g_value(self, value):
""" """
Set thermal opening g-value Set thermal opening transmittance at normal incidence
:param value: float :param value: float
""" """
if value is not None: if value is not None:
@ -154,43 +134,9 @@ class ThermalOpening:
if self._overall_u_value is None and self.conductivity is not None: if self._overall_u_value is None and self.conductivity is not None:
h_i = self.hi h_i = self.hi
h_e = self.he 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 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 @property
def overall_u_value(self) -> Union[None, float]: def overall_u_value(self) -> Union[None, float]:
""" """
@ -242,57 +188,6 @@ class ThermalOpening:
if value is not None: if value is not None:
self._he = float(value) 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 @property
def construction_name(self): def construction_name(self):
""" """

View File

@ -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.lighting import Lighting
from city_model_structure.building_demand.internal_gain import InternalGain from city_model_structure.building_demand.internal_gain import InternalGain
from city_model_structure.building_demand.thermal_control import ThermalControl 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 from city_model_structure.attributes.schedule import Schedule
import helpers.constants as cte import helpers.constants as cte
@ -53,26 +52,26 @@ class ThermalZone:
self._appliances = None self._appliances = None
self._internal_gains = None self._internal_gains = None
self._thermal_control = None self._thermal_control = None
self._usage_zones = None self._usages = None
@property @property
def usage_zones(self): def usage_zones(self):
# example 70-office_30-residential # example 70-office_30-residential
if self._usage_from_parent: 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: else:
values = self._usage.split('_') values = self._usage.split('_')
usages = [] usages = []
for value in values: for value in values:
usages.append(value.split('-')) usages.append(value.split('-'))
self._usage_zones = [] self._usages = []
for parent_usage_zone in self._parent_internal_zone.usage_zones: for parent_usage in self._parent_internal_zone.usage_zones:
for value in usages: for value in usages:
if parent_usage_zone.usage == value[1]: if parent_usage.usage == value[1]:
new_usage_zone = copy.deepcopy(parent_usage_zone) new_usage = copy.deepcopy(parent_usage)
new_usage_zone.percentage = float(value[0])/100 new_usage.percentage = float(value[0])/100
self._usage_zones.append(new_usage_zone) self._usages.append(new_usage)
return self._usage_zones return self._usages
@property @property
def id(self): def id(self):
@ -103,7 +102,7 @@ class ThermalZone:
@property @property
def additional_thermal_bridge_u_value(self) -> Union[None, float]: 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: None or float
""" """
return self._additional_thermal_bridge_u_value return self._additional_thermal_bridge_u_value
@ -111,7 +110,7 @@ class ThermalZone:
@additional_thermal_bridge_u_value.setter @additional_thermal_bridge_u_value.setter
def additional_thermal_bridge_u_value(self, value): 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 :param value: float
""" """
if value is not None: if value is not None:
@ -208,15 +207,6 @@ class ThermalZone:
if value is not None: if value is not None:
self._ordinate_number = int(value) 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 @property
def view_factors_matrix(self): def view_factors_matrix(self):
""" """
@ -483,6 +473,9 @@ class ThermalZone:
* internal_gain.latent_fraction * internal_gain.latent_fraction
for usage_zone in self.usage_zones: for usage_zone in self.usage_zones:
for internal_gain in usage_zone.internal_gains: for internal_gain in usage_zone.internal_gains:
if internal_gain.schedules is None:
_schedules_defined = False
break
if len(internal_gain.schedules) == 0: if len(internal_gain.schedules) == 0:
_schedules_defined = False _schedules_defined = False
break break

View File

@ -7,6 +7,7 @@ Code contributors: Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
import uuid import uuid
from typing import Union, List 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.occupancy import Occupancy
from city_model_structure.building_demand.lighting import Lighting from city_model_structure.building_demand.lighting import Lighting
from city_model_structure.building_demand.appliances import Appliances from city_model_structure.building_demand.appliances import Appliances
@ -79,9 +80,47 @@ class UsageZone:
@property @property
def internal_gains(self) -> List[InternalGain]: def internal_gains(self) -> List[InternalGain]:
""" """
Get usage zone internal gains Calculates and returns the list of all internal gains defined
:return: [InternalGain] :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 return self._internal_gains
@internal_gains.setter @internal_gains.setter

View File

@ -17,8 +17,8 @@ class BusSystem(CityObject):
""" """
BusSystem(CityObject) class BusSystem(CityObject) class
""" """
def __init__(self, name, lod, surfaces, city_lower_corner): def __init__(self, name, surfaces, city_lower_corner):
super().__init__(name, lod, surfaces, city_lower_corner) super().__init__(name, surfaces, city_lower_corner)
self._bus_routes = None self._bus_routes = None
self._bus_network = None self._bus_network = None
self._buses = None self._buses = None

View File

@ -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.buildings_cluster import BuildingsCluster
from city_model_structure.fuel import Fuel from city_model_structure.fuel import Fuel
from city_model_structure.iot.station import Station 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.machine import Machine
from city_model_structure.parts_consisting_building import PartsConsistingBuilding from city_model_structure.parts_consisting_building import PartsConsistingBuilding
from city_model_structure.subway_entrance import SubwayEntrance from city_model_structure.subway_entrance import SubwayEntrance
@ -59,6 +60,7 @@ class City:
self._machines = None self._machines = None
self._stations = [] self._stations = []
self._lca_materials = None self._lca_materials = None
self._level_of_detail = LevelOfDetail()
@property @property
def fuels(self) -> [Fuel]: def fuels(self) -> [Fuel]:
@ -426,9 +428,9 @@ class City:
""" """
self._lca_materials = value 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 :return: LcaMaterial or None
""" """
for lca_material in self.lca_materials: for lca_material in self.lca_materials:
@ -448,3 +450,11 @@ class City:
for city_object in city.city_objects: for city_object in city.city_objects:
_merge_city.add_city_object(city_object) _merge_city.add_city_object(city_object)
return _merge_city 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

View File

@ -18,11 +18,9 @@ class CityObject:
""" """
class CityObject class CityObject
""" """
def __init__(self, name, lod, surfaces, city_lower_corner): def __init__(self, name, surfaces):
self._name = name self._name = name
self._lod = lod
self._surfaces = surfaces self._surfaces = surfaces
self._city_lower_corner = city_lower_corner
self._type = None self._type = None
self._city_object_lower_corner = None self._city_object_lower_corner = None
self._detailed_polyhedron = None self._detailed_polyhedron = None
@ -45,15 +43,6 @@ class CityObject:
""" """
return self._name 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 @property
def type(self) -> str: def type(self) -> str:
""" """
@ -236,3 +225,5 @@ class CityObject:
:param value: [Sensor] :param value: [Sensor]
""" """
self._sensors = value self._sensors = value

View File

@ -20,8 +20,7 @@ class CityObjectsCluster(ABC, CityObject):
self._cluster_type = cluster_type self._cluster_type = cluster_type
self._city_objects = city_objects self._city_objects = city_objects
self._sensors = [] self._sensors = []
self._lod = '' super().__init__(name, None)
super().__init__(name, self._lod, None, None)
@property @property
def name(self): def name(self):

View File

@ -16,8 +16,8 @@ class EnergySystem(CityObject):
EnergySystem(CityObject) class EnergySystem(CityObject) class
""" """
def __init__(self, name, lod, surfaces, city_lower_corner): def __init__(self, name, surfaces):
super().__init__(name, lod, surfaces, city_lower_corner) super().__init__(name, surfaces)
self._air_source_hp = None self._air_source_hp = None
self._water_to_water_hp = None self._water_to_water_hp = None
self._type = 'energy_system' self._type = 'energy_system'

View File

@ -4,7 +4,8 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
from typing import Union from typing import Union, List
from city_model_structure.building_demand.thermal_zone import ThermalZone
class HvacSystem: class HvacSystem:
@ -13,11 +14,12 @@ class HvacSystem:
""" """
def __init__(self): def __init__(self):
self._type = None self._type = None
self._thermal_zones = None
@property @property
def type(self) -> Union[None, str]: def type(self) -> Union[None, str]:
""" """
Get hvac system type a thermal zone Get hvac system type
:return: None or str :return: None or str
""" """
return self._type return self._type
@ -25,9 +27,24 @@ class HvacSystem:
@type.setter @type.setter
def type(self, value): def type(self, value):
""" """
Set heating set point defined for a thermal zone Set hvac system type
:param value: str :param value: str
""" """
if value is not None: if value is not None:
self._type = str(value) 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

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

View 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

View File

@ -12,7 +12,7 @@ class SubwayEntrance(CityObject):
SubwayEntrance(CityObject) class SubwayEntrance(CityObject) class
""" """
def __init__(self, name, latitude, longitude): def __init__(self, name, latitude, longitude):
super().__init__(0, [], name, []) super().__init__(name, 0, [])
self._name = name self._name = name
self._latitude = latitude self._latitude = latitude
self._longitude = longitude self._longitude = longitude

View File

@ -11,5 +11,10 @@ comnet_occupancy_sensible_radiant = 0.1
comnet_plugs_latent = 0 comnet_plugs_latent = 0
comnet_plugs_convective = 0.75 comnet_plugs_convective = 0.75
comnet_plugs_radiant = 0.25 comnet_plugs_radiant = 0.25
#W/m2K
convective_heat_transfer_coefficient_interior = 3.5 convective_heat_transfer_coefficient_interior = 3.5
convective_heat_transfer_coefficient_exterior = 20 convective_heat_transfer_coefficient_exterior = 20
#W/mK
soil_conductivity = 3
#m
soil_thickness = 0.5

9
data/usage/nrcan.xml Normal file
View 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>

View File

@ -7,7 +7,6 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
Soroush Samareh Abolhassani soroush.samarehabolhassani@mail.concordia.ca Soroush Samareh Abolhassani soroush.samarehabolhassani@mail.concordia.ca
""" """
import copy import copy
import math
from pathlib import Path from pathlib import Path
from geomeppy import IDF from geomeppy import IDF
import helpers.constants as cte import helpers.constants as cte
@ -18,33 +17,37 @@ class Idf:
""" """
Exports city to IDF Exports city to IDF
""" """
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT' _BUILDING = 'BUILDING'
_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'
_ZONE = 'ZONE' _ZONE = 'ZONE'
_LIGHTS = 'LIGHTS' _LIGHTS = 'LIGHTS'
_PEOPLE = 'PEOPLE' _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' _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' _SCHEDULE_LIMIT = 'SCHEDULETYPELIMITS'
_ON_OFF = 'On/Off' _ON_OFF = 'On/Off'
_FRACTION = 'Fraction' _FRACTION = 'Fraction'
_ANY_NUMBER = 'Any Number' _ANY_NUMBER = 'Any Number'
_CONTINUOUS = 'Continuous' _CONTINUOUS = 'Continuous'
_DISCRETE = 'Discrete' _DISCRETE = 'Discrete'
_BUILDING = 'BUILDING'
_SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY' _SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY'
_LOCATION = 'SITE:LOCATION' _LOCATION = 'SITE:LOCATION'
_WINDOW_MATERIAL_SIMPLE = 'WINDOWMATERIAL:SIMPLEGLAZINGSYSTEM'
_WINDOW = 'WINDOW'
_MATERIAL_ROOFVEGETATION = 'MATERIAL:ROOFVEGETATION'
_SIMPLE = 'Simple' _SIMPLE = 'Simple'
idf_surfaces = { idf_surfaces = {
@ -53,10 +56,6 @@ class Idf:
cte.GROUND: 'floor', cte.GROUND: 'floor',
cte.ROOF: 'roof' cte.ROOF: 'roof'
} }
idf_usage = {
# todo: make an enum for all the usage types
cte.RESIDENTIAL: 'residential_building'
}
idf_type_limits = { idf_type_limits = {
cte.ON_OFF: 'on/off', cte.ON_OFF: 'on/off',
cte.FRACTION: 'Fraction', cte.FRACTION: 'Fraction',
@ -76,22 +75,9 @@ class Idf:
cte.WINTER_DESIGN_DAY: 'WinterDesignDay', cte.WINTER_DESIGN_DAY: 'WinterDesignDay',
cte.SUMMER_DESIGN_DAY: 'SummerDesignDay' 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._city = city
self._output_path = str(output_path.resolve()) self._output_path = str(output_path.resolve())
self._output_file = str((output_path / f'{city.name}.idf').resolve()) self._output_file = str((output_path / f'{city.name}.idf').resolve())
@ -106,8 +92,15 @@ class Idf:
Numeric_Type=self._CONTINUOUS) Numeric_Type=self._CONTINUOUS)
self._idf.newidfobject(self._SCHEDULE_LIMIT, Name=self._ON_OFF, Lower_Limit_Value=0, Upper_Limit_Value=1, self._idf.newidfobject(self._SCHEDULE_LIMIT, Name=self._ON_OFF, Lower_Limit_Value=0, Upper_Limit_Value=1,
Numeric_Type=self._DISCRETE) 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() self._export()
@staticmethod @staticmethod
def _matrix_to_list(points, lower_corner): def _matrix_to_list(points, lower_corner):
lower_x = lower_corner[0] lower_x = lower_corner[0]
@ -255,8 +248,8 @@ class Idf:
def _add_construction(self, thermal_boundary): def _add_construction(self, thermal_boundary):
for construction in self._idf.idfobjects[self._CONSTRUCTION]: for construction in self._idf.idfobjects[self._CONSTRUCTION]:
if thermal_boundary.vegetation is not None: if thermal_boundary.parent_surface.vegetation is not None:
if construction.Name == f'{thermal_boundary.construction_name}_{thermal_boundary.vegetation.name}': if construction.Name == f'{thermal_boundary.construction_name}_{thermal_boundary.parent_surface.vegetation.name}':
return return
else: else:
if construction.Name == thermal_boundary.construction_name: if construction.Name == thermal_boundary.construction_name:
@ -271,9 +264,9 @@ class Idf:
self._add_material(layer) self._add_material(layer)
layers = thermal_boundary.layers layers = thermal_boundary.layers
# The constructions should have at least one layer # The constructions should have at least one layer
if thermal_boundary.vegetation is not None: if thermal_boundary.parent_surface.vegetation is not None:
_kwargs = {'Name': f'{thermal_boundary.construction_name}_{thermal_boundary.vegetation.name}', _kwargs = {'Name': f'{thermal_boundary.construction_name}_{thermal_boundary.parent_surface.vegetation.name}',
'Outside_Layer': thermal_boundary.vegetation.name} 'Outside_Layer': thermal_boundary.parent_surface.vegetation.name}
for i in range(0, len(layers) - 1): for i in range(0, len(layers) - 1):
_kwargs[f'Layer_{i + 2}'] = layers[i].material.name _kwargs[f'Layer_{i + 2}'] = layers[i].material.name
else: else:
@ -285,7 +278,7 @@ class Idf:
def _add_window_construction_and_material(self, thermal_opening): def _add_window_construction_and_material(self, thermal_opening):
for window_material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]: for window_material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]:
if window_material['UFactor'] == thermal_opening.overall_u_value and \ 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 return
order = str(len(self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]) + 1) 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} _kwargs = {'Name': window_construction_name, 'Outside_Layer': material_name}
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs) 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']: for zone in self._idf.idfobjects['ZONE']:
if zone.Name == thermal_zone.id: if zone.Name == name:
return return
# todo: what do we need to define a zone in energy plus? # 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._idf.newidfobject(self._ZONE, Name=name, Volume=thermal_zone.volume)
self._add_heating_system(thermal_zone) self._add_heating_system(thermal_zone, name)
def _add_thermostat(self, thermal_zone): def _add_thermostat(self, thermal_zone):
thermostat_name = f'Thermostat {thermal_zone.usage}' thermostat_name = f'Thermostat {thermal_zone.usage}'
@ -317,26 +310,26 @@ class Idf:
Heating_Setpoint_Schedule_Name=f'Heating thermostat schedules {thermal_zone.usage}', Heating_Setpoint_Schedule_Name=f'Heating thermostat schedules {thermal_zone.usage}',
Cooling_Setpoint_Schedule_Name=f'Cooling 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]: 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 return
thermostat = self._add_thermostat(thermal_zone) thermostat = self._add_thermostat(thermal_zone)
self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM, 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}', System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}',
Heating_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}', Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}',
Template_Thermostat_Name=thermostat.Name) 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 number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area
fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / \ fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / \
(thermal_zone.occupancy.sensible_radiative_internal_gain + (thermal_zone.occupancy.sensible_radiative_internal_gain +
thermal_zone.occupancy.sensible_convective_internal_gain) thermal_zone.occupancy.sensible_convective_internal_gain)
self._idf.newidfobject(self._PEOPLE, self._idf.newidfobject(self._PEOPLE,
Name=f'{thermal_zone.id}_occupancy', Name=f'{zone_name}_occupancy',
Zone_or_ZoneList_Name=thermal_zone.id, Zone_or_ZoneList_Name=zone_name,
Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}', Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}',
Number_of_People_Calculation_Method="People", Number_of_People_Calculation_Method="People",
Number_of_People=number_of_people, Number_of_People=number_of_people,
@ -344,21 +337,22 @@ class Idf:
Activity_Level_Schedule_Name=f'Activity Level schedules {thermal_zone.usage}' 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"]: for zone in self._idf.idfobjects["ZONE"]:
if zone.Name == f'{thermal_zone.id}_infiltration': if zone.Name == f'{zone_name}_infiltration':
return return
self._idf.newidfobject(self._INFILTRATION, self._idf.newidfobject(self._INFILTRATION,
Name=f'{thermal_zone.id}_infiltration', Name=f'{zone_name}_infiltration',
Zone_or_ZoneList_Name=thermal_zone.id, Zone_or_ZoneList_Name=zone_name,
Schedule_Name=f'Infiltration schedules {thermal_zone.usage}', Schedule_Name=f'Infiltration schedules {thermal_zone.usage}',
Design_Flow_Rate_Calculation_Method='AirChanges/Hour', Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
Air_Changes_per_Hour=thermal_zone.mechanical_air_change 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]: for building in self._idf.idfobjects[self._BUILDING]:
building.Name = b.name building.Name = f'Buildings in {name}'
building['Solar_Distribution'] = 'FullExterior' building['Solar_Distribution'] = 'FullExterior'
def _remove_sizing_periods(self): def _remove_sizing_periods(self):
@ -370,48 +364,64 @@ class Idf:
def _export(self): 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" export type = "Surfaces|Block"
""" """
self._remove_location() self._remove_location()
self._remove_sizing_periods() self._remove_sizing_periods()
self._rename_building(self._city.name)
self._lod = self._city.level_of_detail.geometry
for building in self._city.buildings: for building in self._city.buildings:
self._rename_building(building)
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones: for thermal_zone in internal_zone.thermal_zones:
for thermal_boundary in thermal_zone.thermal_boundaries: for thermal_boundary in thermal_zone.thermal_boundaries:
self._add_construction(thermal_boundary) self._add_construction(thermal_boundary)
if thermal_boundary.vegetation is not None: if thermal_boundary.parent_surface.vegetation is not None:
self._add_vegetation_material(thermal_boundary.vegetation) self._add_vegetation_material(thermal_boundary.parent_surface.vegetation)
for thermal_opening in thermal_boundary.thermal_openings: for thermal_opening in thermal_boundary.thermal_openings:
self._add_window_construction_and_material(thermal_opening) self._add_window_construction_and_material(thermal_opening)
usage = thermal_zone.usage usage = thermal_zone.usage
self._add_infiltration_schedules(thermal_zone) if building.name in self._target_buildings or building.name in self._adjacent_buildings:
self._add_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules) self._add_infiltration_schedules(thermal_zone)
self._add_schedules(usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules) self._add_schedules(usage, 'Occupancy', thermal_zone.occupancy.occupancy_schedules)
self._add_schedules(usage, 'Heating thermostat', thermal_zone.thermal_control.heating_set_point_schedules) self._add_schedules(usage, 'HVAC AVAIL', thermal_zone.thermal_control.hvac_availability_schedules)
self._add_schedules(usage, 'Cooling thermostat', thermal_zone.thermal_control.cooling_set_point_schedules) self._add_schedules(usage, 'Heating thermostat', thermal_zone.thermal_control.heating_set_point_schedules)
self._add_people_activity_level_schedules(thermal_zone) 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_zone(thermal_zone, building.name)
self._add_heating_system(thermal_zone) self._add_heating_system(thermal_zone, building.name)
self._add_infiltration(thermal_zone) self._add_infiltration(thermal_zone, building.name)
self._add_occupancy(thermal_zone) self._add_occupancy(thermal_zone, building.name)
if self._export_type == "Surfaces": 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: else:
self._add_block(building) 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( self._idf.newidfobject(
"OUTPUT:VARIABLE", "OUTPUT:VARIABLE",
Variable_Name="Zone Ideal Loads Supply Air Total Heating Energy", Variable_Name="Zone Ideal Loads Supply Air Total Heating Energy",
Reporting_Frequency="Hourly", Reporting_Frequency="Hourly",
) )
self._idf.newidfobject( self._idf.newidfobject(
"OUTPUT:VARIABLE", "OUTPUT:VARIABLE",
Variable_Name="Zone Ideal Loads Supply Air Total Cooling Energy", Variable_Name="Zone Ideal Loads Supply Air Total Cooling Energy",
Reporting_Frequency="Hourly", Reporting_Frequency="Hourly",
) )
self._idf.match() self._idf.match()
try: try:
self._idf.intersect_match() self._idf.intersect_match()
@ -445,7 +455,25 @@ class Idf:
break break
self._idf.intersect_match() 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 internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones: for thermal_zone in internal_zone.thermal_zones:
for boundary in thermal_zone.thermal_boundaries: for boundary in thermal_zone.thermal_boundaries:
@ -457,13 +485,13 @@ class Idf:
outside_boundary_condition = 'Ground' outside_boundary_condition = 'Ground'
sun_exposure = 'NoSun' sun_exposure = 'NoSun'
wind_exposure = 'NoWind' wind_exposure = 'NoWind'
if boundary.vegetation is not None: if boundary.parent_surface.vegetation is not None:
construction_name = f'{boundary.construction_name}_{boundary.vegetation.name}' construction_name = f'{boundary.construction_name}_{boundary.parent_surface.vegetation.name}'
else: else:
construction_name = boundary.construction_name construction_name = boundary.construction_name
surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.parent_surface.name}', surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.parent_surface.name}',
Surface_Type=idf_surface_type, Surface_Type=idf_surface_type,
Zone_Name=thermal_zone.id, Zone_Name=zone_name,
Construction_Name=construction_name, Construction_Name=construction_name,
Outside_Boundary_Condition=outside_boundary_condition, Outside_Boundary_Condition=outside_boundary_condition,
Sun_Exposure=sun_exposure, Sun_Exposure=sun_exposure,
@ -472,26 +500,29 @@ class Idf:
self._city.lower_corner) self._city.lower_corner)
surface.setcoords(coordinates) surface.setcoords(coordinates)
self._add_windows(boundary)
def _add_windows(self, boundary): if self._lod >= 3:
for opening in boundary.thermal_openings: for internal_zone in building.internal_zones:
for construction in self._idf.idfobjects[self._CONSTRUCTION]: for thermal_zone in internal_zone.thermal_zones:
if construction['Outside_Layer'].split('_')[0] == 'glazing': for boundary in thermal_zone.thermal_boundaries:
window_construction = construction self._add_windows_by_vertices(boundary)
if self._compare_window_constructions(window_construction, opening): else:
opening_name = 'window_' + str(len(self._idf.idfobjects[self._WINDOW]) + 1) # idf only allows setting wwr for external walls
opening_length = math.sqrt(opening.area) wwr = 0
self._idf.newidfobject(self._WINDOW, Name=f'{opening_name}', Construction_Name=window_construction['Name'], for surface in building.surfaces:
Building_Surface_Name=boundary.parent_surface.name, Multiplier='1', if surface.type == cte.WALL:
Length=opening_length, Height=opening_length) 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): def _compare_window_constructions(self, window_construction, opening):
glazing = window_construction['Outside_Layer'] glazing = window_construction['Outside_Layer']
for material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]: for material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]:
if material['Name'] == glazing: if material['Name'] == glazing:
if material['UFactor'] == opening.overall_u_value and \ 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 True
return False return False
@ -533,5 +564,4 @@ class Idf:
soil.residual_volumetric_moisture_content, soil.residual_volumetric_moisture_content,
Initial_Volumetric_Moisture_Content_of_the_Soil_Layer= Initial_Volumetric_Moisture_Content_of_the_Soil_Layer=
soil.initial_volumetric_moisture_content, soil.initial_volumetric_moisture_content,
Moisture_Diffusion_Calculation_Method=self._SIMPLE Moisture_Diffusion_Calculation_Method=self._SIMPLE)
)

View 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

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

View File

@ -194,7 +194,7 @@ class HeatPumpExport:
self._input_data["HPDisactivationTemperature"] = self._input_data["HPSupTemp"] - 2 self._input_data["HPDisactivationTemperature"] = self._input_data["HPSupTemp"] - 2
self._input_data["HPReactivationTemperature"] = self._input_data["HPSupTemp"] - 4 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() max_demand = self._compute_max_demand()
# compute TESCapacity # compute TESCapacity
self._input_data["TESCapacity"] = self._input_data["HoursOfStorageAtMaxDemand"] * (max_demand * 3.6) / ( self._input_data["TESCapacity"] = self._input_data["HoursOfStorageAtMaxDemand"] * (max_demand * 3.6) / (

View File

@ -6,8 +6,6 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from pathlib import Path 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.obj import Obj
from exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm from exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
from exports.formats.stl import Stl from exports.formats.stl import Stl
@ -17,13 +15,14 @@ class ExportsFactory:
""" """
Exports factory class 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._city = city
self._export_type = '_' + export_type.lower() self._export_type = '_' + export_type.lower()
if isinstance(path, str): if isinstance(path, str):
path = Path(path) path = Path(path)
self._path = path self._path = path
self._target_buildings = target_buildings self._target_buildings = target_buildings
self._adjacent_buildings = adjacent_buildings
@property @property
def _citygml(self): def _citygml(self):
@ -37,14 +36,6 @@ class ExportsFactory:
def _collada(self): def _collada(self):
raise NotImplementedError raise NotImplementedError
@property
def _energy_ade(self):
"""
Export to citygml with application domain extensions
:return: None
"""
return EnergyAde(self._city, self._path)
@property @property
def _stl(self): def _stl(self):
""" """
@ -69,17 +60,6 @@ class ExportsFactory:
""" """
return Obj(self._city, self._path).to_ground_points() 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 @property
def _sra(self): def _sra(self):
""" """
@ -101,4 +81,4 @@ class ExportsFactory:
Export the city given to the class using the given export type handler Export the city given to the class using the given export type handler
:return: None :return: None
""" """
return getattr(self, self._export_type) return Obj(self._city, self._path)

30
exports/formats/insel.py Normal file
View 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

View File

@ -26,7 +26,7 @@ class Obj(Triangular):
file_name_out = self._city.name + '_ground.' + self._triangular_format file_name_out = self._city.name + '_ground.' + self._triangular_format
file_path_in = (Path(self._path).resolve() / file_name_in).resolve() file_path_in = (Path(self._path).resolve() / file_name_in).resolve()
file_path_out = (Path(self._path).resolve() / file_name_out).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() scene.rezero()
obj_file = trimesh.exchange.obj.export_obj(scene) obj_file = trimesh.exchange.obj.export_obj(scene)
with open(file_path_out, 'w') as file: with open(file_path_out, 'w') as file:

View File

@ -111,7 +111,7 @@ class ConfigurationHelper:
def convective_heat_transfer_coefficient_interior(self) -> float: def convective_heat_transfer_coefficient_interior(self) -> float:
""" """
Get configured convective heat transfer coefficient for surfaces inside the building 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 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: def convective_heat_transfer_coefficient_exterior(self) -> float:
""" """
Get configured convective heat transfer coefficient for surfaces outside the building 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 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

View File

@ -6,7 +6,6 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
# universal constants # universal constants
import sys
KELVIN = 273.15 KELVIN = 273.15

View File

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

View File

@ -20,6 +20,9 @@ class GeometryHelper:
""" """
Geometry helper class Geometry helper class
""" """
srs_transformations = {
'urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH': 'epsg:25832'
}
def __init__(self, delta=0, area_delta=0): def __init__(self, delta=0, area_delta=0):
self._delta = delta self._delta = delta

47
helpers/monthly_values.py Normal file
View 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

View File

@ -60,7 +60,7 @@ class BuildingArchetype:
@property @property
def additional_thermal_bridge_u_value(self): 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: float
""" """
return self._additional_thermal_bridge_u_value return self._additional_thermal_bridge_u_value

View File

@ -103,7 +103,7 @@ class ConstructionHelper:
:param city: str :param city: str
:return: str :return: str
""" """
# ToDo: Dummy function that needs to be implemented # todo: Dummy function that needs to be implemented
reference_city = 'Baltimore' reference_city = 'Baltimore'
if city is not None: if city is not None:
reference_city = 'Baltimore' reference_city = 'Baltimore'

View File

@ -99,7 +99,7 @@ class StoreysGeneration:
polygon = Polygon(coordinates) polygon = Polygon(coordinates)
ceiling = Surface(polygon, polygon, surface_type=cte.INTERIOR_SLAB) ceiling = Surface(polygon, polygon, surface_type=cte.INTERIOR_SLAB)
surfaces_child.append(ceiling) surfaces_child.append(ceiling)
volume = ceiling.area_above_ground * height volume = ceiling.perimeter_area * height
total_volume += volume total_volume += volume
storeys.append(Storey(name, surfaces_child, neighbours, volume, self._internal_zone, self._floor_area)) storeys.append(Storey(name, surfaces_child, neighbours, volume, self._internal_zone, self._floor_area))
name = 'storey_' + str(number_of_storeys - 1) name = 'storey_' + str(number_of_storeys - 1)

View File

@ -29,7 +29,6 @@ class UsPhysicsParameters(NrelPhysicsInterface):
""" """
Returns the city with the construction parameters assigned to the buildings Returns the city with the construction parameters assigned to the buildings
""" """
# todo: erase
city = self._city city = self._city
for building in city.buildings: for building in city.buildings:
try: try:
@ -40,7 +39,8 @@ class UsPhysicsParameters(NrelPhysicsInterface):
f'and climate zone reference norm {self._climate_zone}\n') f'and climate zone reference norm {self._climate_zone}\n')
return 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 len(building.internal_zones) == 1:
if building.internal_zones[0].thermal_zones is None: if building.internal_zones[0].thermal_zones is None:
self._create_storeys(building, archetype, self._divide_in_storeys) self._create_storeys(building, archetype, self._divide_in_storeys)
@ -74,7 +74,7 @@ class UsPhysicsParameters(NrelPhysicsInterface):
if (str(function) == str(building_archetype.function)) and \ if (str(function) == str(building_archetype.function)) and \
(climate_zone == str(building_archetype.climate_zone)): (climate_zone == str(building_archetype.climate_zone)):
return building_archetype return building_archetype
return None raise KeyError('archetype not found')
@staticmethod @staticmethod
def _search_construction_in_archetype(archetype, construction_type): def _search_construction_in_archetype(archetype, construction_type):
@ -121,9 +121,13 @@ class UsPhysicsParameters(NrelPhysicsInterface):
thermal_boundary.layers.append(layer) thermal_boundary.layers.append(layer)
# The agreement is that the layers are defined from outside to inside # The agreement is that the layers are defined from outside to inside
external_layer = construction_archetype.layers[0] external_layer = construction_archetype.layers[0]
thermal_boundary.outside_solar_absorptance = external_layer.material.solar_absorptance external_surface = thermal_boundary.parent_surface
thermal_boundary.outside_thermal_absorptance = external_layer.material.thermal_absorptance external_surface.short_wave_reflectance = 1 - float(external_layer.material.solar_absorptance)
thermal_boundary.outside_visible_absorptance = external_layer.material.visible_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: for thermal_opening in thermal_boundary.thermal_openings:
if construction_archetype.window is not None: if construction_archetype.window is not None:

View File

@ -24,6 +24,7 @@ class ConstructionFactory:
Enrich the city by using NREL information Enrich the city by using NREL information
""" """
UsPhysicsParameters(self._city, self._base_path).enrich_buildings() UsPhysicsParameters(self._city, self._base_path).enrich_buildings()
self._city.level_of_detail.construction = 2
def enrich(self): def enrich(self):
""" """
@ -31,3 +32,10 @@ class ConstructionFactory:
:return: None :return: None
""" """
getattr(self, self._handler, lambda: 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()

View File

@ -82,7 +82,7 @@ class AirSourceHeatPumpParameters:
heat_pump.heating_comp_power = h_data[1] heat_pump.heating_comp_power = h_data[1]
heat_pump.heating_capacity_coff = self._compute_coefficients(h_data) 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 energy_system.air_source_hp = heat_pump
self._city.add_city_object(energy_system) self._city.add_city_object(energy_system)
return self._city return self._city

View File

@ -129,7 +129,7 @@ class WaterToWaterHPParameters:
heat_pump.entering_water_temp = data['ewt'] heat_pump.entering_water_temp = data['ewt']
heat_pump.leaving_water_temp = data['lwt'] heat_pump.leaving_water_temp = data['lwt']
heat_pump.power_demand_coff = self._compute_coefficients(data) 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 energy_system.water_to_water_hp = heat_pump
self._city.add_city_object(energy_system) self._city.add_city_object(energy_system)
return self._city return self._city

View File

@ -21,10 +21,14 @@ class CityGml:
""" """
CityGml class 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._city = None
self._lod = None
self._lod1_tags = ['lod1Solid', 'lod1MultiSurface'] self._lod1_tags = ['lod1Solid', 'lod1MultiSurface']
self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve'] self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve']
self._extrusion_height_field = extrusion_height_field
self._year_of_construction_field = year_of_construction_field
self._function_field = function_field
self._lower_corner = None self._lower_corner = None
self._upper_corner = None self._upper_corner = None
with open(path) as gml: with open(path) as gml:
@ -64,6 +68,9 @@ class CityGml:
self._upper_corner = np.fromstring(envelope['upperCorner'], dtype=float, sep=' ') self._upper_corner = np.fromstring(envelope['upperCorner'], dtype=float, sep=' ')
if '@srsName' in envelope: if '@srsName' in envelope:
self._srs_name = envelope['@srsName'] self._srs_name = envelope['@srsName']
else:
# If not coordinate system given assuming hub standard
self._srs_name = "EPSG:26911"
else: else:
# get the boundary from the city objects instead # get the boundary from the city objects instead
for city_object_member in self._gml['CityModel']['cityObjectMember']: for city_object_member in self._gml['CityModel']['cityObjectMember']:
@ -74,8 +81,6 @@ class CityGml:
continue continue
envelope = bound['Envelope'] envelope = bound['Envelope']
self._srs_name = envelope['@srsName'] self._srs_name = envelope['@srsName']
lower_corner = None
upper_corner = None
if '#text' in envelope['lowerCorner']: if '#text' in envelope['lowerCorner']:
lower_corner = np.fromstring(envelope['lowerCorner']['#text'], dtype=float, sep=' ') lower_corner = np.fromstring(envelope['lowerCorner']['#text'], dtype=float, sep=' ')
upper_corner = np.fromstring(envelope['upperCorner']['#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: if 'function' in city_object:
function = city_object['function'] function = city_object['function']
if any(key in city_object for key in self._lod1_tags): 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 surfaces = CityGmlLod1(city_object).surfaces
elif any(key in city_object for key in self._lod2_tags): 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 surfaces = CityGmlLod2(city_object).surfaces
else: else:
raise NotImplementedError("Not supported level of detail") 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): def _create_parts_consisting_building(self, city_object):
name = city_object['@id'] name = city_object['@id']
@ -143,4 +150,5 @@ class CityGml:
self._city.add_city_objects_cluster(self._create_parts_consisting_building(city_object)) self._city.add_city_objects_cluster(self._create_parts_consisting_building(city_object))
else: else:
self._city.add_city_object(self._create_building(city_object)) self._city.add_city_object(self._create_building(city_object))
self._city.level_of_detail.geometry = self._lod
return self._city return self._city

View File

@ -24,18 +24,6 @@ class CityGmlBase(ABC):
""" """
return self._surfaces 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 @classmethod
def _solid(cls, city_object_member): def _solid(cls, city_object_member):
raise NotImplementedError raise NotImplementedError

View File

@ -6,6 +6,7 @@ Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca 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 imports.geometry.citygml_classes.citygml_base import CityGmlBase
from city_model_structure.building_demand.surface import Surface from city_model_structure.building_demand.surface import Surface
from city_model_structure.attributes.polygon import Polygon from city_model_structure.attributes.polygon import Polygon
@ -37,19 +38,20 @@ class CityGmlLod1(CityGmlBase):
def _solid(cls, city_object_member): def _solid(cls, city_object_member):
try: try:
solid_points = [ solid_points = [
CityGmlBase._solid_points(CityGmlBase._remove_last_point(s['Polygon']['exterior']['LinearRing']['posList'] GeometryHelper.points_from_string(GeometryHelper.remove_last_point_from_string(
['#text'])) s['Polygon']['exterior']['LinearRing']['posList']['#text']))
for s in city_object_member['lod1Solid']['Solid']['exterior']['CompositeSurface']['surfaceMember']] for s in city_object_member['lod1Solid']['Solid']['exterior']['CompositeSurface']['surfaceMember']]
except TypeError: except TypeError:
solid_points = [ 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']] for s in city_object_member['lod1Solid']['Solid']['exterior']['CompositeSurface']['surfaceMember']]
return [Surface(Polygon(sp), Polygon(sp)) for sp in solid_points] return [Surface(Polygon(sp), Polygon(sp)) for sp in solid_points]
@classmethod @classmethod
def _multi_surface(cls, city_object_member): def _multi_surface(cls, city_object_member):
solid_points = [CityGmlBase._solid_points(CityGmlBase._remove_last_point(s['Polygon']['exterior']['LinearRing'] solid_points = [GeometryHelper.points_from_string(GeometryHelper.remove_last_point_from_string(
['posList'])) s['Polygon']['exterior']['LinearRing']['posList']))
for s in city_object_member['Building']['lod1MultiSurface']['MultiSurface']['surfaceMember']] for s in city_object_member['Building']['lod1MultiSurface']['MultiSurface']['surfaceMember']]
return [Surface(Polygon(sp), Polygon(sp)) for sp in solid_points] return [Surface(Polygon(sp), Polygon(sp)) for sp in solid_points]

View File

@ -60,11 +60,19 @@ class CityGmlLod2(CityGmlBase):
@classmethod @classmethod
def _add_member_surface(cls, member, surface_type): 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"] gml_points = member['Polygon']['exterior']['LinearRing']['posList']["#text"]
else: else:
gml_points = member['Polygon']['exterior']['LinearRing']['posList'] gml_points = member['Polygon']['exterior']['LinearRing'][pos_name]
solid_points = cls._solid_points(cls._remove_last_point(gml_points)) 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) polygon = Polygon(solid_points)
return Surface(polygon, polygon, surface_type=GeometryHelper.gml_surface_to_libs(surface_type)) return Surface(polygon, polygon, surface_type=GeometryHelper.gml_surface_to_libs(surface_type))

149
imports/geometry/geojson.py Normal file
View 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
View 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

View File

@ -6,6 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
import helpers.constants as cte import helpers.constants as cte
import numpy as np
class GeometryHelper: class GeometryHelper:
@ -303,3 +304,15 @@ class GeometryHelper:
if surface == 'GroundSurface': if surface == 'GroundSurface':
return 'Ground' return 'Ground'
return 'Roof' 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])

View File

@ -77,6 +77,7 @@ class Obj:
perimeter_polygon = solid_polygon perimeter_polygon = solid_polygon
surface = Surface(solid_polygon, perimeter_polygon) surface = Surface(solid_polygon, perimeter_polygon)
surfaces.append(surface) 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.add_city_object(building)
self._city.level_of_detail.geometry = lod
return self._city return self._city

View File

@ -101,7 +101,7 @@ class Rhino:
if face is None: if face is None:
break break
hub_surfaces = hub_surfaces + self._add_face(face) 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) city_objects.append(building)
lower_corner = (self._min_x, self._min_y, self._min_z) lower_corner = (self._min_x, self._min_y, self._min_z)
upper_corner = (self._max_x, self._max_y, self._max_z) upper_corner = (self._max_x, self._max_y, self._max_z)
@ -133,4 +133,5 @@ class Rhino:
for building in buildings: for building in buildings:
city.add_city_object(building) city.add_city_object(building)
city.level_of_detail.geometry = 3
return city return city

View File

@ -5,20 +5,32 @@ Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
import geopandas
from city_model_structure.city import City from city_model_structure.city import City
from imports.geometry.citygml import CityGml from imports.geometry.citygml import CityGml
from imports.geometry.obj import Obj from imports.geometry.obj import Obj
from imports.geometry.osm_subway import OsmSubway from imports.geometry.osm_subway import OsmSubway
from imports.geometry.rhino import Rhino from imports.geometry.rhino import Rhino
from imports.geometry.gpandas import GPandas
from imports.geometry.geojson import Geojson
class GeometryFactory: class GeometryFactory:
""" """
GeometryFactory class 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._file_type = '_' + file_type.lower()
self._path = path 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 @property
def _citygml(self) -> City: def _citygml(self) -> City:
@ -26,7 +38,7 @@ class GeometryFactory:
Enrich the city by using CityGML information as data source Enrich the city by using CityGML information as data source
:return: City :return: City
""" """
return CityGml(self._path).city return CityGml(self._path, self._height_field, self._year_of_construction_field, self._function_field).city
@property @property
def _obj(self) -> City: def _obj(self) -> City:
@ -35,6 +47,24 @@ class GeometryFactory:
:return: City :return: City
""" """
return Obj(self._path).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 @property
def _osm_subway(self) -> City: def _osm_subway(self) -> City:
@ -66,4 +96,4 @@ class GeometryFactory:
Enrich the city given to the class using the class given handler Enrich the city given to the class using the class given handler
:return: City :return: City
""" """
return CityGml(self._path).city return Geojson(self._path, self._height_field, self._year_of_construction_field, self._function_field).city

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import helpers.constants as cte
from helpers.configuration_helper import ConfigurationHelper as ch from helpers.configuration_helper import ConfigurationHelper as ch
from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.geometry.helpers.geometry_helper import GeometryHelper
from imports.usage.helpers.usage_helper import UsageHelper 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.usage_zone import UsageZone
from city_model_structure.building_demand.lighting import Lighting from city_model_structure.building_demand.lighting import Lighting
from city_model_structure.building_demand.occupancy import Occupancy from city_model_structure.building_demand.occupancy import Occupancy
@ -42,8 +42,8 @@ class ComnetUsageParameters:
""" """
number_usage_types = 33 number_usage_types = 33
xl_file = pd.ExcelFile(self._base_path) xl_file = pd.ExcelFile(self._base_path)
file_data = pd.read_excel(xl_file, sheet_name="Modeling Data", skiprows=[0, 1, 2], file_data = pd.read_excel(xl_file, sheet_name="Modeling Data", usecols="A:AB", skiprows=[0, 1, 2],
nrows=number_usage_types, usecols="A:AB") nrows=number_usage_types)
lighting_data = {} lighting_data = {}
plug_loads_data = {} plug_loads_data = {}
@ -109,8 +109,8 @@ class ComnetUsageParameters:
schedules_usage = UsageHelper.schedules_key(data['schedules_key'][comnet_usage][0]) schedules_usage = UsageHelper.schedules_key(data['schedules_key'][comnet_usage][0])
_extracted_data = pd.read_excel(schedules_data, sheet_name=schedules_usage, _extracted_data = pd.read_excel(schedules_data, sheet_name=schedules_usage, usecols="A:AA", skiprows=[0, 1, 2, 3],
skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA") nrows=39)
schedules = [] schedules = []
number_of_schedule_types = 13 number_of_schedule_types = 13
schedules_per_schedule_type = 3 schedules_per_schedule_type = 3

View File

@ -26,12 +26,14 @@ class UsageFactory:
""" """
Enrich the city with HFT usage library Enrich the city with HFT usage library
""" """
self._city.level_of_detail.usage = 2
return HftUsageParameters(self._city, self._base_path).enrich_buildings() return HftUsageParameters(self._city, self._base_path).enrich_buildings()
def _comnet(self): def _comnet(self):
""" """
Enrich the city with COMNET usage library Enrich the city with COMNET usage library
""" """
self._city.level_of_detail.usage = 2
return ComnetUsageParameters(self._city, self._base_path).enrich_buildings() return ComnetUsageParameters(self._city, self._base_path).enrich_buildings()
def enrich(self): def enrich(self):

View File

@ -19,3 +19,6 @@ pyecore==0.12.2
python-dotenv python-dotenv
SQLAlchemy SQLAlchemy
bcrypt==4.0.1 bcrypt==4.0.1
shapely
geopandas
triangle

View File

@ -23,7 +23,7 @@ class TestCityMerge(TestCase):
def _get_citygml(self, file): def _get_citygml(self, file):
file_path = (self._example_path / file).resolve() 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') self.assertIsNotNone(city, 'city is none')
return city return city

View File

@ -26,114 +26,20 @@ class TestConstructionFactory(TestCase):
def _get_citygml(self, file): def _get_citygml(self, file):
file_path = (self._example_path / file).resolve() 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, 'city is none')
self.assertIsNotNone(self._city.level_of_detail.geometry, 'wrong construction level of detail')
return self._city return self._city
def _check_buildings(self, city): @staticmethod
for building in city.buildings: def _internal_function(function_format, original_function):
self.assertIsNotNone(building.name, 'building name is none') if function_format == 'hft':
self.assertIsNotNone(building.lod, 'building lod is none') new_function = GeometryHelper.libs_function_from_hft(original_function)
self.assertIsNotNone(building.type, 'building type is none') elif function_format == 'pluto':
self.assertIsNotNone(building.volume, 'building volume is none') new_function = GeometryHelper.libs_function_from_pluto(original_function)
self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none') else:
self.assertIsNotNone(building.simplified_polyhedron, 'building simplified polyhedron is none') raise Exception('Function key not recognized. Implemented only "hft" and "pluto"')
self.assertIsNotNone(building.surfaces, 'building surfaces is none') return new_function
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')
def test_citygml_function(self): def test_citygml_function(self):
""" """
@ -162,6 +68,110 @@ class TestConstructionFactory(TestCase):
for building in city.buildings: for building in city.buildings:
self.assertRaises(Exception, lambda: self._internal_function(function_format, building.function)) 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): def test_city_with_construction_extended_library(self):
""" """
Enrich the city with the construction information and verify it 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: for thermal_zone in internal_zone.thermal_zones:
self._check_thermal_boundaries(thermal_zone) self._check_thermal_boundaries(thermal_zone)
for thermal_boundary in thermal_zone.thermal_boundaries: 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.assertIsNotNone(thermal_boundary.layers, 'layers is none')
self._check_thermal_openings(thermal_boundary) 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

View File

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

View File

@ -40,7 +40,7 @@ class TestEnergySystemsFactory(TestCase):
""" """
city_file = "../unittests/tests_data/C40_Final.gml" city_file = "../unittests/tests_data/C40_Final.gml"
self._output_path = "../unittests/tests_data/as_user_output.csv" 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() EnergySystemsFactory('air source hp', self._city).enrich()
def test_air_source_heat_pump_import(self): def test_air_source_heat_pump_import(self):

View File

@ -41,7 +41,7 @@ class TestEnergySystemsFactory(TestCase):
""" """
city_file = "../unittests/tests_data/C40_Final.gml" city_file = "../unittests/tests_data/C40_Final.gml"
self._output_path = "../unittests/tests_data/w2w_user_output.csv" 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() EnergySystemsFactory('water to water hp', self._city).enrich()
def test_water_to_water_heat_pump_import(self): 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[0].water_to_water_hp.model, 'ClimateMaster 156 kW')
self.assertEqual(self._city.energy_systems[2].water_to_water_hp.model, 'ClimateMaster 335 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): def test_water_to_water_heat_pump_export(self):
# User defined paramenters
EnergySystemsExportFactory(city=self._city, user_input=user_input, hp_model='ClimateMaster 156 kW', user_input = {
output_path=self._output_path).export('water') 'StartYear': 2020,
df = pd.read_csv(self._output_path) 'EndYear': 2021,
self.assertEqual(df.shape, (13, 3)) 'MaximumHPEnergyInput': 8000,
self.assertEqual(df.iloc[0, 1], 1162387.5) 'HoursOfStorageAtMaxDemand': 1,
'BuildingSuppTemp': 40,
def test_water_to_water_parallel_heat_pump_export(self): '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', EnergySystemsExportFactory(city=self._city, user_input=user_input, hp_model='ClimateMaster 256 kW',
output_path=self._output_path, sim_type=1).export('water') output_path=self._output_path, sim_type=1).export('water')

View File

@ -27,7 +27,7 @@ class TestGeometryFactory(TestCase):
def _get_citygml(self, file): def _get_citygml(self, file):
file_path = (self._example_path / file).resolve() 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, 'city is none')
return self._city return self._city
@ -47,8 +47,8 @@ class TestGeometryFactory(TestCase):
for internal_zone in building.internal_zones: for internal_zone in building.internal_zones:
self.assertIsNotNone(internal_zone.usage_zones, 'usage zones are not defined') self.assertIsNotNone(internal_zone.usage_zones, 'usage zones are not defined')
self.assertIsNotNone(internal_zone.thermal_zones, 'thermal 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.assertIsNone(building.basement_heated, 'building basement_heated is not none')
self.assertIsNotNone(building.attic_heated, 'building attic_heated is 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.average_storey_height, 'building average_storey_height is none')
self.assertIsNotNone(building.storeys_above_ground, 'building storeys_above_ground 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') self.assertTrue(building.is_conditioned, 'building is_conditioned is not conditioned')
@ -91,7 +91,7 @@ class TestGeometryFactory(TestCase):
def _test_hft(self, file): def _test_hft(self, file):
_construction_keys = ['nrel'] _construction_keys = ['nrel']
_usage_keys = ['ca', 'comnet', 'hft'] _usage_keys = ['comnet', 'hft']
for construction_key in _construction_keys: for construction_key in _construction_keys:
for usage_key in _usage_keys: for usage_key in _usage_keys:
# construction factory called first # construction factory called first

View File

@ -14,6 +14,7 @@ from imports.geometry.helpers.geometry_helper import GeometryHelper
from imports.construction_factory import ConstructionFactory from imports.construction_factory import ConstructionFactory
from imports.usage_factory import UsageFactory from imports.usage_factory import UsageFactory
from exports.exports_factory import ExportsFactory from exports.exports_factory import ExportsFactory
from exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
import helpers.constants as cte import helpers.constants as cte
from city_model_structure.city import City from city_model_structure.city import City
@ -34,7 +35,7 @@ class TestExports(TestCase):
def _get_citygml(self, file): def _get_citygml(self, file):
file_path = (self._example_path / file).resolve() 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, 'city is none')
return self._city return self._city
@ -51,7 +52,7 @@ class TestExports(TestCase):
building.year_of_construction = 2006 building.year_of_construction = 2006
ConstructionFactory('nrel', self._complete_city).enrich() ConstructionFactory('nrel', self._complete_city).enrich()
UsageFactory('ca', 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_file = Path(cli)
self._complete_city.climate_reference_city = 'Summerland' 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] 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 export to SRA
""" """
self._export('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!")

View File

@ -9,8 +9,9 @@ from unittest import TestCase
from numpy import inf from numpy import inf
from imports.geometry_factory import GeometryFactory import exports.exports_factory
from imports.construction_factory import ConstructionFactory from imports.construction_factory import ConstructionFactory
from imports.geometry_factory import GeometryFactory
class TestGeometryFactory(TestCase): class TestGeometryFactory(TestCase):
@ -25,36 +26,26 @@ class TestGeometryFactory(TestCase):
""" """
self._city = None self._city = None
self._example_path = (Path(__file__).parent / 'tests_data').resolve() 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() file_path = (self._example_path / file).resolve()
self._city = GeometryFactory('citygml', file_path).city self._city = GeometryFactory(file_type,
self.assertIsNotNone(self._city, 'city is none') path=file_path,
return self._city height_field=height_field,
year_of_construction_field=year_of_construction_field,
def _get_obj(self, file): function_field=function_field).city
# 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.assertIsNotNone(self._city, 'city is none') self.assertIsNotNone(self._city, 'city is none')
return self._city return self._city
def _check_buildings(self, city): def _check_buildings(self, city):
for building in city.buildings: for building in city.buildings:
self.assertIsNotNone(building.name, 'building name is none') 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.type, 'building type is none')
self.assertIsNotNone(building.volume, 'building volume is none') self.assertIsNotNone(building.volume, 'building volume is none')
self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron 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.simplified_polyhedron, 'building simplified polyhedron is none')
self.assertIsNotNone(building.surfaces, 'building surfaces 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.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.external_temperature), 0, 'building external temperature is calculated')
self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal 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.basement_heated, 'building basement_heated is not none')
self.assertIsNone(building.attic_heated, 'building attic_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.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.average_storey_height, 'building average_storey_height is not none')
self.assertIsNone(building.storeys_above_ground, 'building storeys_above_ground 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') self.assertEqual(len(building.heating), 0, 'building heating is not none')
@ -89,18 +78,21 @@ class TestGeometryFactory(TestCase):
for surface in building.surfaces: for surface in building.surfaces:
self.assertIsNotNone(surface.name, 'surface name is none') self.assertIsNotNone(surface.name, 'surface name is none')
self.assertIsNotNone(surface.id, 'surface id 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.lower_corner, 'surface envelope_lower_corner is none')
self.assertIsNotNone(surface.upper_corner, 'surface envelope_upper_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.perimeter_area, 'surface area_above_ground is none')
self.assertIsNotNone(surface.area_above_ground, 'surface area_above_ground is none')
self.assertIsNotNone(surface.azimuth, 'surface azimuth 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.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.assertEqual(len(surface.global_irradiance), 0, 'global irradiance is calculated')
self.assertIsNotNone(surface.perimeter_polygon, 'surface perimeter_polygon is none') self.assertIsNotNone(surface.perimeter_polygon, 'surface perimeter_polygon is none')
self.assertIsNone(surface.holes_polygons, 'surface hole_polygons is not none') self.assertIsNone(surface.holes_polygons, 'surface hole_polygons is not none')
self.assertIsNotNone(surface.solid_polygon, 'surface solid_polygon is 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 # citygml_classes
def test_import_citygml(self): def test_import_citygml(self):
@ -108,8 +100,8 @@ class TestGeometryFactory(TestCase):
Test city objects in the city Test city objects in the city
:return: None :return: None
""" """
file = 'one_building_in_kelowna.gml' file = 'FZK_Haus_LoD_2.gml'
city = self._get_citygml(file) city = self._get_city(file, 'citygml', year_of_construction_field='yearOfConstruction')
self.assertTrue(len(city.buildings) == 1) self.assertTrue(len(city.buildings) == 1)
self._check_buildings(city) self._check_buildings(city)
for building in city.buildings: for building in city.buildings:
@ -117,13 +109,12 @@ class TestGeometryFactory(TestCase):
building.year_of_construction = 2006 building.year_of_construction = 2006
city = ConstructionFactory('nrel', city).enrich() city = ConstructionFactory('nrel', city).enrich()
# rhino
def test_import_rhino(self): def test_import_rhino(self):
""" """
Test rhino import Test rhino import
""" """
file = 'dompark.3dm' file = 'dompark.3dm'
city = self._get_rhino(file) city = self._get_city(file, 'rhino')
self.assertIsNotNone(city, 'city is none') self.assertIsNotNone(city, 'city is none')
self.assertTrue(len(city.buildings) == 36) self.assertTrue(len(city.buildings) == 36)
i = 0 i = 0
@ -131,28 +122,40 @@ class TestGeometryFactory(TestCase):
self.assertIsNot(building.volume, inf, 'open volume') self.assertIsNot(building.volume, inf, 'open volume')
i += 1 i += 1
# obj
def test_import_obj(self): def test_import_obj(self):
""" """
Test obj import Test obj import
""" """
file = 'kelowna.obj' file = 'kelowna.obj'
city = self._get_obj(file) city = self._get_city(file, 'obj')
self.assertIsNotNone(city, 'city is none')
self.assertTrue(len(city.buildings) == 1) self.assertTrue(len(city.buildings) == 1)
self._check_buildings(city) self._check_buildings(city)
for building in city.buildings: for building in city.buildings:
self._check_surfaces(building) self._check_surfaces(building)
# osm def test_import_geopandas(self):
def test_subway(self):
""" """
Test subway parsing Test geopandas import
:return:
""" """
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') exports.exports_factory.ExportsFactory('obj', city, self._output_path).export_debug()
self.assertEqual(len(city.city_objects), 20, 'Wrong number of subway entrances') self.assertEqual(207, len(city.buildings), 'wrong number of buildings')
self._check_buildings(city)

View File

@ -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 SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca

View File

@ -11,7 +11,7 @@ from unittest import TestCase
from imports.geometry_factory import GeometryFactory from imports.geometry_factory import GeometryFactory
from imports.usage_factory import UsageFactory from imports.usage_factory import UsageFactory
from imports.construction_factory import ConstructionFactory 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.vegetation import Vegetation
from city_model_structure.greenery.soil import Soil from city_model_structure.greenery.soil import Soil
from city_model_structure.greenery.plant import Plant 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" city_file = "../unittests/tests_data/one_building_in_kelowna.gml"
output_path = Path('../unittests/tests_outputs/').resolve() 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: for building in city.buildings:
building.year_of_construction = 2006 building.year_of_construction = 2006
ConstructionFactory('nrel', city).enrich() ConstructionFactory('nrel', city).enrich()
@ -62,13 +62,11 @@ class GreeneryInIdf(TestCase):
plants = [plant] plants = [plant]
vegetation = Vegetation(vegetation_name, soil, soil_thickness, plants) vegetation = Vegetation(vegetation_name, soil, soil_thickness, plants)
for building in city.buildings: for building in city.buildings:
for internal_zone in building.internal_zones: for surface in building.surfaces:
for thermal_zone in internal_zone.thermal_zones: if surface.type == cte.ROOF:
for thermal_boundary in thermal_zone.thermal_boundaries: surface.vegetation = vegetation
if thermal_boundary.type == cte.ROOF:
thermal_boundary.vegetation = vegetation
_idf_2 = ExportsFactory('idf', city, output_path).export_debug() _idf_2 = EnergyBuildingsExportsFactory('idf', city, output_path).export_debug()
_idf_2.run() _idf_2.run()
with open((output_path / f'{city.name}_out.csv').resolve()) as f: with open((output_path / f'{city.name}_out.csv').resolve()) as f:
reader = csv.reader(f, delimiter=',') reader = csv.reader(f, delimiter=',')
@ -82,12 +80,12 @@ class GreeneryInIdf(TestCase):
print('With greenery') print('With greenery')
print(f'heating: {heating} MWh/yr, cooling: {cooling} MWh/yr') 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: for building in city.buildings:
building.year_of_construction = 2006 building.year_of_construction = 2006
ConstructionFactory('nrel', city).enrich() ConstructionFactory('nrel', city).enrich()
UsageFactory('comnet', city).enrich() UsageFactory('comnet', city).enrich()
_idf = ExportsFactory('idf', city, output_path).export() _idf = EnergyBuildingsExportsFactory('idf', city, output_path).export()
_idf.run() _idf.run()
with open((output_path / f'{city.name}_out.csv').resolve()) as f: with open((output_path / f'{city.name}_out.csv').resolve()) as f:
reader = csv.reader(f, delimiter=',') reader = csv.reader(f, delimiter=',')

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

View File

@ -24,28 +24,28 @@ class TestLifeCycleAssessment(TestCase):
def test_fuel(self): def test_fuel(self):
city_file = "../unittests/tests_data/C40_Final.gml" 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() LifeCycleAssessment('fuel', city).enrich()
for fuel in city.fuels: for fuel in city.fuels:
self.assertTrue(len(city.fuels) > 0) self.assertTrue(len(city.fuels) > 0)
def test_vehicle(self): def test_vehicle(self):
city_file = "../unittests/tests_data/C40_Final.gml" 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() LifeCycleAssessment('vehicle', city).enrich()
for vehicle in city.vehicles: for vehicle in city.vehicles:
self.assertTrue(len(city.vehicles) > 0) self.assertTrue(len(city.vehicles) > 0)
def test_machine(self): def test_machine(self):
city_file = "../unittests/tests_data/C40_Final.gml" 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() LifeCycleAssessment('machine', city).enrich()
for machine in city.machines: for machine in city.machines:
self.assertTrue(len(city.machines) > 0) self.assertTrue(len(city.machines) > 0)
def test_material(self): def test_material(self):
city_file = "../unittests/tests_data/C40_Final.gml" 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() LifeCycleAssessment('material', city).enrich()
for material in city.lca_materials: for material in city.lca_materials:
self.assertTrue(len(city.lca_materials) > 0) self.assertTrue(len(city.lca_materials) > 0)

View File

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

View File

@ -14,4 +14,10 @@ class TestConstructionCatalog(TestCase):
catalog = UsageCatalogFactory('comnet').catalog catalog = UsageCatalogFactory('comnet').catalog
self.assertIsNotNone(catalog, 'catalog is none') self.assertIsNotNone(catalog, 'catalog is none')
content = catalog.entries() 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')

View File

@ -26,14 +26,13 @@ class TestUsageFactory(TestCase):
def _get_citygml(self, file): def _get_citygml(self, file):
file_path = (self._example_path / file).resolve() 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, 'city is none')
return self._city return self._city
def _check_buildings(self, city): def _check_buildings(self, city):
for building in city.buildings: for building in city.buildings:
self.assertIsNotNone(building.name, 'building name is none') 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.type, 'building type is none')
self.assertIsNotNone(building.volume, 'building volume is none') self.assertIsNotNone(building.volume, 'building volume is none')
self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none') self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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
}
}
]
}