""" Surface 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 """ from __future__ import annotations import numpy as np import uuid from city_model_structure.attributes.polygon import Polygon from city_model_structure.attributes.pv_system import PvSystem class Surface: """ Surface class """ def __init__(self, solid_polygon, perimeter_polygon, holes_polygons=None, surface_type=None, swr=None): self._type = surface_type self._swr = swr self._name = None self._id = None self._azimuth = None self._inclination = None self._area_above_ground = None self._area_below_ground = None self._parent = None self._lower_corner = None self._upper_corner = None self._shared_surfaces = [] self._global_irradiance = dict() self._perimeter_polygon = perimeter_polygon self._holes_polygons = holes_polygons self._solid_polygon = solid_polygon self._pv_system_installed = None @property def name(self): """ Surface name :return: str """ if self._name is None: self._name = str(uuid.uuid4()) return self._name @property def id(self): """ Surface id :return str """ if self._id is None: raise ValueError('Undefined surface id') return self._id @id.setter def id(self, value): self._id = value @property def swr(self): """ Get surface short wave reflectance :return: float """ return self._swr @swr.setter def swr(self, value): """ Set surface short wave reflectance :param value: float """ self._swr = value def _max_coord(self, axis): if axis == 'x': axis = 0 elif axis == 'y': axis = 1 else: axis = 2 max_coordinate = '' for point in self.perimeter_polygon.points: if max_coordinate == '': max_coordinate = point[axis] elif max_coordinate < point[axis]: max_coordinate = point[axis] return max_coordinate def _min_coord(self, axis): if axis == 'x': axis = 0 elif axis == 'y': axis = 1 else: axis = 2 min_coordinate = '' for point in self.perimeter_polygon.points: if min_coordinate == '': min_coordinate = point[axis] elif min_coordinate > point[axis]: min_coordinate = point[axis] return min_coordinate @property def lower_corner(self): """ Surface's lower corner [x, y, z] :return: [float] """ if self._lower_corner is None: self._lower_corner = [self._min_coord('x'), self._min_coord('y'), self._min_coord('z')] return self._lower_corner @property def upper_corner(self): """ Surface's upper corner [x, y, z] :return: [float] """ if self._upper_corner is None: self._upper_corner = [self._max_coord('x'), self._max_coord('y'), self._max_coord('z')] return self._upper_corner @property def area_above_ground(self): """ Surface area above ground in square meters :return: float """ if self._area_above_ground is None: self._area_above_ground = self.perimeter_polygon.area - self.area_below_ground return self._area_above_ground # todo: to be implemented when adding terrains @property def area_below_ground(self): """ Surface area below ground in square meters :return: float """ return 0.0 @property def azimuth(self): """ Surface azimuth in radians :return: float """ if self._azimuth is None: normal = self.perimeter_polygon.normal self._azimuth = np.arctan2(normal[1], normal[0]) return self._azimuth @property def inclination(self): """ Surface inclination in radians :return: float """ if self._inclination is None: self._inclination = np.arccos(self.perimeter_polygon.normal[2]) return self._inclination @property def type(self): """ Surface type Ground, Wall or Roof :return: str """ if self._type is None: grad = np.rad2deg(self.inclination) if grad >= 170: self._type = 'Ground' elif 80 <= grad <= 100: self._type = 'Wall' else: self._type = 'Roof' return self._type @property def global_irradiance(self) -> dict: """ global irradiance on surface in Wh/m2 :return: dict{DataFrame(float)} """ return self._global_irradiance @global_irradiance.setter def global_irradiance(self, value): """ global irradiance on surface in Wh/m2 :param value: dict{DataFrame(float)} """ self._global_irradiance = value @property def perimeter_polygon(self) -> Polygon: """ total surface defined by the perimeter, merging solid and holes :return: Polygon """ return self._perimeter_polygon @property def solid_polygon(self) -> Polygon: """ solid surface :return: Polygon """ return self._solid_polygon @property def holes_polygons(self) -> [Polygon]: """ hole surfaces, a list of hole polygons found in the surface :return: None, [] or [Polygon] None -> not known whether holes exist in reality or not due to low level of detail of input data [] -> no holes in the surface [Polygon] -> one or more holes in the surface """ return self._holes_polygons @property def pv_system_installed(self) -> PvSystem: """ PV system installed on the surface :return: PvSystem """ return self._pv_system_installed @pv_system_installed.setter def pv_system_installed(self, value): """ PV system installed on the surface :param value: PvSystem """ self._pv_system_installed = value