city_retrofit/city_model_structure/building.py
2020-10-30 08:25:36 -04:00

452 lines
12 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 matplotlib.patches as patches
import numpy as np
from matplotlib import pylab
from shapely import ops
from shapely.geometry import MultiPolygon
import pandas as pd
import helpers.constants as cte
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 city_model_structure.building_unit import BuildingUnit
class Building(CityObject):
"""
Building(CityObject) class
"""
def __init__(self, name, lod, surfaces, terrains, year_of_construction, function, lower_corner, attic_heated=0,
basement_heated=0):
super().__init__(lod, surfaces, name)
self._basement_heated = basement_heated
self._attic_heated = attic_heated
self._terrains = terrains
self._year_of_construction = year_of_construction
self._function = function
self._lower_corner = lower_corner
self._average_storey_height = None
self._storeys_above_ground = None
self._foot_print = None
self._usage_zones = []
self._building_units = []
self._type = 'building'
self._m_heating = pd.DataFrame()
self._h_heating = pd.DataFrame()
self._heating = pd.DataFrame()
self._m_cooling = pd.DataFrame()
self._h_cooling = pd.DataFrame()
self._cooling = pd.DataFrame()
self._external_temperature = dict()
self._global_horizontal = dict()
self._diffuse = dict()
self._beam = dict()
# ToDo: Check this for LOD4
self._thermal_zones = []
if self.lod < 8:
# for lod under 4 is just one thermal zone
self._thermal_zones.append(ThermalZone(self.surfaces))
for t_zones in self._thermal_zones:
t_zones.bounded = [ThermalBoundary(s, [t_zones]) for s in t_zones.surfaces]
surface_id = 0
for surface in self._surfaces:
surface.lower_corner = self._lower_corner
surface.parent(self, surface_id)
surface_id += 1
@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._attic_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 the calculated based on the basement and attic heated values
return self.volume
@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]
def _plot(self, polygon):
points = ()
for point_tuple in polygon.exterior.coords:
almost_equal = False
for point in points:
point_1 = Building._tuple_to_point(point)
point_2 = Building._tuple_to_point(point_tuple)
if self._geometry.almost_equal(point_1, point_2):
almost_equal = True
break
if not almost_equal:
points = points + (point_tuple,)
points = points + (points[0],)
pylab.scatter([point[0] for point in points], [point[1] for point in points])
pylab.gca().add_patch(patches.Polygon(points, closed=True, fill=True))
pylab.grid()
pylab.show()
@property
def foot_print(self) -> Surface:
"""
City object foot print surface
:return: Surface
"""
if self._foot_print is None:
shapelys = []
union = None
for surface in self.surfaces:
if surface.shapely.is_empty or not surface.shapely.is_valid:
continue
shapelys.append(surface.shapely)
union = ops.unary_union(shapelys)
shapelys = [union]
if isinstance(union, MultiPolygon):
Exception('foot print returns a multipolygon')
points_list = []
for point_tuple in union.exterior.coords:
# ToDo: should be Z 0.0 or min Z?
point = Building._tuple_to_point(point_tuple)
almost_equal = False
for existing_point in points_list:
if self._geometry.almost_equal(point, existing_point):
almost_equal = True
break
if not almost_equal:
points_list.append(point)
points_list = np.reshape(points_list, len(points_list) * 3)
points = np.array_str(points_list).replace('[', '').replace(']', '')
self._foot_print = Surface(points, remove_last=False, is_projected=True)
return self._foot_print
@property
def type(self):
"""
building type
:return: str
"""
return self._type
@property
def building_units(self) -> [BuildingUnit]:
"""
Building units
:return:
"""
return self._building_units
@building_units.setter
def building_units(self, value):
"""
Building units
:param value: [BuildingUnit]
"""
self._building_units = value
@property
def _monthly_heating(self) -> pd.DataFrame:
"""
building monthly heating values in Watts-hour
:return: DataFrame with 12 values and a header with the source of those
"""
return self._m_heating
@_monthly_heating.setter
def _monthly_heating(self, value):
"""
building monthly heating values in Watts-hour and a header with the source
:param value: DataFrame(heating demand)
"""
if self._m_heating.empty:
self._m_heating = value
else:
self._m_heating = pd.concat([self._m_heating, value], axis=1)
@property
def _hourly_heating(self) -> pd.DataFrame:
"""
building hourly heating values in Watts-hour
:return: DataFrame with 8760 values and a header with the source of those
"""
return self._h_heating
@_hourly_heating.setter
def _hourly_heating(self, value):
"""
building hourly heating values in Watts-hour and a header with the source
:param value: DataFrame(heating demand)
"""
if self._h_heating.empty:
self._h_heating = value
else:
self._h_heating = pd.concat([self._h_heating, value], axis=1)
def heating(self, time_scale):
"""
Get heating demand in Wh in a defined time_scale
:param time_scale: string.
:return: DataFrame(float)
"""
if time_scale == cte.time_scale['hour']:
self._heating = self._hourly_heating
elif time_scale == cte.time_scale['month']:
self._heating = self._monthly_heating
else:
raise NotImplementedError
return self._heating
@property
def _monthly_cooling(self) -> pd.DataFrame:
"""
building monthly cooling values in Watts-hour
:return: DataFrame with 12 values and a header with the source of those
"""
return self._m_cooling
@_monthly_cooling.setter
def _monthly_cooling(self, value):
"""
building monthly cooling values in Watts-hour and a header with the source
:param value: DataFrame(cooling demand)
"""
if self._m_cooling.empty:
self._m_cooling = value
else:
self._m_cooling = pd.concat([self._m_cooling, value], axis=1)
@property
def _hourly_cooling(self) -> pd.DataFrame:
"""
building hourly cooling values in Watts-hour
:return: DataFrame with 8760 values and a header with the source of those
"""
return self._h_cooling
@_hourly_cooling.setter
def _hourly_cooling(self, value):
"""
building hourly cooling values in Watts-hour and a header with the source
:param value: DataFrame(cooling demand)
"""
if self._h_cooling.empty:
self._h_cooling = value
else:
self._h_cooling = pd.concat([self._h_cooling, value], axis=1)
def cooling(self, time_scale):
"""
Get cooling demand in Wh in a defined time_scale
:param time_scale: string.
:return: DataFrame(float)
"""
if time_scale == cte.time_scale['hour']:
self._cooling = self._hourly_cooling
elif time_scale == cte.time_scale['month']:
self._cooling = self._monthly_cooling
else:
raise NotImplementedError
return self._cooling
@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(external temperature)}
"""
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(global horizontal radiation)}
"""
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(diffuse radiation)}
"""
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(beam radiation)}
"""
self._beam = value