First working version of dynamic building simulation
This commit is contained in:
parent
b5e29681c4
commit
a78cb879a0
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
!.gitignore
|
||||
/venv/
|
||||
.idea/
|
||||
/development_tests/
|
|
@ -5,19 +5,14 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc
|
|||
contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||
"""
|
||||
|
||||
|
||||
import sys
|
||||
from typing import List
|
||||
import math
|
||||
import numpy as np
|
||||
from city_model_structure.building_demand.surface import Surface
|
||||
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
||||
from city_model_structure.building_demand.thermal_boundary import ThermalBoundary
|
||||
from city_model_structure.building_demand.usage_zone import UsageZone
|
||||
from city_model_structure.building_demand.storey import Storey
|
||||
from city_model_structure.attributes.polygon import Polygon
|
||||
from city_model_structure.attributes.point import Point
|
||||
from city_model_structure.city_object import CityObject
|
||||
from helpers import constants as cte
|
||||
|
||||
|
||||
class Building(CityObject):
|
||||
|
@ -25,7 +20,7 @@ class Building(CityObject):
|
|||
Building(CityObject) class
|
||||
"""
|
||||
def __init__(self, name, lod, surfaces, year_of_construction, function,
|
||||
city_lower_corner, terrains=None, divide_in_storeys=False):
|
||||
city_lower_corner, terrains=None):
|
||||
super().__init__(name, lod, surfaces, city_lower_corner)
|
||||
self._basement_heated = None
|
||||
self._attic_heated = None
|
||||
|
@ -36,13 +31,14 @@ class Building(CityObject):
|
|||
self._storeys_above_ground = None
|
||||
self._floor_area = None
|
||||
self._roof_type = None
|
||||
self._storeys = None
|
||||
self._thermal_zones = []
|
||||
self._thermal_boundaries = None
|
||||
self._usage_zones = []
|
||||
self._type = 'building'
|
||||
self._heating = dict()
|
||||
self._cooling = dict()
|
||||
self._eave_height = None
|
||||
self._divide_in_storeys = divide_in_storeys
|
||||
self._grounds = []
|
||||
self._roofs = []
|
||||
self._walls = []
|
||||
|
@ -298,86 +294,18 @@ class Building(CityObject):
|
|||
@property
|
||||
def storeys(self) -> [Storey]:
|
||||
"""
|
||||
subsections of building trimesh by storey in case of no interiors defined
|
||||
Storeys inside the building
|
||||
:return: [Storey]
|
||||
"""
|
||||
number_of_storeys, height = self._calculate_number_storeys_and_height(self.average_storey_height, self.eave_height,
|
||||
self.storeys_above_ground)
|
||||
number_of_storeys = 1
|
||||
if not self._divide_in_storeys or number_of_storeys == 1:
|
||||
return [Storey('storey_0', self.surfaces, [None, None], self.volume)]
|
||||
return self._storeys
|
||||
|
||||
if number_of_storeys == 0:
|
||||
raise Exception('Number of storeys cannot be 0')
|
||||
|
||||
storeys = []
|
||||
surfaces_child_last_storey = []
|
||||
rest_surfaces = []
|
||||
|
||||
total_volume = 0
|
||||
for i in range(0, number_of_storeys-1):
|
||||
name = 'storey_' + str(i)
|
||||
surfaces_child = []
|
||||
if i == 0:
|
||||
neighbours = [None, 'storey_1']
|
||||
for surface in self.surfaces:
|
||||
if surface.type == cte.GROUND:
|
||||
surfaces_child.append(surface)
|
||||
else:
|
||||
rest_surfaces.append(surface)
|
||||
else:
|
||||
neighbours = ['storey_' + str(i-1), 'storey_' + str(i+1)]
|
||||
height_division = self.lower_corner[2] + height*(i+1)
|
||||
intersections = []
|
||||
for surface in rest_surfaces:
|
||||
if surface.type == cte.ROOF:
|
||||
if height_division >= surface.upper_corner[2] > height_division-height:
|
||||
surfaces_child.append(surface)
|
||||
else:
|
||||
surfaces_child_last_storey.append(surface)
|
||||
else:
|
||||
surface_child, rest_surface, intersection = surface.divide(height_division)
|
||||
surfaces_child.append(surface_child)
|
||||
intersections.extend(intersection)
|
||||
if i == number_of_storeys-2:
|
||||
surfaces_child_last_storey.append(rest_surface)
|
||||
points = []
|
||||
for intersection in intersections:
|
||||
points.append(intersection[1])
|
||||
coordinates = self._intersections_to_coordinates(intersections)
|
||||
polygon = Polygon(coordinates)
|
||||
ceiling = Surface(polygon, polygon, surface_type=cte.INTERIOR_SLAB)
|
||||
surfaces_child.append(ceiling)
|
||||
volume = ceiling.area_above_ground * height
|
||||
total_volume += volume
|
||||
storeys.append(Storey(name, surfaces_child, neighbours, volume))
|
||||
name = 'storey_' + str(number_of_storeys-1)
|
||||
neighbours = ['storey_' + str(number_of_storeys-2), None]
|
||||
volume = self.volume - total_volume
|
||||
if volume < 0:
|
||||
raise Exception('Error in storeys creation, volume of last storey cannot be lower that 0')
|
||||
storeys.append(Storey(name, surfaces_child_last_storey, neighbours, volume))
|
||||
return storeys
|
||||
|
||||
@staticmethod
|
||||
def _calculate_number_storeys_and_height(average_storey_height, eave_height, storeys_above_ground):
|
||||
if average_storey_height is None:
|
||||
if storeys_above_ground is None or storeys_above_ground <= 0:
|
||||
sys.stderr.write('Warning: not enough information to divide building into storeys, '
|
||||
'either number of storeys or average storey height must be provided.\n')
|
||||
return 0, 0
|
||||
number_of_storeys = int(storeys_above_ground)
|
||||
height = eave_height / number_of_storeys
|
||||
else:
|
||||
height = float(average_storey_height)
|
||||
if storeys_above_ground is not None:
|
||||
number_of_storeys = int(storeys_above_ground)
|
||||
else:
|
||||
number_of_storeys = math.floor(float(eave_height) / height) + 1
|
||||
last_storey_height = float(eave_height) - height*(number_of_storeys-1)
|
||||
if last_storey_height < 0.3*height:
|
||||
number_of_storeys -= 1
|
||||
return number_of_storeys, height
|
||||
@storeys.setter
|
||||
def storeys(self, value):
|
||||
"""
|
||||
Storeys inside the building
|
||||
:param value: [Storey]
|
||||
"""
|
||||
self._storeys = value
|
||||
|
||||
@property
|
||||
def roof_type(self):
|
||||
|
@ -406,31 +334,20 @@ class Building(CityObject):
|
|||
self._floor_area += surface.perimeter_polygon.area
|
||||
return self._floor_area
|
||||
|
||||
@staticmethod
|
||||
def _intersections_to_coordinates(edges_list):
|
||||
# todo: this method is too complex, the while loop needs to be improved
|
||||
points = [Point(edges_list[0][0]), Point(edges_list[0][1])]
|
||||
found_edges = []
|
||||
j = 0
|
||||
while j < len(points)-1:
|
||||
for i in range(1, len(edges_list)):
|
||||
if i not in found_edges:
|
||||
point_2 = points[len(points) - 1]
|
||||
point_1 = Point(edges_list[i][0])
|
||||
found = False
|
||||
if point_1.distance_to_point(point_2) <= 1e-10:
|
||||
points.append(Point(edges_list[i][1]))
|
||||
found_edges.append(i)
|
||||
found = True
|
||||
if not found:
|
||||
point_1 = Point(edges_list[i][1])
|
||||
if point_1.distance_to_point(point_2) <= 1e-10:
|
||||
points.append(Point(edges_list[i][0]))
|
||||
found_edges.append(i)
|
||||
j += 1
|
||||
|
||||
points.remove(points[len(points)-1])
|
||||
array_points = []
|
||||
for point in points:
|
||||
array_points.append(point.coordinates)
|
||||
return np.array(array_points)
|
||||
@property
|
||||
def thermal_boundaries(self) -> List[ThermalBoundary]:
|
||||
"""
|
||||
List of all thermal boundaries associated to the building's thermal zones
|
||||
:return: [ThermalBoundary]
|
||||
"""
|
||||
if self._thermal_boundaries is None:
|
||||
self._thermal_boundaries = []
|
||||
for thermal_zone in self.thermal_zones:
|
||||
_thermal_boundary_duplicated = False
|
||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
||||
if len(thermal_boundary.thermal_zones) > 1:
|
||||
if thermal_zone != thermal_boundary.thermal_zones[1]:
|
||||
self._thermal_boundaries.append(thermal_boundary)
|
||||
else:
|
||||
self._thermal_boundaries.append(thermal_boundary)
|
||||
return self._thermal_boundaries
|
||||
|
|
|
@ -6,11 +6,9 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons
|
|||
|
||||
from __future__ import annotations
|
||||
from typing import List
|
||||
import numpy as np
|
||||
from city_model_structure.building_demand.surface import Surface
|
||||
from city_model_structure.building_demand.thermal_boundary import ThermalBoundary
|
||||
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
||||
import helpers.constants as cte
|
||||
|
||||
|
||||
class Storey:
|
||||
|
@ -18,10 +16,10 @@ class Storey:
|
|||
"""
|
||||
Storey class
|
||||
"""
|
||||
def __init__(self, name, surfaces, neighbours, volume):
|
||||
def __init__(self, name, storey_surfaces, neighbours, volume):
|
||||
# todo: the information of the parent surface is lost -> need to recover it
|
||||
self._name = name
|
||||
self._surfaces = surfaces
|
||||
self._storey_surfaces = storey_surfaces
|
||||
self._thermal_boundaries = None
|
||||
self._virtual_surfaces = None
|
||||
self._thermal_zone = None
|
||||
|
@ -42,7 +40,7 @@ class Storey:
|
|||
External surfaces enclosing the storey
|
||||
:return: [Surface]
|
||||
"""
|
||||
return self._surfaces
|
||||
return self._storey_surfaces
|
||||
|
||||
@property
|
||||
def neighbours(self):
|
||||
|
@ -58,21 +56,10 @@ class Storey:
|
|||
Thermal boundaries bounding the thermal zone
|
||||
:return: [ThermalBoundary]
|
||||
"""
|
||||
# todo: it cannot be, it creates a loop between thermal boundaries and thermal zones
|
||||
if self._thermal_boundaries is None:
|
||||
self._thermal_boundaries = []
|
||||
for surface in self.surfaces:
|
||||
if surface.type != cte.INTERIOR_WALL or surface.type != cte.INTERIOR_SLAB:
|
||||
# external thermal boundary -> only one thermal zone
|
||||
delimits = [self.thermal_zone]
|
||||
else:
|
||||
# internal thermal boundary -> two thermal zones
|
||||
grad = np.rad2deg(surface.inclination)
|
||||
if grad >= 170:
|
||||
delimits = [self.thermal_zone, self._neighbours[0]]
|
||||
else:
|
||||
delimits = [self._neighbours[1], self.thermal_zone]
|
||||
self._thermal_boundaries.append(ThermalBoundary(surface, delimits))
|
||||
self._thermal_boundaries.append(ThermalBoundary(surface))
|
||||
return self._thermal_boundaries
|
||||
|
||||
@property
|
||||
|
|
|
@ -38,7 +38,7 @@ class Surface:
|
|||
self._pv_system_installed = None
|
||||
self._inverse = None
|
||||
# todo: do I need it???
|
||||
self._associated_thermal_boundary = None
|
||||
self._associated_thermal_boundaries = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
|
@ -7,18 +7,19 @@ Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|||
from typing import List, TypeVar, Union
|
||||
from city_model_structure.building_demand.layer import Layer
|
||||
from city_model_structure.building_demand.thermal_opening import ThermalOpening
|
||||
ThermalZone = TypeVar('ThermalZone')
|
||||
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
||||
from city_model_structure.building_demand.surface import Surface
|
||||
|
||||
Polygon = TypeVar('Polygon')
|
||||
Surface = TypeVar('Surface')
|
||||
|
||||
|
||||
class ThermalBoundary:
|
||||
"""
|
||||
ThermalBoundary class
|
||||
"""
|
||||
def __init__(self, surface, thermal_zones):
|
||||
def __init__(self, surface):
|
||||
self._surface = surface
|
||||
self._thermal_zones = thermal_zones
|
||||
self._thermal_zones = None
|
||||
# ToDo: up to at least LOD2 will be just one thermal opening per Thermal boundary only if window_ratio > 0,
|
||||
# review for LOD3 and LOD4
|
||||
self._thermal_openings = None
|
||||
|
@ -29,8 +30,8 @@ class ThermalBoundary:
|
|||
self._u_value = None
|
||||
self._shortwave_reflectance = None
|
||||
self._construction_name = None
|
||||
self._hi = None
|
||||
self._he = None
|
||||
self._hi = 3.5
|
||||
self._he = 20
|
||||
self._window_ratio = None
|
||||
self._refurbishment_measure = None
|
||||
self._surface_geometry = None
|
||||
|
@ -57,6 +58,14 @@ class ThermalBoundary:
|
|||
"""
|
||||
return self._thermal_zones
|
||||
|
||||
@thermal_zones.setter
|
||||
def thermal_zones(self, value):
|
||||
"""
|
||||
Thermal zones delimited by the thermal boundary
|
||||
:param value: [ThermalZone]
|
||||
"""
|
||||
self._thermal_zones = value
|
||||
|
||||
@property
|
||||
def azimuth(self):
|
||||
"""
|
||||
|
|
|
@ -29,6 +29,7 @@ class ThermalZone:
|
|||
self._volume = volume
|
||||
self._volume_geometry = None
|
||||
self._id = None
|
||||
self._ordinate_number = None
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
|
@ -181,3 +182,19 @@ class ThermalZone:
|
|||
:return: Polyhedron
|
||||
"""
|
||||
return self._volume_geometry
|
||||
|
||||
@property
|
||||
def ordinate_number(self):
|
||||
"""
|
||||
In case the thermal_zones need to be enumerated and their order saved, this property saves that order
|
||||
:return: int
|
||||
"""
|
||||
return self._ordinate_number
|
||||
|
||||
@ordinate_number.setter
|
||||
def ordinate_number(self, value):
|
||||
"""
|
||||
Sets an specific order of the zones to be called
|
||||
:param value: int
|
||||
"""
|
||||
self._ordinate_number = value
|
||||
|
|
|
@ -6,6 +6,7 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons
|
|||
import sys
|
||||
from imports.construction.helpers.construction_helper import ConstructionHelper
|
||||
from imports.construction.nrel_physics_interface import NrelPhysicsInterface
|
||||
from imports.construction.helpers.storeys_generation import StoreysGeneration
|
||||
|
||||
|
||||
class CaPhysicsParameters(NrelPhysicsInterface):
|
||||
|
@ -31,6 +32,8 @@ class CaPhysicsParameters(NrelPhysicsInterface):
|
|||
f'{ConstructionHelper.nrcan_from_function(building.function)} '
|
||||
f'and building year of construction: {building.year_of_construction}\n')
|
||||
continue
|
||||
|
||||
self._create_storeys(building, archetype)
|
||||
self._assign_values(building, archetype)
|
||||
|
||||
def _search_archetype(self, function, year_of_construction):
|
||||
|
@ -45,8 +48,6 @@ class CaPhysicsParameters(NrelPhysicsInterface):
|
|||
return None
|
||||
|
||||
def _assign_values(self, building, archetype):
|
||||
building.average_storey_height = archetype.average_storey_height
|
||||
building.storeys_above_ground = archetype.storeys_above_ground
|
||||
for thermal_zone in building.thermal_zones:
|
||||
thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value
|
||||
thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity
|
||||
|
@ -67,3 +68,12 @@ class CaPhysicsParameters(NrelPhysicsInterface):
|
|||
thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio
|
||||
thermal_opening.g_value = thermal_opening_archetype.g_value
|
||||
thermal_opening.overall_u_value = thermal_opening_archetype.overall_u_value
|
||||
|
||||
@staticmethod
|
||||
def _create_storeys(building, archetype):
|
||||
building.average_storey_height = archetype.average_storey_height
|
||||
building.storeys_above_ground = archetype.storeys_above_ground
|
||||
storeys_generation = StoreysGeneration(building)
|
||||
storeys = storeys_generation.storeys
|
||||
building.storeys = storeys
|
||||
storeys_generation.assign_thermal_zones_delimited_by_thermal_boundaries()
|
||||
|
|
|
@ -10,6 +10,7 @@ from imports.construction.nrel_physics_interface import NrelPhysicsInterface
|
|||
from imports.construction.helpers.construction_helper import ConstructionHelper
|
||||
from city_model_structure.building_demand.layer import Layer
|
||||
from city_model_structure.building_demand.material import Material
|
||||
from imports.construction.helpers.storeys_generation import StoreysGeneration
|
||||
|
||||
|
||||
class UsPhysicsParameters(NrelPhysicsInterface):
|
||||
|
@ -39,6 +40,8 @@ class UsPhysicsParameters(NrelPhysicsInterface):
|
|||
sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} '
|
||||
f'and building year of construction: {building.year_of_construction}\n')
|
||||
continue
|
||||
|
||||
self._create_storeys(building, archetype)
|
||||
self._assign_values(building, archetype)
|
||||
|
||||
def _search_archetype(self, building_type, standard, climate_zone):
|
||||
|
@ -51,8 +54,6 @@ class UsPhysicsParameters(NrelPhysicsInterface):
|
|||
return None
|
||||
|
||||
def _assign_values(self, building, archetype):
|
||||
building.average_storey_height = archetype.average_storey_height
|
||||
building.storeys_above_ground = archetype.storeys_above_ground
|
||||
for thermal_zone in building.thermal_zones:
|
||||
thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value
|
||||
thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity
|
||||
|
@ -95,3 +96,12 @@ class UsPhysicsParameters(NrelPhysicsInterface):
|
|||
thermal_opening_archetype.back_side_solar_transmittance_at_normal_incidence
|
||||
thermal_opening.front_side_solar_transmittance_at_normal_incidence = \
|
||||
thermal_opening_archetype.front_side_solar_transmittance_at_normal_incidence
|
||||
|
||||
@staticmethod
|
||||
def _create_storeys(building, archetype):
|
||||
building.average_storey_height = archetype.average_storey_height
|
||||
building.storeys_above_ground = archetype.storeys_above_ground
|
||||
storeys_generation = StoreysGeneration(building)
|
||||
storeys = storeys_generation.storeys
|
||||
building.storeys = storeys
|
||||
storeys_generation.assign_thermal_zones_delimited_by_thermal_boundaries()
|
||||
|
|
|
@ -83,8 +83,7 @@ class CityGml:
|
|||
surfaces = CityGmlLod2(city_object).surfaces
|
||||
else:
|
||||
raise NotImplementedError("Not supported level of detail")
|
||||
return Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, [],
|
||||
divide_in_storeys=True)
|
||||
return Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, [])
|
||||
|
||||
def _create_parts_consisting_building(self, city_object):
|
||||
name = city_object['@id']
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""
|
||||
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 © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
|
@ -17,6 +18,10 @@ class SchedulesFactory:
|
|||
self._handler = '_' + handler.lower().replace(' ', '_')
|
||||
self._city = city
|
||||
self._base_path = base_path
|
||||
for building in city.buildings:
|
||||
if len(building.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.')
|
||||
|
||||
def _comnet(self):
|
||||
ComnetSchedules(self._city, self._base_path)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""
|
||||
UsageFactory retrieve the specific usage module for the given region
|
||||
This factory can only be called after calling the construction factory so the thermal zones are created.
|
||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||
"""
|
||||
|
@ -18,6 +19,10 @@ class UsageFactory:
|
|||
self._handler = '_' + handler.lower().replace(' ', '_')
|
||||
self._city = city
|
||||
self._base_path = base_path
|
||||
for building in city.buildings:
|
||||
if len(building.thermal_zones) == 0:
|
||||
raise Exception('It seems that the usage factory is being called before the construction factory. '
|
||||
'Please ensure that the construction factory is called first.')
|
||||
|
||||
def _hft(self):
|
||||
return HftUsageParameters(self._city, self._base_path).enrich_buildings()
|
||||
|
|
Loading…
Reference in New Issue
Block a user