""" Building module SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import sys from typing import List import numpy as np import math 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 trimesh import Trimesh class Building(CityObject): """ Building(CityObject) class """ def __init__(self, name, lod, surfaces, year_of_construction, function, city_lower_corner, terrains=None, zones_surfaces_ids=None): super().__init__(name, lod, surfaces, city_lower_corner) self._basement_heated = None self._attic_heated = None self._terrains = terrains self._year_of_construction = year_of_construction self._function = function 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._eave_height = None self._grounds = [] self._roofs = [] self._walls = [] self._internal_walls = [] self._thermal_zones = [] zone_surfaces = None if zones_surfaces_ids is not None: for zone_surfaces_ids in zones_surfaces_ids: zone_surfaces = [] for surface_id in zone_surfaces_ids: zone_surfaces.append(self.surface(surface_id)) else: zone_surfaces = surfaces self._thermal_zones.append(ThermalZone(zone_surfaces)) for t_zones in self._thermal_zones: t_zones.bounded = [ThermalBoundary(s, [t_zones]) for s in t_zones.surfaces] for surface_id, surface in enumerate(self.surfaces): self._min_x = min(self._min_x, surface.bounds_lower_corner[0]) self._min_y = min(self._min_y, surface.bounds_lower_corner[1]) self._min_z = min(self._min_z, surface.bounds_lower_corner[2]) surface.id = surface_id if surface.type == 'Ground': self._grounds.append(surface) elif surface.type == 'Wall': self._walls.append(surface) elif surface.type == 'Roof': self._roofs.append(surface) else: self._internal_walls.append(surface) self._pv_plus_hp_installation = None @property def grounds(self) -> [Surface]: """ Building ground surfaces """ return self._grounds @property def is_heated(self): """ Get building heated flag :return: Boolean """ for thermal_zone in self.thermal_zones: if thermal_zone.is_heated: return thermal_zone.is_heated return False @property def is_cooled(self): """ Get building cooled flag :return: Boolean """ for thermal_zone in self.thermal_zones: if thermal_zone.is_cooled: return thermal_zone.is_cooled return False @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 """ self._usage_zones = values for thermal_zone in self.thermal_zones: thermal_zone.usage_zones = 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 @function.setter def function(self, value): """ Set building function :param value: string :return: None """ self._function = value @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 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 eave_height(self): """ building eave height in meters :return: float """ if self._eave_height is None: self._eave_height = 0 for wall in self.walls: self._eave_height = max(self._eave_height, wall.bounds_upper_corner[2]) return self._eave_height @property def storeys(self) -> [Trimesh]: """ subsections of building trimesh by storage in case of no interiors defined :return: [Trimesh] """ trimesh = self.simplified_polyhedron.trimesh if self.average_storey_height is None: if self.storeys_above_ground is None or self.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 [trimesh] else: number_of_storeys = int(self.storeys_above_ground) height = self.eave_height / number_of_storeys else: height = self.average_storey_height if self.storeys_above_ground is not None: number_of_storeys = int(self.storeys_above_ground) else: number_of_storeys = math.floor(float(self.eave_height) / height) + 1 last_storey_height = float(self.eave_height) - height*(number_of_storeys-1) if last_storey_height < 0.3*height: number_of_storeys -= 1 storeys = [] for n in range(0, number_of_storeys - 1): point_plane = [self.city_object_lower_corner[0], self.city_object_lower_corner[1], self.city_object_lower_corner[2] + height * (n + 1)] normal = [0, 0, -1] storey, trimesh = gh.divide_mesh_by_plane(trimesh, normal, point_plane) storeys.append(storey) storeys.append(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 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 pv_plus_hp_installation(self): return self._pv_plus_hp_installation @pv_plus_hp_installation.setter def pv_plus_hp_installation(self, value): self._pv_plus_hp_installation = value