city_retrofit/city_model_structure/building.py

398 lines
10 KiB
Python

"""
Building module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
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
from city_model_structure.surface import Surface
from city_model_structure.thermal_boundary import ThermalBoundary
from city_model_structure.thermal_zone import ThermalZone
from city_model_structure.usage_zone import UsageZone
from city_model_structure.city_object import CityObject
from city_model_structure.building_unit import BuildingUnit
from city_model_structure.schedule_value import ScheduleValue
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._monthly_heating = pd.DataFrame()
self._monthly_cooling = pd.DataFrame()
self._hourly_heating = []
self._hourly_cooling = []
self._week_day_schedule = []
self._saturday_schedule = []
self._sunday_schedule = []
# 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):
"""
building monthly heating values in Watts-hour
:return: DataFrame with 12 values and a header with the source of those
"""
return self._monthly_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._monthly_heating.empty:
self._monthly_heating = value
else:
self._monthly_heating = pd.concat([self._monthly_heating, value])
@property
def monthly_cooling(self):
"""
building monthly cooling values in Watts-hour
:return: DataFrame with 12 values and a header with the source of those
"""
return self._monthly_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._monthly_cooling.empty:
self._monthly_cooling = value
else:
self._monthly_cooling = pd.concat([self._monthly_cooling, value])
@property
def hourly_heating(self):
"""
building hourly heating values in Watts-hour
:return: DataFrame with 8760 values and a header with the source of those
"""
return self._hourly_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)
"""
self._hourly_heating.append(value)
@property
def hourly_cooling(self):
"""
building hourly cooling values in Watts-hour
:return: DataFrame with 8760 values and a header with the source of those
"""
return self._hourly_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)
"""
self._hourly_cooling.append(value)
@property
def week_day_schedule(self) -> List[ScheduleValue]:
"""
Get schedule of weekdays
:return: [ScheduleValue]
"""
return self._week_day_schedule
@week_day_schedule.setter
def week_day_schedule(self, value):
"""
occupancy schedules of week days
:param value: week day schedules
"""
self._week_day_schedule = value
@property
def saturday_schedule(self) -> List[ScheduleValue]:
"""
Get schedule of Saturdays
:return: [Saturday Schedule_Values]
"""
return self._saturday_schedule
@saturday_schedule.setter
def saturday_schedule(self, value):
"""
occupancy schedules of Saturdays
:param value: Saturday schedules
"""
self._saturday_schedule = value
@property
def sunday_schedule(self) -> List[ScheduleValue]:
"""
Get schedule of Sundays
:return: [Sundays Schedule_Values]
"""
return self._sunday_schedule
@sunday_schedule.setter
def sunday_schedule(self, value):
"""
occupancy schedules of Sundays
:param value: Sunday schedules
"""
self._sunday_schedule = value