attic_heated() (city_model_structure.building.Building property)

average_storey_height() (city_model_structure.building.Building property)

basement_heated() (city_model_structure.building.Building property)

heated_volume() (city_model_structure.building.Building property)

storeys_above_ground() (city_model_structure.building.Building property)

thermal_zones() (city_model_structure.building.Building property)

year_of_construction() (city_model_structure.building.Building property)

latent_fraction() (city_model_structure.internal_gains.InternalGains property)

conductivity() (city_model_structure.material.Material property)

solar_absorptance() (city_model_structure.material.Material property)

specific_heat() (city_model_structure.material.Material property)

thermal_absorptance() (city_model_structure.material.Material property)

thermal_resistance() (city_model_structure.material.Material property)

visible_absorptance() (city_model_structure.material.Material property)

vertices() (city_model_structure.polyhedron.Polyhedron property)

area_above_ground() (city_model_structure.surface.Surface property)

area_below_ground() (city_model_structure.surface.Surface property)

global_irradiance_hour() (city_model_structure.surface.Surface property)

global_irradiance_month() (city_model_structure.surface.Surface property)

ground_polygon() (city_model_structure.surface.Surface property)

ThermalBoundary (class in city_model_structure.thermal_boundary)

area() (city_model_structure.thermal_boundary.ThermalBoundary property)

azimuth() (city_model_structure.thermal_boundary.ThermalBoundary property)

delimits() (city_model_structure.thermal_boundary.ThermalBoundary property)

inclination() (city_model_structure.thermal_boundary.ThermalBoundary property)

layers() (city_model_structure.thermal_boundary.ThermalBoundary property)

type() (city_model_structure.thermal_boundary.ThermalBoundary property)

u_value() (city_model_structure.thermal_boundary.ThermalBoundary property)

window_area() (city_model_structure.thermal_boundary.ThermalBoundary property)

window_ratio() (city_model_structure.thermal_boundary.ThermalBoundary property)

conductivity() (city_model_structure.thermal_opening.ThermalOpening property)

frame_ratio() (city_model_structure.thermal_opening.ThermalOpening property)

g_value() (city_model_structure.thermal_opening.ThermalOpening property)

openable_ratio() (city_model_structure.thermal_opening.ThermalOpening property)

overall_u_value() (city_model_structure.thermal_opening.ThermalOpening property)

thickness() (city_model_structure.thermal_opening.ThermalOpening property)

bounded() (city_model_structure.thermal_zone.ThermalZone property)

cooled() (city_model_structure.thermal_zone.ThermalZone property)

floor_area() (city_model_structure.thermal_zone.ThermalZone property)

heated() (city_model_structure.thermal_zone.ThermalZone property)

surfaces() (city_model_structure.thermal_zone.ThermalZone property)

usage_zones() (city_model_structure.thermal_zone.ThermalZone property)

cooling_setpoint() (city_model_structure.usage_zone.UsageZone property)

days_year() (city_model_structure.usage_zone.UsageZone property)

heating_setback() (city_model_structure.usage_zone.UsageZone property)

heating_setpoint() (city_model_structure.usage_zone.UsageZone property)

hours_day() (city_model_structure.usage_zone.UsageZone property)

internal_gains() (city_model_structure.usage_zone.UsageZone property)

mechanical_air_change() (city_model_structure.usage_zone.UsageZone property)

CERC libs documentation master file, created by
sphinx-quickstart on Mon Jun 15 09:17:03 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

City module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca

Copyright © 2020 Project Author Guille Gutierrez 

Add a CityObject to the city
:param new_city_object:CityObject
:return: None

Bixi features belonging to the city
:return: NotImplementedError

City.city_object(name) -> Optional[city_model_structure.city_object.CityObject]

Retrieve the city CityObject with the given name
:param name:str
:return: None or CityObject

City objects belonging to the city
:return: None or [CityObject]

Composting plants belonging to the city

CityObject module

Building(name, lod, surfaces, terrains, year_of_construction, function, lower_corner, attic_heated=0, basement_heated=0)

name, lod, surfaces, terrains, year_of_construction, function, lower_corner, attic_heated=0, basement_heated=0

Export the city object to stl file (city_object_name.stl) to the given path
:param path: str

Building.surface(name) -> Optional[city_model_structure.surface.Surface]

Get the city object surface with a given name
:param name: str
:return: None or Surface

InternalGains module

Layers module

Material module

Polyhedron module

Export the polyhedron to stl given file
:param full_path: str

Surface module

Surface(coordinates, surface_type=None, name=None, swr='0.2', remove_last=True, is_projected=False)

coordinates, surface_type=None, name=None, swr='0.2', remove_last=True, is_projected=False

Add a given surface and shared area in percent to this surface.
:param surface:
:param intersection_area:
:return:

Surface.intersect(surface) -> Optional[city_model_structure.surface.Surface]

Get the intersection surface, if any, between the given surface and this surface
:param surface: Surface

Assign a city object as surface parent and a surface id
:param parent:  CityObject
:param surface_id: str

Check if given surface share some area with this surface

ThermalBoundary module

Thermal boundary area above ground in square meters
:return: float

Thermal boundary area below ground in square meters

Get the thermal zones delimited by the thermal boundary
:return: [ThermalZone]

Thermal boundary u value in W/m2K
internal and external convective coefficient in W/m2K values, can be configured at configuration.ini

ThermalOpening module

Get thermal opening back side solar transmittance at normal incidence

Get thermal opening front side solar transmittance at normal incidence

Get thermal opening openable ratio, NOT IMPLEMENTED
:return: Exception

ThermalZone module

Get thermal zone additional thermal bridge u value W/m2K

Get thermal boundaries bounding with the thermal zone
:return: [ThermalBoundary]

Get thermal zone infiltration rate system off in air changes per hour

Get thermal zone infiltration rate system on in air changes per hour

UsageZone module

Get usage zone heating set point in celsius grads

Set usage zone mechanical air change in air change per hour

GeometryFactory retrieve the specific geometric module to load the given format

Load  the city model structure from a geometry source
:return: City

PhysicsFactory retrieve the specific physics module for the given region

UsageFactory retrieve the specific usage module for the given region

"""
"""
import sys
from typing import List, Union

import pyproj
import reverse_geocoder as rg
from pyproj import Transformer

from city_model_structure.building import Building
from city_model_structure.city_object import CityObject

class City:
  """
  City class
  """

  def __init__(self, lower_corner, upper_corner, srs_name, buildings=None):
    self._buildings = None
    self._name = None
    self._lower_corner = lower_corner
    self._upper_corner = upper_corner
    self._buildings = buildings
    self._srs_name = srs_name
    # todo: right now extracted at city level, in the future should be extracted also at building level if exist
    self._location = None

  def _get_location(self):
    if self._location is None:
      gps = pyproj.CRS('EPSG:4326')  # LatLon with WGS84 datum used by GPS units and Google Earth
      try:
        input_reference = pyproj.CRS(self.srs_name)  # Projected coordinate system from input data
      except pyproj.exceptions.CRSError:
        print('Invalid projection reference system, please check the input data. (e.g. in CityGML files: srs_name)')
        sys.exit()
      transformer = Transformer.from_crs(input_reference, gps)
      coordinates = transformer.transform(self.lower_corner[0], self.lower_corner[1])
      self._location = rg.search(coordinates)
    return self._location

  @property
  def country_code(self):
    """
    City country code
    :return: str
    """
    return self._get_location()[0]['cc']

  @property
  def name(self):
    """
    City name
    """
    if self._name is None:
      self._name = self._get_location()[0]['name']
    return self._name

  @property
  def city_objects(self) -> Union[List[CityObject], None]:
    """
    City objects belonging to the city
    :return: None or [CityObject]
    """
    return self.buildings

  @property
  def buildings(self) -> Union[List[Building], None]:
    """
    Buildings belonging to the city
    :return: None or [Building]
    """
    return self._buildings

  @property
  def trees(self) -> NotImplementedError:
    """
    Trees belonging to the city
    :return: NotImplementedError
    """
    raise NotImplementedError

  @property
  def bixi_features(self) -> NotImplementedError:
    """
    Bixi features belonging to the city
    """

  @property
  def composting_plants(self) -> NotImplementedError:
    """
    Composting plants belonging to the city
    """

  @property
  def lower_corner(self):
    """
    City lower corner
    :return: [x,y,z]
    """
    return self._lower_corner

  @property
  def upper_corner(self):
    """
    City upper corner
    """
    return self._upper_corner

  def city_object(self, name) -> Union[CityObject, None]:
    """
    Retrieve the city CityObject with the given name
    :param name:str
    :return: None or CityObject
    """
    for city_object in self.buildings:
      if city_object.name == name:
        return city_object
    return None

  def add_city_object(self, new_city_object):
    """
    Add a CityObject to the city
    :param new_city_object:CityObject
    :return: None
    """
    if new_city_object.type != 'building':
      raise NotImplementedError(new_city_object.type)
    if self._buildings is None:
      self._buildings = []
    for building in self.buildings:
      for surface in building.surfaces:
        for surface2 in new_city_object.surfaces:
          surface.shared(surface2)
    self._buildings.append(new_city_object)

  @property
  def srs_name(self):
    """
    srs name
    """
    return self._srs_name

  @name.setter
  def name(self, value):
    """
    Set the city name
    :param value:str
    """
    self._name = value

"""
"""
from pathlib import Path
from typing import Union, List

import matplotlib.patches as patches
import numpy as np
from matplotlib import pylab
from shapely import ops
from shapely.geometry import MultiPolygon

from city_model_structure.polyhedron import Polyhedron
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

class Building(CityObject):
  """
  CityObject class
  """
  def __init__(self, name, lod, surfaces, terrains, year_of_construction, function, lower_corner, attic_heated=0,
               basement_heated=0):
    super().__init__(lod)
    self._name = name
    self._surfaces = surfaces
    self._basement_heated = basement_heated
    self._attic_heated = attic_heated
    self._terrains = terrains
    self._year_of_construction = year_of_construction
    self._function = function
    self._average_storey_height = None
    self._storeys_above_ground = None
    self._foot_print = None
    self._usage_zones = []
    self._type = 'building'

    # 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]
    """
    # 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
    """
    self._attic_heated = value

  @property
  def basement_heated(self):
    """
    Get if the city object basement is heated
    """
    return self._basement_heated

  @basement_heated.setter
  def basement_heated(self, value):
    """
    Set if the city object basement is heated
    """

  @property
    """
    City object name
    """

  @property
  def lod(self):
    """
    City object level of detail 1, 2, 3 or 4
    :return: int
    """
    return self._lod

  @property
  def surfaces(self) -> List[Surface]:
    """
    City object surfaces
    """
    return self._surfaces

  def surface(self, name) -> Union[Surface, None]:
    """
    Get the city object surface with a given name
    :param name: str
    :return: None or Surface
    """
    for s in self.surfaces:
      if s.name == name:
        return s

  @property
  def thermal_zones(self) -> List[ThermalZone]:
    """
    City object thermal zones
    :return: [ThermalZone]
    """
    return self._thermal_zones

  @property
  def volume(self):
    """
    City object volume in cubic meters
    :return: float
    """
    if self._polyhedron is None:
      self._polyhedron = Polyhedron(self.surfaces)
    return self._polyhedron.volume

  @property
  def heated_volume(self):
    """
    City object heated volume in cubic meters
    """
    # ToDo: this need to be the calculated based on the basement and attic heated values

  def stl_export(self, path):
    """
    Export the city object to stl file (city_object_name.stl) to the given path
    :param path: str
    """
    # todo: this is a method just for debugging, it will be soon removed
    full_path = (Path(path) / (self._name + '.stl')).resolve()
    self._polyhedron.export(full_path)

  @property
  def year_of_construction(self):
    """
    City object year of construction
    """
    return self._year_of_construction

  @property
  def function(self):
    """
    City object function
    """
    return self._function

  @property
  def average_storey_height(self):
    """
    Get city object average storey height in meters
    """
    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
    """
    self._average_storey_height = value

  @property
  def storeys_above_ground(self):
    """
    Get city object storeys number above ground
    """
    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):
    """
    City object type
    """
    return self._type

  @property
  def max_height(self):
    """
    City object maximal height in meters
    """
    return self._polyhedron.max_z

"""
"""

class InternalGains:
  """
  InternalGains class
  """
  def __init__(self):
    self._average_internal_gain = None
    self._convective_fraction = None
    self._radiative_fraction = None
    self._latent_fraction = None

  @property
  def average_internal_gain(self):
    """
    Get internal gains average internal gain in w/m2
    """
    return self._average_internal_gain

  @average_internal_gain.setter
  def average_internal_gain(self, value):
    """
    Set internal gains average internal gain in w/m2
    """
    self._average_internal_gain = value

  @property
  def convective_fraction(self):
    """
    Get internal gains convective fraction
    """
    return self._convective_fraction

  @convective_fraction.setter
  def convective_fraction(self, value):
    """
    Set internal gains convective fraction
    """
    self._convective_fraction = value

  @property
  def radiative_fraction(self):
    """
    Get internal gains radiative fraction
    """
    return self._radiative_fraction

  @radiative_fraction.setter
  def radiative_fraction(self, value):
    """
    """
    self._radiative_fraction = value

  @property
  def latent_fraction(self):
    """
    Get internal gains latent fraction
    """
    return self._latent_fraction

  @latent_fraction.setter
  def latent_fraction(self, value):
    """
    Set internal gains latent fraction
    """
    self._latent_fraction = value

"""
"""
from city_model_structure.material import Material

class Layer:
  """
  Layer class
  """
    self._material = None
    self._thickness = None

  @property
  def material(self) -> Material:
    """
    Get layer material
    :return: Material
    """
    return self._material

  @material.setter
  def material(self, value):
    """
    Set layer material
    :param value: Material
    """
    self._material = value

  @property
  def thickness(self):
    """
    Get layer thickness in meters
    """
    return self._thickness

  @thickness.setter
  def thickness(self, value):
    """
    """
    self._thickness = value

"""
"""

class Material:
  """
  Material class
  """
    self._conductivity = None
    self._specific_heat = None
    self._density = None
    self._solar_absorptance = None
    self._thermal_absorptance = None
    self._visible_absorptance = None
    self._no_mass = False
    self._thermal_resistance = None

  @property
  def conductivity(self):
    """
     Get material conductivity in W/mK
     :return: float
    """
    return self._conductivity

  @conductivity.setter
  def conductivity(self, value):
    """
    Set material conductivity in W/mK
    """
    self._conductivity = value

  @property
  def specific_heat(self):
    """
    Get material conductivity in J/kgK
    """
    return self._specific_heat

  @specific_heat.setter
  def specific_heat(self, value):
    """
    """
    self._specific_heat = value

  @property
  def density(self):
    """
    Get material density in kg/m3
    """
    return self._density

  @density.setter
  def density(self, value):
    """
    Set material density in kg/m3
    """
    self._density = value

  @property
  def solar_absorptance(self):
    """
    Get material solar absorptance
    """
    return self._solar_absorptance

  @solar_absorptance.setter
  def solar_absorptance(self, value):
    """
    Set material solar absorptance
    """
    self._solar_absorptance = value

  @property
  def thermal_absorptance(self):
    """
    Get material thermal absorptance
    """
    return self._thermal_absorptance

  @thermal_absorptance.setter
  def thermal_absorptance(self, value):
    """
    Set material thermal absorptance
    """
    self._thermal_absorptance = value

  @property
  def visible_absorptance(self):
    """
    Get material visible absorptance
    """
    return self._visible_absorptance

  @visible_absorptance.setter
  def visible_absorptance(self, value):
    """
    Set material visible absorptance
    """
    self._visible_absorptance = value

  @property
  def no_mass(self):
    """
    Get material no mass flag
    """
    return self._no_mass

  @no_mass.setter
  def no_mass(self, value):
    """
    Set material no mass flag
    """
    self._no_mass = value

  @property
  def thermal_resistance(self):
    """
    Get material thermal resistance in m2K/W
    """
    return self._thermal_resistance

  @thermal_resistance.setter
  def thermal_resistance(self, value):
    """
    Set material thermal resistance in m2K/W
    """
    self._thermal_resistance = value

"""
"""
from trimesh import Trimesh

from helpers.geometry_helper import GeometryHelper

class Polyhedron:
  """
  Polyhedron class
  """
  def __init__(self, surfaces):
    self._surfaces = list(surfaces)
    self._polygons = [s.polygon for s in surfaces]
    self._polyhedron = None
    self._volume = None
    self._faces = None
    self._vertices = None
    self._mesh = None
    self._geometry = GeometryHelper()

  def _position_of(self, point):
    vertices = self.vertices
    for i in range(len(vertices)):
      if self._geometry.almost_equal(vertices[i], point):
        return i
    return -1

  @property
  def vertices(self) -> np.ndarray:
    """
    Polyhedron vertices
    :return: np.ndarray(int)
    """
    if self._vertices is None:
      vertices, self._vertices = [], []
      _ = [vertices.extend(s.points) for s in self._surfaces]
      for vertex_1 in vertices:
        found = False
        for vertex_2 in self._vertices:
          found = False
          if self._geometry.almost_equal(vertex_1, vertex_2):
            found = True
            break
        if not found:
          self._vertices.append(vertex_1)
      self._vertices = np.asarray(self._vertices)
    return self._vertices

  @property
  def faces(self) -> np.ndarray:
    """
    Polyhedron faces
    :return: np.ndarray([int])
    """
    if self._faces is None:
      self._faces = []
      for surface in self._surfaces:
        face = []
        points = surface.points
        for point in points:
          face.append(self._position_of(point))
        self._faces.append(face)
      self._faces = np.asarray(self._faces)
    return self._faces

  @property
  def _polyhedron_mesh(self):
    if self._mesh is None:
      self._mesh = Trimesh(vertices=np.asarray(self.vertices), faces=np.asarray(self.faces))
      if not self._mesh.is_volume:
        print('The geometry is not a closed volume')
        return None
    return self._mesh

  @property
    """
    Polyhedron volume in cubic meters
    """
    if self._volume is None:
      self._volume = self._polyhedron_mesh.volume
    return self._volume

  @property
  def max_z(self):
    """
    Polyhedron maximal z value
    """
    bounds = self._polyhedron_mesh.bounds
    z_max = max(bounds[:, 2])
    return z_max

  def export(self, full_path):
    """
    Export the polyhedron to stl given file
    :param full_path: str
    """
    self._polyhedron_mesh.export(full_path)

"""
"""
from __future__ import annotations

from typing import Union

import pyny3d.geoms as pn

class Surface:
  """
  Surface class
  """
  def __init__(self, coordinates, surface_type=None, name=None, swr='0.2', remove_last=True, is_projected=False):
    self._coordinates = coordinates
    self._type = surface_type
    self._swr = swr
    self._remove_last = remove_last
    self._is_projected = is_projected
    self._geometry_helper = GeometryHelper()
    self._polygon = None
    self._ground_polygon = None
    self._area = None
    self._points = None
    self._ground_points = None
    self._points_list = None
    self._normal = None
    self._azimuth = None
    self._inclination = None
    self._area_above_ground = None
    self._area_below_ground = None
    self._parent = None
    self._shapely = None
    self._projected_surface = None
    self._min_x = None
    self._min_y = None
    self._min_z = None
    self._shared_surfaces = []
    self._global_irradiance_hour = np.zeros(8760)
    self._global_irradiance_month = np.zeros(12)
    self._ground_coordinates = (self.min_x, self.min_y, self.min_z)

  def parent(self, parent, surface_id):
    """
    Assign a city object as surface parent and a surface id
    :param parent:  CityObject
    :param surface_id: str
    """
    self._parent = parent
    self._name = str(surface_id)

  @property
    """
    Surface name
    """
      raise Exception('surface has no name')

  @property
  def swr(self):
    """
    Get surface short wave reflectance
    """
    return self._swr

  @swr.setter
  def swr(self, value):
    """
    Set surface short wave reflectance
    """
    self._swr = value

  @property
  def points(self) -> np.ndarray:
    """
    Surface point matrix
    :return: np.ndarray
    """
    if self._points is None:
      self._points = np.fromstring(self._coordinates, dtype=float, sep=' ')
      self._points = GeometryHelper.to_points_matrix(self._points, self._remove_last)
    return self._points

  def _min_coord(self, axis):
    if axis == 'x':
      axis = 0
    elif axis == 'y':
      axis = 1
    else:
      axis = 2
    min_coordinate = ''
    for point in self.points:
      if min_coordinate == '':
        min_coordinate = point[axis]
      elif min_coordinate > point[axis]:
    return min_coordinate

  @property
  def min_x(self):
    """
    Surface minimal x value
    """
    if self._min_x is None:
      self._min_x = self._min_coord('x')
    return self._min_x

  @property
  def min_y(self):
    """
    Surface minimal y value
    """
    if self._min_y is None:
      self._min_y = self._min_coord('y')
    return self._min_y

  @property
  def min_z(self):
    """
    Surface minimal z value
    """
    if self._min_z is None:
      self._min_z = self._min_coord('z')
    return self._min_z

  @property
  def ground_points(self) -> np.ndarray:
    """
    Surface grounded points matrix
    """
    if self._ground_points is None:
      coordinates = ''
      for point in self.points:
        x = point[0] - self._ground_coordinates[0]
        y = point[1] - self._ground_coordinates[1]
        z = point[2] - self._ground_coordinates[2]
        if coordinates != '':
          coordinates = coordinates + ' '
        coordinates = coordinates + str(x) + ' ' + str(y) + ' ' + str(z)
      self._ground_points = np.fromstring(coordinates, dtype=float, sep=' ')
      self._ground_points = GeometryHelper.to_points_matrix(self._ground_points, False)
    return self._ground_points

  @property
  def points_list(self) -> np.ndarray:
    """
    Surface point list
    """
    if self._points_list is None:
      s = self.points
      self._points_list = np.reshape(s, len(s) * 3)
    return self._points_list

  @property
  def polygon(self) -> Union[pn.Polygon, None]:
    """
    Surface polygon
    :return: None or pyny3d.Polygon
    """
    if self._polygon is None:
      try:
        self._polygon = pn.Polygon(self.points)
      except ValueError:
        # is not really a polygon but a line so just return none
        self._polygon = None
    return self._polygon

  @property
  def ground_polygon(self) -> Union[pn.Polygon, None]:
    """
    Surface grounded polygon
    """
    if self._ground_polygon is None:
      try:
        self._ground_polygon = pn.Polygon(self.ground_points)
        self._ground_polygon = None
    return self._ground_polygon

  @property
  def area(self):
    """
    Surface area in square meters
    """
    if self._area is None:
      self._area = self.polygon.get_area()
    return self._area

  def _is_almost_same_terrain(self, terrain_points, ground_points):
    equal = 0
    for terrain_point in terrain_points:
      for ground_point in ground_points:
        if self._geometry_helper.almost_equal(terrain_point, ground_point):
          equal += 1
    return equal == len(terrain_points)

  @property
  def _is_terrain(self):
    for t_points in self._parent.terrains:
      if len(t_points) == len(self.points) and self._is_almost_same_terrain(t_points, self.points):
        return True
    return False

  @property
  def area_above_ground(self):
    """
    Surface area above ground in square meters
    """
    if self._area_above_ground is None:
      self._area_above_ground = self.area - self.area_below_ground
    return self._area_above_ground

  @property
  def area_below_ground(self):
    """
    Surface area below ground in square meters
    """
    if self._area_below_ground is None:
      self._area_below_ground = 0.0
      if self._is_terrain:
        self._area_below_ground = self.area
    return self._area_below_ground

  @property
  def normal(self) -> np.ndarray:
    """
    Surface normal vector
    """
    if self._normal is None:
      points = self.points
      cross_product = np.cross(points[1] - points[0], points[2] - points[0])
      self._normal = cross_product / np.linalg.norm(cross_product)
    return self._normal

  @property
  def azimuth(self):
    """
    Surface azimuth in radians
    """
    if self._azimuth is None:
      normal = self.normal
      self._azimuth = np.arctan2(normal[1], normal[0])
    return self._azimuth

  @property
  def inclination(self):
    """
    Surface inclination in radians
    """
    if self._inclination is None:
      self._inclination = np.arccos(self.normal[2])
    return self._inclination

  @property
    """
    Surface type Ground, Wall or Roof
    """
    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'

  def add_shared(self, surface, intersection_area):
    """
    Add a given surface and shared area in percent to this surface.
    :param surface:
    :param intersection_area:
    :return:
    """
    percent = intersection_area / self.area
    self._shared_surfaces.append((percent, surface))

  def shared(self, surface):
    """
    Check if given surface share some area with this surface
    :param surface: Surface
    """
    if self.type != 'Wall' or surface.type != 'Wall':
      return
    if self._geometry_helper.is_almost_same_surface(self, surface):
      intersection_area = self.intersect(surface).area
      self.add_shared(surface, intersection_area)
      surface.add_shared(self, intersection_area)

  @property
  def global_irradiance_hour(self):
    """
    Get surface global irradiance hour in Wh/m2
    """
    return self._global_irradiance_hour

  @global_irradiance_hour.setter
  def global_irradiance_hour(self, value):
    """
    Set surface global irradiance per hour in Wh/m2
    """
    self._global_irradiance_hour = value

  @property
  def global_irradiance_month(self):
    """
    Get surface global irradiance per month in Wh/m2
    """
    return self._global_irradiance_month

  @global_irradiance_month.setter
  def global_irradiance_month(self, value):
    """
    Set surface global irradiance per month in Wh/m2
    """
    self._global_irradiance_month = value

  @property
  def shapely(self) -> Union[None, pn.Polygon]:
    """
    Surface shapely (Z projection)
    """
    if self.polygon is None:
      return None
    if self._shapely is None:
      self._shapely = self.polygon.get_shapely()
    return self._shapely

  def _polygon_to_surface(polygon) -> Surface:
    coordinates = ''
    for coordinate in polygon.exterior.coords:
      if coordinates != '':
        coordinates = coordinates + ' '
      coordinates = coordinates + str(coordinate[0]) + ' ' + str(coordinate[1]) + ' 0.0'
    return Surface(coordinates, remove_last=False)

  @property
  def projection(self) -> Surface:
    """
    Projected surface (Z projection)
    """
    if self._is_projected:
      return self
    if self._projected_surface is None:
      self._projected_surface = self._polygon_to_surface(self.shapely)
    return self._projected_surface

  def intersect(self, surface) -> Union[Surface, None]:
    """
    Get the intersection surface, if any, between the given surface and this surface
    """
    min_x = min(self.min_x, surface.min_x)
    min_y = min(self.min_y, surface.min_y)
    min_z = min(self.min_z, surface.min_z)
    self._ground_coordinates = (min_x, min_y, min_z)
    surface._ground_coordinates = (min_x, min_y, min_z)
    origin = (0, 0, 0)
    azimuth = self.azimuth - (np.pi / 2)
    while azimuth < 0:
      azimuth += (np.pi / 2)
    inclination = self.inclination - np.pi
    while inclination < 0:
      inclination += np.pi
    polygon1 = self.ground_polygon.rotate(azimuth, 'z', origin).rotate(inclination, 'x', origin)
    polygon2 = surface.ground_polygon.rotate(azimuth, 'z', origin).rotate(inclination, 'x', origin)
    try:
      intersection = pn.Surface([polygon1]).intersect_with(polygon2)
      if len(intersection) == 0:
      for coordinate in pn.Surface([polygon1]).intersect_with(polygon2)[0]:
        coordinates = coordinates + str(coordinate[0]) + ' ' + str(coordinate[1]) + ' 0.0'
      if coordinates == '':
      intersect_surface = Surface(coordinates, remove_last=False)
      if intersect_surface.polygon is None:
      return Surface(coordinates, remove_last=False)
    except Exception as err:
      print('Error', err)

"""
"""
from typing import List

from city_model_structure.layer import Layer
from city_model_structure.thermal_opening import ThermalOpening
from helpers.configuration_helper import ConfigurationHelper

class ThermalBoundary:
  """
  ThermalBoundary class
  """
  def __init__(self, surface, delimits):
    self._surface = surface
    self._delimits = delimits
    # ToDo: up to at least LOD2 will be just one thermal opening per Thermal boundary, review for LOD3 and LOD4
    self._thermal_openings = [ThermalOpening()]
    self._layers = None
    self._outside_solar_absorptance = ConfigurationHelper().outside_solar_absorptance
    self._outside_thermal_absorptance = None
    self._outside_visible_absorptance = None
    self._window_ratio = None
    self._u_value = None
    self._window_area = None
    self._shortwave_reflectance = 1 - self._outside_solar_absorptance

  @property
  def delimits(self) -> List[ThermalZone]:
    """
    Get the thermal zones delimited by the thermal boundary
    """
    return self._delimits

  @property
    """
    Thermal boundary azimuth in radians
    """
    return self._surface.azimuth

  @property
    """
    Thermal boundary inclination in radians
    """
    return self._surface.inclination

  @property
    """
    Thermal boundary area in square meters
    """
    return self._surface.area

  @property
    """
    Thermal boundary area above ground in square meters
    """
    return self._surface.area_above_ground

  @property
    """
    Thermal boundary area below ground in square meters
    """
    return self._surface.area_below_ground

  @property
  def outside_solar_absorptance(self):
    """
    Get thermal boundary outside solar absorptance
    """
    return self._outside_solar_absorptance

  @outside_solar_absorptance.setter
  def outside_solar_absorptance(self, value):
    """
    Set thermal boundary outside solar absorptance
    """
    self._outside_solar_absorptance = value
    self._shortwave_reflectance = 1.0 - float(value)

  @property
  def outside_thermal_absorptance(self):
    """
    Get thermal boundary outside thermal absorptance
    """
    return self._outside_thermal_absorptance

  @outside_thermal_absorptance.setter
  def outside_thermal_absorptance(self, value):
    """
    Set thermal boundary outside thermal absorptance
    """
    self._outside_thermal_absorptance = value

  @property
  def outside_visible_absorptance(self):
    """
    Get thermal boundary outside visible absorptance
    """
    return self._outside_visible_absorptance

  @outside_visible_absorptance.setter
  def outside_visible_absorptance(self, value):
    """
    Set thermal boundary outside visible absorptance
    """
    self._outside_visible_absorptance = value

  @property
  def thermal_openings(self) -> List[ThermalOpening]:
    """
    Get thermal boundary thermal openings
    :return: [ThermalOpening]
    """
    return self._thermal_openings

  @thermal_openings.setter
  def thermal_openings(self, value):
    """
    Set thermal boundary thermal openings
    :param value: [ThermalOpening]
    """
    self._thermal_openings = value

  @property
  def layers(self) -> List[Layer]:
    """
    Get thermal boundary layers
    :return: [Layers]
    """
    return self._layers

  @layers.setter
  def layers(self, value):
    """
    Set thermal boundary layers
    :param value: [Layer]
    """
    self._layers = value

  @property
    """
    Thermal boundary surface type
    """
    return self._surface.type

  @property
  def window_ratio(self):
    """
    Get thermal boundary window ratio
    """
    return self._window_ratio

  @window_ratio.setter
  def window_ratio(self, value):
    """
    Set thermal boundary window ratio
    """
    self._window_ratio = value

  @property
  def window_area(self):
    """
    Thermal boundary window area in square meters
    """
    if self._window_area is None:
      try:
        self._window_area = float(self._surface.area) * float(self.window_ratio)
      except TypeError:
        raise Exception('Window ratio is not defined or invalid surface area')
    return self._window_area

  @property
  def u_value(self):
    """
    Thermal boundary u value in W/m2K
    internal and external convective coefficient in W/m2K values, can be configured at configuration.ini
    """
    if self._u_value is None:
      h_i = ConfigurationHelper().h_i
      h_e = ConfigurationHelper().h_e
      r_value = 1.0/h_i + 1.0/h_e
      try:
        for layer in self.layers:
          if layer.material.no_mass:
            r_value += float(layer.material.thermal_resistance)
          else:
            r_value = r_value + float(layer.material.conductivity) / float(layer.thickness)
        self._u_value = 1.0/r_value
        raise Exception('Constructions layers are not initialized')
    return self._u_value

  @property
  def shortwave_reflectance(self):
    """
    Get thermal boundary shortwave reflectance
    """
    return self._shortwave_reflectance

  @shortwave_reflectance.setter
  def shortwave_reflectance(self, value):
    """
    Set thermal boundary shortwave reflectance
    :return:
    """
    self._shortwave_reflectance = value
    self._outside_solar_absorptance = 1.0 - float(value)

"""
"""

class ThermalOpening:
  """
  ThermalOpening class
  """
    self._openable_ratio = None
    self._frame_ratio = ConfigurationHelper().frame_ratio
    self._g_value = None
    self._front_side_solar_transmittance_at_normal_incidence = None
    self._back_side_solar_transmittance_at_normal_incidence = None
    self._overall_u_value = None

  @property
  def openable_ratio(self):
    """
    Get thermal opening openable ratio, NOT IMPLEMENTED
    :return: Exception
    """
    raise NotImplementedError()

  @openable_ratio.setter
  def openable_ratio(self, value):
    """
    Set thermal opening openable ratio, NOT IMPLEMENTED
    :param value: Any
    """

  @property
    """
    Get thermal opening conductivity in W/mK
    """

    """
    """
    # The code to calculate overall_u_value is duplicated here and in thickness_m.
    # This ensures a more robust code that returns the overall_u_value regardless the order the parameters are read.
    if self._overall_u_value is None and self.thickness is not None:
      r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self.thickness)
      self._overall_u_value = 1 / r_value

  @property
  def frame_ratio(self):
    """
    Get thermal opening frame ratio
    """
    return self._frame_ratio

  @frame_ratio.setter
  def frame_ratio(self, value):
    """
    Set thermal opening frame ratio
    """
    self._frame_ratio = value

  @property
  def g_value(self):
    """
    Get thermal opening g value
    """
    return self._g_value

  @g_value.setter
  def g_value(self, value):
    """
    Set thermal opening g value
    :param value:
    :return:
    """
    self._g_value = value

  @property
    """
    Get thermal opening thickness in meters
    :return:
    """

    """
    Set thermal opening thickness in meters
    """
    # The code to calculate overall_u_value is duplicated here and in conductivity.
    if self._overall_u_value is None and self.conductivity is not None:

  @property
  def front_side_solar_transmittance_at_normal_incidence(self):
    """
    Get thermal opening front side solar transmittance at normal incidence
    """
    return self._front_side_solar_transmittance_at_normal_incidence

  @front_side_solar_transmittance_at_normal_incidence.setter
  def front_side_solar_transmittance_at_normal_incidence(self, value):
    """
    Set thermal opening front side solar transmittance at normal incidence
    """
    self._front_side_solar_transmittance_at_normal_incidence = value

  @property
  def back_side_solar_transmittance_at_normal_incidence(self):
    """
    Get thermal opening back side solar transmittance at normal incidence
    """
    return self._back_side_solar_transmittance_at_normal_incidence

  @back_side_solar_transmittance_at_normal_incidence.setter
  def back_side_solar_transmittance_at_normal_incidence(self, value):
    """
    Set thermal opening back side solar transmittance at normal incidence
    """
    self._back_side_solar_transmittance_at_normal_incidence = value

  @property
  def overall_u_value(self):
    """
    Get thermal opening overall u value in W/m2K
    """
    return self._overall_u_value

  @overall_u_value.setter
  def overall_u_value(self, value):
    """
    """
    self._overall_u_value = value

"""
"""
from typing import List, TypeVar

ThermalBoundary = TypeVar('ThermalBoundary')

class ThermalZone:
  """
  ThermalZone class
  """
    self._floor_area = None
    self._bounded = None
    self._heated = ConfigurationHelper().heated
    self._cooled = ConfigurationHelper().cooled
    self._additional_thermal_bridge_u_value = ConfigurationHelper().additional_thermal_bridge_u_value
    self._effective_thermal_capacity = None
    self._indirectly_heated_area_ratio = ConfigurationHelper().indirectly_heated_area_ratio
    self._infiltration_rate_system_on = ConfigurationHelper().infiltration_rate_system_on
    self._infiltration_rate_system_off = None
    self._usage_zones = None

  @property
  def heated(self):
    """
    Get thermal zone heated flag
    """
    return self._heated

  @property
  def cooled(self):
    """
    Get thermal zone cooled flag
    """
    return self._cooled

  @property
  def floor_area(self):
    """
    Get thermal zone floor area in square meters
    """
    if self._floor_area is None:
      self._floor_area = 0
      for s in self._surfaces:
        if s.type == 'Ground':
          self._floor_area += s.area
    return self._floor_area

  @property
  def bounded(self) -> List[ThermalBoundary]:
    """
    Get thermal boundaries bounding with the thermal zone
    :return: [ThermalBoundary]
    """
    return self._bounded

  @bounded.setter
  def bounded(self, value):
    """
    Set thermal boundaries bounding with the thermal zone
    :param value: [ThermalBoundary]
    """
    self._bounded = value

  @property
    # todo: This property should be erased
    """
    Get thermal zone surfaces
    """

  @property
  def additional_thermal_bridge_u_value(self):
    """
    Get thermal zone additional thermal bridge u value W/m2K
    """
    return self._additional_thermal_bridge_u_value

  @additional_thermal_bridge_u_value.setter
  def additional_thermal_bridge_u_value(self, value):
    """
    Set thermal zone additional thermal bridge u value W/m2K
    """
    self._additional_thermal_bridge_u_value = value

  @property
  def effective_thermal_capacity(self):
    """
    Get thermal zone effective thermal capacity
    """
    return self._effective_thermal_capacity

  @effective_thermal_capacity.setter
  def effective_thermal_capacity(self, value):
    """
    Set thermal zone effective thermal capacity
    """
    self._effective_thermal_capacity = value

  @property
  def indirectly_heated_area_ratio(self):
    """
    Get thermal zone indirectly heated area ratio
    """
    return self._indirectly_heated_area_ratio

  @indirectly_heated_area_ratio.setter
  def indirectly_heated_area_ratio(self, value):
    """
    Set thermal zone indirectly heated area ratio
    """
    self._indirectly_heated_area_ratio = value

  @property
  def infiltration_rate_system_on(self):
    """
    Get thermal zone infiltration rate system on in air changes per hour
    """
    return self._infiltration_rate_system_on

  @infiltration_rate_system_on.setter
  def infiltration_rate_system_on(self, value):
    """
    Set thermal zone infiltration rate system on in air changes per hour
    """
    self._infiltration_rate_system_on = value

  @property
  def infiltration_rate_system_off(self):
    """
    Get thermal zone infiltration rate system off in air changes per hour
    """
    return self._infiltration_rate_system_off

  @infiltration_rate_system_off.setter
  def infiltration_rate_system_off(self, value):
    """
    """
    self._infiltration_rate_system_off = value

  @property
    """
    Get thermal zone usage zones
    """

    """
    Set thermal zone usage zones
    :param values: [UsageZone]
    """

"""
"""

from city_model_structure.internal_gains import InternalGains

class UsageZone:
  """
  UsageZone class
  """
    self._usage = None
    self._internal_gains = None
    self._heating_setpoint = None
    self._heating_setback = None
    self._cooling_setpoint = None
    self._hours_day = None
    self._days_year = None
    # todo: this must come from library, talk to Rabeeh
    self._mechanical_air_change = ConfigurationHelper().min_air_change

  @property
  def internal_gains(self) -> List[InternalGains]:
    """
    Get usage zone internal gains
    :return: [InternalGains]
    """
    return self._internal_gains

  @internal_gains.setter
  def internal_gains(self, value):
    """
    Set usage zone internal gains
    :param value: [InternalGains]
    """
    self._internal_gains = value

  @property
  def heating_setpoint(self):
    """
    Get usage zone heating set point in celsius grads
    """
    return self._heating_setpoint

  @heating_setpoint.setter
  def heating_setpoint(self, value):
    """
    Set usage zone heating set point in celsius grads
    """
    self._heating_setpoint = value

  @property
  def heating_setback(self):
    """
    Get usage zone heating setback in celsius grads
    """
    return self._heating_setback

  @heating_setback.setter
  def heating_setback(self, value):
    """
    Set usage zone heating setback in celsius grads
    """
    self._heating_setback = value

  @property
  def cooling_setpoint(self):
    """
    Get usage zone cooling setpoint in celsius grads
    """
    return self._cooling_setpoint

  @cooling_setpoint.setter
  def cooling_setpoint(self, value):
    """
    Set usage zone cooling setpoint in celsius grads
    """
    self._cooling_setpoint = value

  @property
  def hours_day(self):
    """
    Get usage zone usage hours per day
    """
    return self._hours_day

  @hours_day.setter
  def hours_day(self, value):
    """
    Set usage zone usage hours per day
    """
    self._hours_day = value

  @property
  def days_year(self):
    """
    Get usage zone usage days per year
    """
    return self._days_year

  @days_year.setter
  def days_year(self, value):
    """
    Set usage zone usage days per year
    """
    self._days_year = value

  @property
  def mechanical_air_change(self):
    """
    Set usage zone mechanical air change in air change per hour
    """
    return self._mechanical_air_change

  @mechanical_air_change.setter
  def mechanical_air_change(self, value):
    """
    Get usage zone mechanical air change in air change per hour
    """
    self._mechanical_air_change = value

  @property
  def usage(self):
    """
    Get usage zone usage
    """
    return self._usage

  @usage.setter
  def usage(self, value):
    """
    :param value: str
    """
    self._usage = value

"""
"""
from city_model_structure.city import City
from geometry.geometry_feeders.city_gml import CityGml

class GeometryFactory:
  """
  GeometryFactory class
  """
  def __init__(self, file_type, path):
    self._file_type = '_' + file_type.lower()
    self._path = path

  @property
  def _citygml(self):
    return CityGml(self._path).city

  @property
  def _geojson(self):
    raise Exception('Not implemented')

  @property
  def _bim(self):

  @property
  def city(self) -> City:
    """
    Load  the city model structure from a geometry source
    :return: City
    """
    return getattr(self, self._file_type, lambda: None)

"""
"""
from physics.physics_feeders.us_new_york_city_physics_parameters import UsNewYorkCityPhysicsParameters
from physics.physics_feeders.us_physics_parameters import UsPhysicsParameters

class PhysicsFactory:
  """
  PhysicsFactor class
  """
  def __init__(self, handler, city, base_path='data/physics'):
    self._handler = '_' + handler.lower().replace(' ', '_')
    self._city = city
    self._base_path = base_path
    self.factory()

  def _us_new_york_city(self):
    UsNewYorkCityPhysicsParameters(self._city, self._base_path)

  def _us(self):
    UsPhysicsParameters(self._city, self._base_path)

  def _ca(self):

  def _de(self):

  def _es(self):

  def factory(self):
    """
    Enrich the city with the physics information
    """
    getattr(self, self._handler, lambda: None)()

"""
"""
from usage.usage_feeders.de_usage_parameters import DeUsageParameters
from usage.usage_feeders.us_new_york_city_usage_parameters import UsNewYorkCityUsageParameters

class UsageFactory:
  """
  UsageFactory class
  """
  def __init__(self, handler, city):

    UsNewYorkCityUsageParameters(self._city)

    DeUsageParameters(self._city)

    """
    Enrich the city with the usage information
    """