Merge remote-tracking branch 'origin/master'

This commit is contained in:
pilar 2020-06-11 15:46:50 -04:00
commit 2385827d0a
17 changed files with 458 additions and 61 deletions

View File

@ -4,10 +4,10 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from __future__ import annotations from __future__ import annotations
from typing import Union
import numpy as np import numpy as np
import pyny3d.geoms as pn import pyny3d.geoms as pn
from helpers.geometry import Geometry from helpers.geometry import Geometry
from typing import Union
class Surface: class Surface:
@ -207,9 +207,9 @@ class Surface:
def _is_almost_same_terrain(self, terrain_points, ground_points): def _is_almost_same_terrain(self, terrain_points, ground_points):
equal = 0 equal = 0
for t in terrain_points: for terrain_point in terrain_points:
for g in ground_points: for ground_point in ground_points:
if self._geometry.almost_equal(t, g): if self._geometry.almost_equal(terrain_point, ground_point):
equal += 1 equal += 1
return equal == len(terrain_points) return equal == len(terrain_points)
@ -250,8 +250,8 @@ class Surface:
""" """
if self._normal is None: if self._normal is None:
points = self.points points = self.points
n = np.cross(points[1] - points[0], points[2] - points[0]) cross_product = np.cross(points[1] - points[0], points[2] - points[0])
self._normal = n / np.linalg.norm(n) self._normal = cross_product / np.linalg.norm(cross_product)
return self._normal return self._normal
@property @property
@ -283,7 +283,7 @@ class Surface:
""" """
if self._type is None: if self._type is None:
grad = np.rad2deg(self.inclination) grad = np.rad2deg(self.inclination)
if 170 <= grad: if grad >= 170:
self._type = 'Ground' self._type = 'Ground'
elif 80 <= grad <= 100: elif 80 <= grad <= 100:
self._type = 'Wall' self._type = 'Wall'
@ -307,7 +307,7 @@ class Surface:
:param surface: Surface :param surface: Surface
:return: None :return: None
""" """
if self.type is not 'Wall' or surface.type is not 'Wall': if self.type != 'Wall' or surface.type != 'Wall':
return return
if self._geometry.is_almost_same_surface(self, surface): if self._geometry.is_almost_same_surface(self, surface):
intersection_area = self.intersect(surface).area intersection_area = self.intersect(surface).area

View File

@ -3,14 +3,17 @@ ThermalBoundary module
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from typing import List
from city_model_structure.thermal_opening import ThermalOpening from city_model_structure.thermal_opening import ThermalOpening
from city_model_structure.thermal_zone import ThermalZone from city_model_structure.thermal_zone import ThermalZone
from city_model_structure.layer import Layer from city_model_structure.layer import Layer
from helpers.configuration import Configuration from helpers.configuration import Configuration
from typing import List
class ThermalBoundary: class ThermalBoundary:
"""
ThermalBoundary class
"""
def __init__(self, surface, delimits): def __init__(self, surface, delimits):
self._surface = surface self._surface = surface
self._delimits = delimits self._delimits = delimits
@ -27,83 +30,169 @@ class ThermalBoundary:
@property @property
def delimits(self) -> List[ThermalZone]: def delimits(self) -> List[ThermalZone]:
"""
Get the thermal zones delimited by the thermal boundary
:return: [ThermalZone]
"""
return self._delimits return self._delimits
@property @property
def azimuth(self): def azimuth(self):
"""
Thermal boundary azimuth in radians
:return: float
"""
return self._surface.azimuth return self._surface.azimuth
@property @property
def inclination(self): def inclination(self):
"""
Thermal boundary inclination in radians
:return: float
"""
return self._surface.inclination return self._surface.inclination
@property @property
def area(self): def area(self):
"""
Thermal boundary area in square meters
:return: float
"""
return self._surface.area return self._surface.area
@property @property
def area_above_ground(self): def area_above_ground(self):
"""
Thermal boundary area above ground in square meters
:return: float
"""
return self._surface.area_above_ground return self._surface.area_above_ground
@property @property
def area_below_ground(self): def area_below_ground(self):
"""
Thermal boundary area below ground in square meters
:return: float
"""
return self._surface.area_below_ground return self._surface.area_below_ground
@property @property
def outside_solar_absorptance(self): def outside_solar_absorptance(self):
"""
Get thermal boundary outside solar absorptance
:return: float
"""
return self._outside_solar_absorptance return self._outside_solar_absorptance
@outside_solar_absorptance.setter @outside_solar_absorptance.setter
def outside_solar_absorptance(self, value): def outside_solar_absorptance(self, value):
"""
Set thermal boundary outside solar absorptance
:param value: float
:return: None
"""
self._outside_solar_absorptance = value self._outside_solar_absorptance = value
self._shortwave_reflectance = 1.0 - float(value) self._shortwave_reflectance = 1.0 - float(value)
@property @property
def outside_thermal_absorptance(self): def outside_thermal_absorptance(self):
"""
Get thermal boundary outside thermal absorptance
:return: float
"""
return self._outside_thermal_absorptance return self._outside_thermal_absorptance
@outside_thermal_absorptance.setter @outside_thermal_absorptance.setter
def outside_thermal_absorptance(self, value): def outside_thermal_absorptance(self, value):
"""
Set thermal boundary outside thermal absorptance
:param value: float
:return: None
"""
self._outside_thermal_absorptance = value self._outside_thermal_absorptance = value
@property @property
def outside_visible_absorptance(self): def outside_visible_absorptance(self):
"""
Get thermal boundary outside visible absorptance
:return: float
"""
return self._outside_visible_absorptance return self._outside_visible_absorptance
@outside_visible_absorptance.setter @outside_visible_absorptance.setter
def outside_visible_absorptance(self, value): def outside_visible_absorptance(self, value):
"""
Set thermal boundary outside visible absorptance
:param value: float
:return: None
"""
self._outside_visible_absorptance = value self._outside_visible_absorptance = value
@property @property
def thermal_openings(self) -> List[ThermalOpening]: def thermal_openings(self) -> List[ThermalOpening]:
"""
Get thermal boundary thermal openings
:return: [ThermalOpening]
"""
return self._thermal_openings return self._thermal_openings
@thermal_openings.setter @thermal_openings.setter
def thermal_openings(self, value): def thermal_openings(self, value):
"""
Set thermal boundary thermal openings
:param value: [ThermalOpening]
:return: None
"""
self._thermal_openings = value self._thermal_openings = value
@property @property
def layers(self) -> List[Layer]: def layers(self) -> List[Layer]:
"""
Get thermal boundary layers
:return: [Layers]
"""
return self._layers return self._layers
@layers.setter @layers.setter
def layers(self, value): def layers(self, value):
"""
Set thermal boundary layers
:param value: [Layer]
:return: None
"""
self._layers = value self._layers = value
@property @property
def type(self): def type(self):
"""
Thermal boundary surface type
:return: str
"""
return self._surface.type return self._surface.type
@property @property
def window_ratio(self): def window_ratio(self):
"""
Get thermal boundary window ratio
:return: float
"""
return self._window_ratio return self._window_ratio
@window_ratio.setter @window_ratio.setter
def window_ratio(self, value): def window_ratio(self, value):
"""
Set thermal boundary window ratio
:param value: float
:return: None
"""
self._window_ratio = value self._window_ratio = value
@property @property
def window_area(self): def window_area(self):
"""
Thermal boundary window area in square meters
:return: float
"""
if self._window_area is None: if self._window_area is None:
try: try:
self._window_area = float(self._surface.area) * float(self.window_ratio) self._window_area = float(self._surface.area) * float(self.window_ratio)
@ -113,6 +202,11 @@ class ThermalBoundary:
@property @property
def u_value(self): 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
:return: float
"""
if self._u_value is None: if self._u_value is None:
h_i = Configuration().h_i h_i = Configuration().h_i
h_e = Configuration().h_e h_e = Configuration().h_e
@ -130,9 +224,18 @@ class ThermalBoundary:
@property @property
def shortwave_reflectance(self): def shortwave_reflectance(self):
"""
Get thermal boundary shortwave reflectance
:return: float
"""
return self._shortwave_reflectance return self._shortwave_reflectance
@shortwave_reflectance.setter @shortwave_reflectance.setter
def shortwave_reflectance(self, value): def shortwave_reflectance(self, value):
"""
Set thermal boundary shortwave reflectance
:param value: float
:return:
"""
self._shortwave_reflectance = value self._shortwave_reflectance = value
self._outside_solar_absorptance = 1.0 - float(value) self._outside_solar_absorptance = 1.0 - float(value)

View File

@ -7,90 +7,165 @@ from helpers.configuration import Configuration
class ThermalOpening: class ThermalOpening:
"""
ThermalOpening class
"""
def __init__(self): def __init__(self):
self._openable_ratio = None self._openable_ratio = None
self._conductivity_w_mk = None self._conductivity = None
self._frame_ratio = Configuration().frame_ratio self._frame_ratio = Configuration().frame_ratio
self._g_value = None self._g_value = None
self._thickness_m = None self._thickness = None
self._front_side_solar_transmittance_at_normal_incidence = None self._front_side_solar_transmittance_at_normal_incidence = None
self._back_side_solar_transmittance_at_normal_incidence = None self._back_side_solar_transmittance_at_normal_incidence = None
self._overall_u_value = None self._overall_u_value = None
@property @property
def openable_ratio(self): def openable_ratio(self):
"""
Get thermal opening openable ratio, NOT IMPLEMENTED
:return: Exception
"""
raise Exception('Not implemented') raise Exception('Not implemented')
@openable_ratio.setter @openable_ratio.setter
def openable_ratio(self, value): def openable_ratio(self, value):
"""
Set thermal opening openable ratio, NOT IMPLEMENTED
:param value: Any
:return: Exception
"""
raise Exception('Not implemented') raise Exception('Not implemented')
@property @property
def conductivity_w_mk(self): def conductivity(self):
return self._conductivity_w_mk """
Get thermal opening conductivity in W/mK
:return: float
"""
return self._conductivity
@conductivity_w_mk.setter @conductivity.setter
def conductivity_w_mk(self, value): def conductivity(self, value):
"""
Get thermal opening conductivity in W/mK
:param value: float
:return: None
"""
# The code to calculate overall_u_value is duplicated here and in thickness_m. # 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. # This ensures a more robust code that returns the overall_u_value regardless the order the parameters are read.
self._conductivity_w_mk = value self._conductivity = value
if self._overall_u_value is None and self.thickness_m is not None: if self._overall_u_value is None and self.thickness is not None:
h_i = Configuration().h_i h_i = Configuration().h_i
h_e = Configuration().h_e h_e = Configuration().h_e
r_value = 1 / h_i + 1 / h_e + float(self.conductivity_w_mk) / float(self.thickness_m) r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self.thickness)
self._overall_u_value = 1 / r_value self._overall_u_value = 1 / r_value
@property @property
def frame_ratio(self): def frame_ratio(self):
"""
Get thermal opening frame ratio
:return: float
"""
return self._frame_ratio return self._frame_ratio
@frame_ratio.setter @frame_ratio.setter
def frame_ratio(self, value): def frame_ratio(self, value):
"""
Set thermal opening frame ratio
:param value: float
:return: None
"""
self._frame_ratio = value self._frame_ratio = value
@property @property
def g_value(self): def g_value(self):
"""
Get thermal opening g value
:return: float
"""
return self._g_value return self._g_value
@g_value.setter @g_value.setter
def g_value(self, value): def g_value(self, value):
"""
Set thermal opening g value
:param value:
:return:
"""
self._g_value = value self._g_value = value
@property @property
def thickness_m(self): def thickness(self):
return self._thickness_m """
Get thermal opening thickness in meters
:return:
"""
return self._thickness
@thickness_m.setter @thickness.setter
def thickness_m(self, value): def thickness(self, value):
# The code to calculate overall_u_value is duplicated here and in conductivity_w_mk. """
Set thermal opening thickness in meters
:param value: float
:return: None
"""
# The code to calculate overall_u_value is duplicated here and in conductivity.
# This ensures a more robust code that returns the overall_u_value regardless the order the parameters are read. # This ensures a more robust code that returns the overall_u_value regardless the order the parameters are read.
self._thickness_m = value self._thickness = value
if self._overall_u_value is None and self.conductivity_w_mk is not None: if self._overall_u_value is None and self.conductivity is not None:
h_i = Configuration().h_i h_i = Configuration().h_i
h_e = Configuration().h_e h_e = Configuration().h_e
r_value = 1 / h_i + 1 / h_e + float(self.conductivity_w_mk) / float(self.thickness_m) r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self.thickness)
self._overall_u_value = 1 / r_value self._overall_u_value = 1 / r_value
@property @property
def front_side_solar_transmittance_at_normal_incidence(self): def front_side_solar_transmittance_at_normal_incidence(self):
"""
Get thermal opening front side solar transmittance at normal incidence
:return: float
"""
return self._front_side_solar_transmittance_at_normal_incidence return self._front_side_solar_transmittance_at_normal_incidence
@front_side_solar_transmittance_at_normal_incidence.setter @front_side_solar_transmittance_at_normal_incidence.setter
def front_side_solar_transmittance_at_normal_incidence(self, value): def front_side_solar_transmittance_at_normal_incidence(self, value):
"""
Set thermal opening front side solar transmittance at normal incidence
:param value: float
:return: None
"""
self._front_side_solar_transmittance_at_normal_incidence = value self._front_side_solar_transmittance_at_normal_incidence = value
@property @property
def back_side_solar_transmittance_at_normal_incidence(self): def back_side_solar_transmittance_at_normal_incidence(self):
"""
Get thermal opening back side solar transmittance at normal incidence
:return: float
"""
return self._back_side_solar_transmittance_at_normal_incidence return self._back_side_solar_transmittance_at_normal_incidence
@back_side_solar_transmittance_at_normal_incidence.setter @back_side_solar_transmittance_at_normal_incidence.setter
def back_side_solar_transmittance_at_normal_incidence(self, value): def back_side_solar_transmittance_at_normal_incidence(self, value):
"""
Set thermal opening back side solar transmittance at normal incidence
:param value: float
:return: None
"""
self._back_side_solar_transmittance_at_normal_incidence = value self._back_side_solar_transmittance_at_normal_incidence = value
@property @property
def overall_u_value(self): def overall_u_value(self):
"""
Get thermal opening overall u value
:return: float
"""
return self._overall_u_value return self._overall_u_value
@overall_u_value.setter @overall_u_value.setter
def overall_u_value(self, value): def overall_u_value(self, value):
"""
Get thermal opening overall u value
:param value: float
:return: None
"""
self._overall_u_value = value self._overall_u_value = value

View File

@ -3,8 +3,8 @@ UsageZone module
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from city_model_structure.internal_gains import InternalGains
from typing import List from typing import List
from city_model_structure.internal_gains import InternalGains
class UsageZone: class UsageZone:
@ -41,59 +41,118 @@ class UsageZone:
@property @property
def heating_setpoint(self): def heating_setpoint(self):
""" """
Get usage zone heating set point in celsius grads
:return: :return: float
""" """
return self._heating_setpoint return self._heating_setpoint
@heating_setpoint.setter @heating_setpoint.setter
def heating_setpoint(self, value): def heating_setpoint(self, value):
"""
Set usage zone heating set point in celsius grads
:param value: float
:return: None
"""
self._heating_setpoint = value self._heating_setpoint = value
@property @property
def heating_setback(self): def heating_setback(self):
"""
Get usage zone heating setback in celsius grads
:return: float
"""
return self._heating_setback return self._heating_setback
@heating_setback.setter @heating_setback.setter
def heating_setback(self, value): def heating_setback(self, value):
"""
Set usage zone heating setback in celsius grads
:param value: float
:return: None
"""
self._heating_setback = value self._heating_setback = value
@property @property
def cooling_setpoint(self): def cooling_setpoint(self):
"""
Get usage zone cooling setpoint in celsius grads
:return: float
"""
return self._cooling_setpoint return self._cooling_setpoint
@cooling_setpoint.setter @cooling_setpoint.setter
def cooling_setpoint(self, value): def cooling_setpoint(self, value):
"""
Set usage zone cooling setpoint in celsius grads
:param value: float
:return: None
"""
self._cooling_setpoint = value self._cooling_setpoint = value
@property @property
def hours_day(self): def hours_day(self):
"""
Get usage zone usage hours per day
:return: float
"""
return self._hours_day return self._hours_day
@hours_day.setter @hours_day.setter
def hours_day(self, value): def hours_day(self, value):
"""
Set usage zone usage hours per day
:param value: float
:return: float
"""
self._hours_day = value self._hours_day = value
@property @property
def days_year(self): def days_year(self):
"""
Get usage zone usage days per year
:return: float
"""
return self._days_year return self._days_year
@days_year.setter @days_year.setter
def days_year(self, value): def days_year(self, value):
"""
Set usage zone usage days per year
:param value: float
:return: None
"""
self._days_year = value self._days_year = value
@property @property
def mechanical_air_change(self): def mechanical_air_change(self):
"""
Set usage zone mechanical air change in air change per hour
:return: float
"""
return self._mechanical_air_change return self._mechanical_air_change
@mechanical_air_change.setter @mechanical_air_change.setter
def mechanical_air_change(self, value): def mechanical_air_change(self, value):
"""
Get usage zone mechanical air change in air change per hour
:param value: float
:return: None
"""
self._mechanical_air_change = value self._mechanical_air_change = value
@property @property
def usage(self): def usage(self):
"""
Get usage zone usage
:return: str
"""
return self._usage return self._usage
@usage.setter @usage.setter
def usage(self, value): def usage(self, value):
"""
Get usage zone usage
:param value: str
:return: None
"""
self._usage = value self._usage = value

View File

@ -9,21 +9,25 @@ from city_model_structure.city import City
class GeometryFactory: class GeometryFactory:
def __init__(self, file_type, path): def __init__(self, file_type, path):
self._file_type = file_type.lower() self._file_type = '_' + file_type.lower()
self._path = path self._path = path
@property @property
def citygml(self): def _citygml(self):
return CityGml(self._path).city return CityGml(self._path).city
@property @property
def geojson(self): def _geojson(self):
raise Exception('Not implemented') raise Exception('Not implemented')
@property @property
def bim(self): def _bim(self):
raise Exception('Not implemented') raise Exception('Not implemented')
@property @property
def city(self) -> City: def city(self) -> City:
"""
Geometry factory city model structure with geometry
:return: City
"""
return getattr(self, self._file_type, lambda: None) return getattr(self, self._file_type, lambda: None)

View File

@ -12,6 +12,9 @@ from helpers.geometry import Geometry
class CityGml: class CityGml:
"""
CityGml class
"""
def __init__(self, path): def __init__(self, path):
self._city = None self._city = None
with open(path) as gml: with open(path) as gml:
@ -42,10 +45,18 @@ class CityGml:
@property @property
def content(self): def content(self):
"""
CityGml raw content
:return: str
"""
return self._gml return self._gml
@property @property
def city(self): def city(self) -> City:
"""
City model structure enriched with the geometry information
:return: City
"""
if self._city is None: if self._city is None:
self._city = City(self._lower_corner, self._upper_corner, self._srs_name) self._city = City(self._lower_corner, self._upper_corner, self._srs_name)
for o in self._gml['CityModel']['cityObjectMember']: for o in self._gml['CityModel']['cityObjectMember']:

View File

@ -8,6 +8,9 @@ from pathlib import Path
class Configuration: class Configuration:
"""
Configuration class
"""
def __init__(self): def __init__(self):
base_path = Path().resolve().parent base_path = Path().resolve().parent
config_file = Path(base_path / 'libs/config/configuration.ini').resolve() config_file = Path(base_path / 'libs/config/configuration.ini').resolve()
@ -16,36 +19,73 @@ class Configuration:
@property @property
def h_i(self): def h_i(self):
"""
Configured internal convective coefficient in W/m2K
:return: float
"""
return self._config.getfloat('convective_fluxes', 'h_i') return self._config.getfloat('convective_fluxes', 'h_i')
@property @property
def h_e(self): def h_e(self):
"""
Configured external convective coefficient in W/m2K
:return: float
"""
return self._config.getfloat('convective_fluxes', 'h_e') return self._config.getfloat('convective_fluxes', 'h_e')
@property @property
def frame_ratio(self): def frame_ratio(self):
"""
Configured frame ratio
:return: float
"""
return self._config.getfloat('windows', 'frame_ratio') return self._config.getfloat('windows', 'frame_ratio')
@property @property
def heated(self): def heated(self):
"""
Configured heated flag
:return: Boolean
"""
return self._config.getboolean('thermal_zones', 'heated') return self._config.getboolean('thermal_zones', 'heated')
@property @property
def cooled(self): def cooled(self):
"""
Configured cooled flag
:return: Boolean
"""
return self._config.getboolean('thermal_zones', 'cooled') return self._config.getboolean('thermal_zones', 'cooled')
@property @property
def additional_thermal_bridge_u_value(self): def additional_thermal_bridge_u_value(self):
"""
Configured additional thermal bridge u value W/m2K
:return:
"""
return self._config.getfloat('thermal_zones', 'additional_thermal_bridge_u_value') return self._config.getfloat('thermal_zones', 'additional_thermal_bridge_u_value')
@property @property
def indirectly_heated_area_ratio(self): def indirectly_heated_area_ratio(self):
"""
Configured indirectly heated area ratio
:return: float
"""
return self._config.getfloat('thermal_zones', 'indirectly_heated_area_ratio') return self._config.getfloat('thermal_zones', 'indirectly_heated_area_ratio')
@property @property
def infiltration_rate_system_on(self): def infiltration_rate_system_on(self):
"""
Configured infiltration rate system on in air change per hour
:return: float
"""
return self._config.getfloat('thermal_zones', 'infiltration_rate_system_on') return self._config.getfloat('thermal_zones', 'infiltration_rate_system_on')
@property @property
def outside_solar_absorptance(self): def outside_solar_absorptance(self):
"""
Configured infiltration rate system off in air change per hour
:return: float
"""
return self._config.getfloat('thermal_zones', 'outside_solar_absorptance') return self._config.getfloat('thermal_zones', 'outside_solar_absorptance')

View File

@ -11,14 +11,29 @@ import open3d as o3d
class Geometry: class Geometry:
"""
Geometry helper class
"""
def __init__(self, delta=0.5): def __init__(self, delta=0.5):
self._delta = delta self._delta = delta
def almost_equal(self, v1, v2): def almost_equal(self, v1, v2):
"""
Compare two points and decides if they are almost equal (quadratic error under delta)
:param v1: [x,y,z]
:param v2: [x,y,z]
:return: Boolean
"""
delta = math.sqrt(pow((v1[0] - v2[0]), 2) + pow((v1[1] - v2[1]), 2) + pow((v1[2] - v2[2]), 2)) delta = math.sqrt(pow((v1[0] - v2[0]), 2) + pow((v1[1] - v2[1]), 2) + pow((v1[2] - v2[2]), 2))
return delta <= self._delta return delta <= self._delta
def is_almost_same_surface(self, s1, s2): def is_almost_same_surface(self, s1, s2):
"""
Compare two surfaces and decides if they are almost equal (quadratic error under delta)
:param s1: Surface
:param s2: Surface
:return: Boolean
"""
# delta is grads an need to be converted into radians # delta is grads an need to be converted into radians
delta = np.rad2deg(self._delta) delta = np.rad2deg(self._delta)
difference = (s1.inclination - s2.inclination) % math.pi difference = (s1.inclination - s2.inclination) % math.pi
@ -49,6 +64,12 @@ class Geometry:
@staticmethod @staticmethod
def to_points_matrix(points, remove_last=False): def to_points_matrix(points, remove_last=False):
"""
Transform a point vector into a point matrix
:param points: [x, y, z, x, y, z ...]
:param remove_last: Boolean
:return: [[x,y,z],[x,y,z]...]
"""
rows = points.size // 3 rows = points.size // 3
points = points.reshape(rows, 3) points = points.reshape(rows, 3)
if remove_last: if remove_last:
@ -107,6 +128,13 @@ class Geometry:
@staticmethod @staticmethod
def divide_mesh_by_plane(mesh, normal_plane, point_plane): def divide_mesh_by_plane(mesh, normal_plane, point_plane):
"""
Divide a mesh by a plane
:param mesh: Trimesh
:param normal_plane: [x, y, z]
:param point_plane: [x, y, z]
:return: [Trimesh]
"""
# The first mesh returns the positive side of the plane and the second the negative side. # The first mesh returns the positive side of the plane and the second the negative side.
# If the plane does not divide the mesh (i.e. it does not touch it or it is coplanar with one or more faces), # If the plane does not divide the mesh (i.e. it does not touch it or it is coplanar with one or more faces),
# then it returns only the original mesh. # then it returns only the original mesh.

View File

@ -8,26 +8,33 @@ from physics.physics_feeders.us_physics_parameters import UsPhysicsParameters
class PhysicsFactory: class PhysicsFactory:
"""
PhysicsFactor class
"""
def __init__(self, handler, city, base_path='data/physics'): def __init__(self, handler, city, base_path='data/physics'):
self._handler = handler.lower().replace(' ', '_') self._handler = '_' + handler.lower().replace(' ', '_')
self._city = city self._city = city
self._base_path = base_path self._base_path = base_path
self.factory() self.factory()
def us_new_york_city(self): def _us_new_york_city(self):
UsNewYorkCityPhysicsParameters(self._city, self._base_path) UsNewYorkCityPhysicsParameters(self._city, self._base_path)
def us(self): def _us(self):
UsPhysicsParameters(self._city, self._base_path) UsPhysicsParameters(self._city, self._base_path)
def ca(self): def _ca(self):
raise Exception('Not implemented') raise Exception('Not implemented')
def de(self): def _de(self):
raise Exception('Not implemented') raise Exception('Not implemented')
def es(self): def _es(self):
raise Exception('Not implemented') raise Exception('Not implemented')
def factory(self): def factory(self):
"""
Enrich the city with the physics information
:return: None
"""
getattr(self, self._handler, lambda: None)() getattr(self, self._handler, lambda: None)()

View File

@ -6,6 +6,9 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc
class UsPlutoToFunction: class UsPlutoToFunction:
"""
UsPlutoToFunction
"""
building_function = { building_function = {
'A0': 'single family house', 'A0': 'single family house',
'A1': 'single family house', 'A1': 'single family house',

View File

@ -5,7 +5,10 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc
""" """
class UsToLibraryTypes(object): class UsToLibraryTypes:
"""
UsToLibraryTypes
"""
standards = { standards = {
'ASHRAE Std189': 1, 'ASHRAE Std189': 1,
'ASHRAE 90.1-2004': 2 'ASHRAE 90.1-2004': 2
@ -44,6 +47,11 @@ class UsToLibraryTypes(object):
@staticmethod @staticmethod
def yoc_to_standard(year_of_construction): def yoc_to_standard(year_of_construction):
"""
Year of construction to standard
:param year_of_construction: int
:return: str
"""
if int(year_of_construction) < 2009: if int(year_of_construction) < 2009:
standard = 'ASHRAE 90.1_2004' standard = 'ASHRAE 90.1_2004'
else: else:
@ -52,6 +60,11 @@ class UsToLibraryTypes(object):
@staticmethod @staticmethod
def city_to_reference_city(city): def city_to_reference_city(city):
"""
City name to reference city
:param city: str
:return: str
"""
# ToDo: Dummy function that need to be implemented # ToDo: Dummy function that need to be implemented
reference_city = 'Baltimore' reference_city = 'Baltimore'
if city is not None: if city is not None:
@ -60,5 +73,10 @@ class UsToLibraryTypes(object):
@staticmethod @staticmethod
def city_to_climate_zone(city): def city_to_climate_zone(city):
"""
City name to climate zone
:param city: str
:return: str
"""
reference_city = UsToLibraryTypes.city_to_reference_city(city) reference_city = UsToLibraryTypes.city_to_reference_city(city)
return UsToLibraryTypes.reference_city_climate_zone[reference_city] return UsToLibraryTypes.reference_city_climate_zone[reference_city]

View File

@ -4,14 +4,17 @@ UsPhysicsParameters as base class
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from pathlib import Path
import xmltodict import xmltodict
from city_model_structure.layer import Layer from city_model_structure.layer import Layer
from city_model_structure.material import Material from city_model_structure.material import Material
from pathlib import Path
from physics.physics_feeders.helpers.us_to_library_types import UsToLibraryTypes from physics.physics_feeders.helpers.us_to_library_types import UsToLibraryTypes
class UsBasePhysicsParameters: class UsBasePhysicsParameters:
"""
UsBasePhysicsParameters class
"""
def __init__(self, climate_zone, city_objects, function_to_type, base_path): def __init__(self, climate_zone, city_objects, function_to_type, base_path):
self._climate_zone = climate_zone self._climate_zone = climate_zone
self._city_objects = city_objects self._city_objects = city_objects
@ -29,9 +32,9 @@ class UsBasePhysicsParameters:
building_type = function_to_type(city_object.function) building_type = function_to_type(city_object.function)
if building_type is None: if building_type is None:
return return
archetype = self.search_archetype(building_type, archetype = self._search_archetype(building_type,
UsToLibraryTypes.yoc_to_standard(city_object.year_of_construction), UsToLibraryTypes.yoc_to_standard(city_object.year_of_construction),
self._climate_zone) self._climate_zone)
# ToDo:remove this in the future # ToDo:remove this in the future
# ToDo: Raise WrongArchetype if not all the surface types are defined for the given city_object # ToDo: Raise WrongArchetype if not all the surface types are defined for the given city_object
if archetype is None: if archetype is None:
@ -50,9 +53,9 @@ class UsBasePhysicsParameters:
thermal_zone.infiltration_rate_system_on = archetype['infiltration_rate_for_ventilation_system_on']['#text'] thermal_zone.infiltration_rate_system_on = archetype['infiltration_rate_for_ventilation_system_on']['#text']
for thermal_boundary in thermal_zone.bounded: for thermal_boundary in thermal_zone.bounded:
construction_type = UsToLibraryTypes.construction_types[thermal_boundary.type] construction_type = UsToLibraryTypes.construction_types[thermal_boundary.type]
construction = UsBasePhysicsParameters.search_construction_in_archetype(archetype, construction_type) construction = UsBasePhysicsParameters._search_construction_in_archetype(archetype, construction_type)
construction_id = construction['@id'] construction_id = construction['@id']
c_lib = self.search_construction_type('construction', construction_id) c_lib = self._search_construction_type('construction', construction_id)
if 'outside_solar_absorptance' in c_lib: if 'outside_solar_absorptance' in c_lib:
thermal_boundary.outside_solar_absorptance = c_lib['outside_solar_absorptance']['#text'] thermal_boundary.outside_solar_absorptance = c_lib['outside_solar_absorptance']['#text']
thermal_boundary.outside_thermal_absorptance = c_lib['outside_thermal_absorptance']['#text'] thermal_boundary.outside_thermal_absorptance = c_lib['outside_thermal_absorptance']['#text']
@ -63,7 +66,7 @@ class UsBasePhysicsParameters:
layer = Layer() layer = Layer()
if 'thickness' in current_layer: if 'thickness' in current_layer:
layer.thickness = current_layer['thickness']['#text'] layer.thickness = current_layer['thickness']['#text']
material_lib = self.search_construction_type('material', current_layer['material']) material_lib = self._search_construction_type('material', current_layer['material'])
material = Material() material = Material()
if 'conductivity' in material_lib: if 'conductivity' in material_lib:
material.conductivity = material_lib['conductivity']['#text'] material.conductivity = material_lib['conductivity']['#text']
@ -80,8 +83,8 @@ class UsBasePhysicsParameters:
for opening in thermal_boundary.thermal_openings: for opening in thermal_boundary.thermal_openings:
if construction['window'] is None: if construction['window'] is None:
continue continue
w_lib = self.search_construction_type('window', construction['window']) w_lib = self._search_construction_type('window', construction['window'])
opening.conductivity_w_mk = w_lib['conductivity']['#text'] opening.conductivity = w_lib['conductivity']['#text']
opening.frame_ratio = w_lib['frame_ratio']['#text'] opening.frame_ratio = w_lib['frame_ratio']['#text']
opening.g_value = w_lib['solar_transmittance_at_normal_incidence']['#text'] opening.g_value = w_lib['solar_transmittance_at_normal_incidence']['#text']
opening.thickness = w_lib['thickness']['#text'] opening.thickness = w_lib['thickness']['#text']
@ -90,7 +93,7 @@ class UsBasePhysicsParameters:
opening.front_side_solar_transmittance_at_normal_incidence = \ opening.front_side_solar_transmittance_at_normal_incidence = \
w_lib['front_side_solar_transmittance_at_normal_incidence']['#text'] w_lib['front_side_solar_transmittance_at_normal_incidence']['#text']
def search_archetype(self, building_type, standard, climate_zone): def _search_archetype(self, building_type, standard, climate_zone):
for archetype in self._archetypes['archetypes']['archetype']: for archetype in self._archetypes['archetypes']['archetype']:
a_yc = str(archetype['@reference_standard']) a_yc = str(archetype['@reference_standard'])
a_bt = str(archetype['@building_type']) a_bt = str(archetype['@building_type'])
@ -99,14 +102,14 @@ class UsBasePhysicsParameters:
return archetype return archetype
return None return None
def search_construction_type(self, construction_type, construction_id): def _search_construction_type(self, construction_type, construction_id):
for c_lib in self._library['library'][construction_type + 's'][construction_type]: for c_lib in self._library['library'][construction_type + 's'][construction_type]:
if construction_id == c_lib['@id']: if construction_id == c_lib['@id']:
return c_lib return c_lib
raise Exception('Archetype definition contains elements that does not exist in the library') raise Exception('Archetype definition contains elements that does not exist in the library')
@staticmethod @staticmethod
def search_construction_in_archetype(archetype, construction_type): def _search_construction_in_archetype(archetype, construction_type):
for construction in archetype['constructions']['construction']: for construction in archetype['constructions']['construction']:
if construction['@type'] == construction_type: if construction['@type'] == construction_type:
return construction return construction

View File

@ -8,6 +8,9 @@ from physics.physics_feeders.helpers.us_pluto_to_function import UsPlutoToFuncti
class UsNewYorkCityPhysicsParameters(UsBasePhysicsParameters): class UsNewYorkCityPhysicsParameters(UsBasePhysicsParameters):
"""
UsNewYorkCityPhysicsParameters class
"""
def __init__(self, city, base_path): def __init__(self, city, base_path):
self._city = city self._city = city
climate_zone = 'ASHRAE_2004:4A' climate_zone = 'ASHRAE_2004:4A'

View File

@ -8,6 +8,9 @@ from physics.physics_feeders.helpers.us_to_library_types import UsToLibraryTypes
class UsPhysicsParameters(UsBasePhysicsParameters): class UsPhysicsParameters(UsBasePhysicsParameters):
"""
UsPhysicsParameters class
"""
def __init__(self, city, base_path): def __init__(self, city, base_path):
self._city = city self._city = city
self._climate_zone = UsToLibraryTypes.city_to_climate_zone(city.name) self._climate_zone = UsToLibraryTypes.city_to_climate_zone(city.name)

View File

@ -140,6 +140,9 @@ disable=print-statement,
exception-escape, exception-escape,
comprehension-escape, comprehension-escape,
import-error, import-error,
parse-error,
syntax-error,
bad-continuation,
no-name-in-module no-name-in-module
# Enable the message, report, category or checker with the given id(s). You can # Enable the message, report, category or checker with the given id(s). You can

View File

@ -7,15 +7,25 @@ from unittest import TestCase
from pathlib import Path from pathlib import Path
from geometry.geometry_factory import GeometryFactory from geometry.geometry_factory import GeometryFactory
import os import os
from city_model_structure.surface import Surface
class TestGeometryFactory(TestCase): class TestGeometryFactory(TestCase):
"""
TestGeometryFactory TestCase
"""
def setUp(self) -> None: def setUp(self) -> None:
"""
Test setup
:return: None
"""
self._city_gml = None self._city_gml = None
self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve() self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve()
def get_citygml(self): def get_citygml(self):
"""
Retrieve the test city gml
:return: City
"""
if self._city_gml is None: if self._city_gml is None:
file_path = (self._example_path / '2050 bp_2buildings.gml').resolve() file_path = (self._example_path / '2050 bp_2buildings.gml').resolve()
self._city_gml = GeometryFactory('citygml', file_path).city self._city_gml = GeometryFactory('citygml', file_path).city
@ -23,6 +33,10 @@ class TestGeometryFactory(TestCase):
return self._city_gml return self._city_gml
def test_citygml_city(self): def test_citygml_city(self):
"""
Test the City parsing
:return: None
"""
city = self.get_citygml() city = self.get_citygml()
self.assertIsNotNone(city.city_objects, 'city_objects is none') self.assertIsNotNone(city.city_objects, 'city_objects is none')
for city_object in city.city_objects: for city_object in city.city_objects:
@ -34,6 +48,10 @@ class TestGeometryFactory(TestCase):
self.assertIsNotNone(city.country_code, 'country code is none') self.assertIsNotNone(city.country_code, 'country code is none')
def test_citygml_city_objects(self): def test_citygml_city_objects(self):
"""
Test city objects in the city
:return: None
"""
city = self.get_citygml() city = self.get_citygml()
for city_object in city.city_objects: for city_object in city.city_objects:
self.assertIsNotNone(city_object.name, 'city_object name is none') self.assertIsNotNone(city_object.name, 'city_object name is none')
@ -58,6 +76,10 @@ class TestGeometryFactory(TestCase):
os.remove(Path(self._example_path, city_object.name + '.stl').resolve()) os.remove(Path(self._example_path, city_object.name + '.stl').resolve())
def test_citygml_surfaces(self): def test_citygml_surfaces(self):
"""
Test surfaces in city objects
:return: None
"""
city = self.get_citygml() city = self.get_citygml()
for city_object in city.city_objects: for city_object in city.city_objects:
for surface in city_object.surfaces: for surface in city_object.surfaces:
@ -85,6 +107,10 @@ class TestGeometryFactory(TestCase):
self.assertIsNotNone(surface.intersect(surface), 'self intersection is none') self.assertIsNotNone(surface.intersect(surface), 'self intersection is none')
def test_citygml_thermal_zone(self): def test_citygml_thermal_zone(self):
"""
Test thermal zones in city objects
:return: None
"""
city = self.get_citygml() city = self.get_citygml()
for city_object in city.city_objects: for city_object in city.city_objects:
for thermal_zone in city_object.thermal_zones: for thermal_zone in city_object.thermal_zones:
@ -97,8 +123,8 @@ class TestGeometryFactory(TestCase):
'thermal_zone additional_thermal_bridge_u_value is not none') 'thermal_zone additional_thermal_bridge_u_value is not none')
self.assertIsNone(thermal_zone.effective_thermal_capacity, self.assertIsNone(thermal_zone.effective_thermal_capacity,
'thermal_zone effective_thermal_capacity is not none') 'thermal_zone effective_thermal_capacity is not none')
self.assertIsNone(thermal_zone.indirectly_heated_area_ratio self.assertIsNone(thermal_zone.indirectly_heated_area_ratio,
, 'thermal_zone indirectly_heated_area_ratio is not none') 'thermal_zone indirectly_heated_area_ratio is not none')
self.assertIsNone(thermal_zone.infiltration_rate_system_off, self.assertIsNone(thermal_zone.infiltration_rate_system_off,
'thermal_zone infiltration_rate_system_off is not none') 'thermal_zone infiltration_rate_system_off is not none')
self.assertIsNone(thermal_zone.infiltration_rate_system_on, self.assertIsNone(thermal_zone.infiltration_rate_system_on,
@ -107,6 +133,10 @@ class TestGeometryFactory(TestCase):
'thermal_zone usage_zones are not none') 'thermal_zone usage_zones are not none')
def test_citygml_thermal_boundary(self): def test_citygml_thermal_boundary(self):
"""
Test thermal boundaries in thermal zones
:return: None
"""
city = self.get_citygml() city = self.get_citygml()
for city_object in city.city_objects: for city_object in city.city_objects:
for thermal_zone in city_object.thermal_zones: for thermal_zone in city_object.thermal_zones:
@ -133,6 +163,10 @@ class TestGeometryFactory(TestCase):
self.assertIsNone(thermal_boundary.window_ratio, 'thermal_boundary window_ratio was initialized') self.assertIsNone(thermal_boundary.window_ratio, 'thermal_boundary window_ratio was initialized')
def test_citygml_thermal_opening(self): def test_citygml_thermal_opening(self):
"""
Test thermal openings in thermal zones
:return: None
"""
city = self.get_citygml() city = self.get_citygml()
for city_object in city.city_objects: for city_object in city.city_objects:
for thermal_zone in city_object.thermal_zones: for thermal_zone in city_object.thermal_zones:
@ -141,11 +175,11 @@ class TestGeometryFactory(TestCase):
self.assertIsNone(thermal_opening.area, 'thermal_opening area was initialized') self.assertIsNone(thermal_opening.area, 'thermal_opening area was initialized')
self.assertTrue(thermal_opening.frame_ratio == 0, 'thermal_opening frame_ratio was not 0') self.assertTrue(thermal_opening.frame_ratio == 0, 'thermal_opening frame_ratio was not 0')
self.assertIsNone(thermal_opening.g_value, 'thermal_opening g_value was initialized') self.assertIsNone(thermal_opening.g_value, 'thermal_opening g_value was initialized')
self.assertIsNone(thermal_opening.conductivity_w_mk, 'thermal_opening conductivity_w_mk was initialized') self.assertIsNone(thermal_opening.conductivity, 'thermal_opening conductivity_w_mk was initialized')
self.assertIsNone(thermal_opening.inside_reflectance, 'thermal_opening inside_reflectance was initialized') self.assertIsNone(thermal_opening.inside_reflectance, 'thermal_opening inside_reflectance was initialized')
self.assertRaises(Exception, lambda: thermal_opening.openable_ratio, self.assertRaises(Exception, lambda: thermal_opening.openable_ratio,
'thermal_opening openable_ratio is not raising an exception') 'thermal_opening openable_ratio is not raising an exception')
self.assertIsNone(thermal_opening.outside_reflectance, self.assertIsNone(thermal_opening.outside_reflectance,
'thermal_opening outside_reflectance was initialized') 'thermal_opening outside_reflectance was initialized')
self.assertIsNone(thermal_opening.thickness_m, 'thermal_opening thickness_m was initialized') self.assertIsNone(thermal_opening.thickness, 'thermal_opening thickness_m was initialized')
self.assertRaises(Exception, lambda: thermal_opening.u_value, 'thermal_opening u_value was initialized') self.assertRaises(Exception, lambda: thermal_opening.u_value, 'thermal_opening u_value was initialized')

View File

@ -10,7 +10,10 @@ from physics.physics_factory import PhysicsFactory
class TestPhysicsFactory(TestCase): class TestPhysicsFactory(TestCase):
def setUp(self) -> None: """
TestPhysicsFactory TestCase
"""
def setup(self) -> None:
self._city_gml = None self._city_gml = None
self._nyc_with_physics = None self._nyc_with_physics = None
self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve() self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve()