hub/city_model_structure/building.py
Pilar e353a7c800 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	city_model_structure/building.py
2021-03-25 09:15:05 -04:00

421 lines
11 KiB
Python

"""
Building module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
contributors: Pilar Monsalvete pilar_monsalvete@yahoo.es
"""
from typing import List
import numpy as np
from city_model_structure.attributes.surface import Surface
from city_model_structure.attributes.thermal_boundary import ThermalBoundary
from city_model_structure.attributes.thermal_zone import ThermalZone
from city_model_structure.attributes.usage_zone import UsageZone
from city_model_structure.city_object import CityObject
from helpers.geometry_helper import GeometryHelper as gh
from helpers.configuration_helper import ConfigurationHelper
import math
from pathlib import Path
class Building(CityObject):
"""
Building(CityObject) class
"""
def __init__(self, name, lod, surfaces, terrains, year_of_construction, function, city_lower_corner):
super().__init__(lod, surfaces, name)
self._basement_heated = None
self._attic_heated = None
self._terrains = terrains
self._year_of_construction = year_of_construction
self._function = function
self._city_lower_corner = city_lower_corner
self._building_lower_corner = None
self._heated = None
self._cooled = None
self._average_storey_height = None
self._storeys_above_ground = None
self._floor_area = None
self._roof_type = None
self._usage_zones = []
self._type = 'building'
self._heating = dict()
self._cooling = dict()
self._external_temperature = dict()
self._global_horizontal = dict()
self._diffuse = dict()
self._beam = dict()
self._min_x = ConfigurationHelper().max_coordinate
self._min_y = ConfigurationHelper().max_coordinate
self._min_z = ConfigurationHelper().max_coordinate
self._centroid = None
self._grounds = []
self._roofs = []
self._walls = []
# ToDo: Check this for LOD4
self._thermal_zones = []
if self.lod < 4:
# for lod under 4 is just one thermal zone
self._thermal_zones.append(ThermalZone(self.surfaces, self._heated, self._cooled))
for t_zones in self._thermal_zones:
t_zones.bounded = [ThermalBoundary(s, [t_zones]) for s in t_zones.surfaces]
for surface in self.surfaces:
self._min_x = min(self._min_x, surface.min_x)
self._min_y = min(self._min_y, surface.min_y)
self._min_z = min(self._min_z, surface.min_z)
if surface.type == 'Ground':
self._grounds.append(surface)
elif surface.type == 'Wall':
self._walls.append(surface)
else:
self._roofs.append(surface)
@property
def grounds(self) -> [Surface]:
"""
Building ground surfaces
"""
return self._grounds
@property
def roofs(self) -> [Surface]:
"""
Building roof surfaces
"""
return self._roofs
@property
def walls(self) -> [Surface]:
"""
Building wall surfaces
"""
return self._walls
@property
def usage_zones(self) -> List[UsageZone]:
"""
Get city object usage zones
:return: [UsageZone]
"""
return self._usage_zones
@usage_zones.setter
def usage_zones(self, values):
"""
Set city objects usage zones
:param values: [UsageZones]
:return: None
"""
# ToDo: this is only valid for one usage zone need to be revised for multiple usage zones.
self._usage_zones = values
for thermal_zone in self.thermal_zones:
thermal_zone.usage_zones = [(100, usage_zone) for usage_zone in values]
@property
def terrains(self) -> List[Surface]:
"""
Get city object terrain surfaces
:return: [Surface]
"""
return self._terrains
@property
def attic_heated(self):
"""
Get if the city object attic is heated
:return: Boolean
"""
return self._attic_heated
@attic_heated.setter
def attic_heated(self, value):
"""
Set if the city object attic is heated
:param value: Boolean
:return: None
"""
self._attic_heated = value
@property
def basement_heated(self):
"""
Get if the city object basement is heated
:return: Boolean
"""
return self._basement_heated
@basement_heated.setter
def basement_heated(self, value):
"""
Set if the city object basement is heated
:param value: Boolean
:return: None
"""
self._basement_heated = value
@property
def name(self):
"""
City object name
:return: str
"""
return self._name
@property
def thermal_zones(self) -> List[ThermalZone]:
"""
City object thermal zones
:return: [ThermalZone]
"""
return self._thermal_zones
@property
def heated_volume(self):
"""
City object heated volume in cubic meters
:return: float
"""
# ToDo: this need to be calculated based on the basement and attic heated values
raise NotImplementedError
@property
def year_of_construction(self):
"""
City object year of construction
:return: int
"""
return self._year_of_construction
@property
def function(self):
"""
City object function
:return: str
"""
return self._function
@property
def average_storey_height(self):
"""
Get city object average storey height in meters
:return: float
"""
return self._average_storey_height
@average_storey_height.setter
def average_storey_height(self, value):
"""
Set city object average storey height in meters
:param value: float
:return: None
"""
self._average_storey_height = value
@property
def storeys_above_ground(self):
"""
Get city object storeys number above ground
:return: int
"""
return self._storeys_above_ground
@storeys_above_ground.setter
def storeys_above_ground(self, value):
"""
Set city object storeys number above ground
:param value: int
:return:
"""
self._storeys_above_ground = value
@staticmethod
def _tuple_to_point(xy_tuple):
return [xy_tuple[0], xy_tuple[1], 0.0]
@property
def type(self):
"""
building type
:return: str
"""
return self._type
@property
def heating(self) -> dict:
"""
heating demand in Wh
:return: dict{DataFrame(float)}
"""
return self._heating
@heating.setter
def heating(self, value):
"""
heating demand in Wh
:param value: dict{DataFrame(float)}
"""
self._heating = value
@property
def cooling(self) -> dict:
"""
cooling demand in Wh
:return: dict{DataFrame(float)}
"""
return self._cooling
@cooling.setter
def cooling(self, value):
"""
cooling demand in Wh
:param value: dict{DataFrame(float)}
"""
self._cooling = value
@property
def external_temperature(self) -> dict:
"""
external temperature surrounding the building in grads Celsius
:return: dict{DataFrame(float)}
"""
return self._external_temperature
@external_temperature.setter
def external_temperature(self, value):
"""
external temperature surrounding the building in grads Celsius
:param value: dict{DataFrame(float)}
"""
self._external_temperature = value
@property
def global_horizontal(self) -> dict:
"""
global horizontal radiation surrounding the building in W/m2
:return: dict{DataFrame(float)}
"""
return self._global_horizontal
@global_horizontal.setter
def global_horizontal(self, value):
"""
global horizontal radiation surrounding the building in W/m2
:param value: dict{DataFrame(float)}
"""
self._global_horizontal = value
@property
def diffuse(self) -> dict:
"""
diffuse radiation surrounding the building in W/m2
:return: dict{DataFrame(float)}
"""
return self._diffuse
@diffuse.setter
def diffuse(self, value):
"""
diffuse radiation surrounding the building in W/m2
:param value: dict{DataFrame(float)}
"""
self._diffuse = value
@property
def beam(self) -> dict:
"""
beam radiation surrounding the building in W/m2
:return: dict{DataFrame(float)}
"""
return self._beam
@beam.setter
def beam(self, value):
"""
beam radiation surrounding the building in W/m2
:param value: dict{DataFrame(float)}
"""
self._beam = value
@property
def building_lower_corner(self):
if self._building_lower_corner is None:
self._building_lower_corner = [self._min_x, self._min_y, self._min_z]
return self._building_lower_corner
@property
def storeys(self):
storeys = []
height = self.average_storey_height
if self.storeys_above_ground is not None:
number_of_storeys = self.storeys_above_ground
else:
number_of_storeys = math.floor(float(self.max_height) / height) + 1
print('number_of_storeys', number_of_storeys)
last_storey_height = float(self.max_height) - height*(number_of_storeys-1)
print('last_storey_height', last_storey_height)
if last_storey_height < height/2:
number_of_storeys -= 1
print('number storeys', number_of_storeys)
trimesh = self.simplified_polyhedron.trimesh
normal_plane = [0, 0, -1]
rest_trimesh = trimesh
for n in range(0, number_of_storeys - 1):
print(n)
point_plane = [self.building_lower_corner[0], self.building_lower_corner[1],
self.building_lower_corner[2] + height*(n+1)]
print('point plane', point_plane)
print('rest trimesh', rest_trimesh.volume)
trimeshes = gh.divide_mesh_by_plane(rest_trimesh, normal_plane, point_plane)
print('number meshes', len(trimeshes))
storey = trimeshes[0]
file_name = 'storey_' + str(n) + '.obj'
path_name = (Path(__file__).parent.parent / 'tests' / 'tests_outputs' / file_name).resolve()
with open(path_name, 'w') as file:
file.write(storey.export(file_type='obj'))
rest_trimesh = trimeshes[1]
file_name = 'rest_trimesh_' + str(n) + '.obj'
path_name = (Path(__file__).parent.parent / 'tests' / 'tests_outputs' / file_name).resolve()
with open(path_name, 'w') as file:
file.write(rest_trimesh.export(file_type='obj'))
storeys.append(storey)
storeys.append(rest_trimesh)
return storeys
@property
def roof_type(self):
"""
Roof type for the building flat or pitch
"""
if self._roof_type is None:
self._roof_type = 'flat'
for roof in self.roofs:
grads = np.rad2deg(roof.inclination)
if 355 > grads > 5:
self._roof_type = 'pitch'
break
print (self._roof_type)
return self._roof_type
@property
def floor_area(self):
"""
Floor area of the building m2
:return: float
"""
if self._floor_area is None:
self._floor_area = 0
for surface in self.surfaces:
if surface.type == 'Ground':
self._floor_area += surface.perimeter_polygon.area
return self._floor_area
@property
def centroid(self):
if self._centroid is None:
self._centroid = self.simplified_polyhedron.centroid
return self._centroid