Add code comment to the classes and improve overall quality

This commit is contained in:
Guille Gutierrez 2020-06-11 16:55:52 -04:00
parent 145520c8a2
commit 4284b2cb3e
14 changed files with 77 additions and 312 deletions

View File

@ -7,7 +7,7 @@ from __future__ import annotations
from typing import Union
import numpy as np
import pyny3d.geoms as pn
from helpers.geometry import Geometry
from helpers.geometry_helper import GeometryHelper
class Surface:
@ -21,7 +21,7 @@ class Surface:
self._swr = swr
self._remove_last = remove_last
self._is_projected = is_projected
self._geometry = Geometry()
self._geometry_helper = GeometryHelper()
self._polygon = None
self._ground_polygon = None
self._area = None
@ -89,7 +89,7 @@ class Surface:
"""
if self._points is None:
self._points = np.fromstring(self._coordinates, dtype=float, sep=' ')
self._points = Geometry.to_points_matrix(self._points, self._remove_last)
self._points = GeometryHelper.to_points_matrix(self._points, self._remove_last)
return self._points
def _min_coord(self, axis):
@ -153,7 +153,7 @@ class Surface:
coordinates = coordinates + ' '
coordinates = coordinates + str(x) + ' ' + str(y) + ' ' + str(z)
self._ground_points = np.fromstring(coordinates, dtype=float, sep=' ')
self._ground_points = Geometry.to_points_matrix(self._ground_points, False)
self._ground_points = GeometryHelper.to_points_matrix(self._ground_points, False)
return self._ground_points
@property
@ -309,7 +309,7 @@ class Surface:
"""
if self.type != 'Wall' or surface.type != 'Wall':
return
if self._geometry.is_almost_same_surface(self, surface):
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)

View File

@ -7,7 +7,7 @@ from typing import List
from city_model_structure.thermal_opening import ThermalOpening
from city_model_structure.thermal_zone import ThermalZone
from city_model_structure.layer import Layer
from helpers.configuration import Configuration
from helpers.configuration_helper import ConfigurationHelper
class ThermalBoundary:
@ -20,7 +20,7 @@ class ThermalBoundary:
# 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 = Configuration().outside_solar_absorptance
self._outside_solar_absorptance = ConfigurationHelper().outside_solar_absorptance
self._outside_thermal_absorptance = None
self._outside_visible_absorptance = None
self._window_ratio = None
@ -208,8 +208,8 @@ class ThermalBoundary:
:return: float
"""
if self._u_value is None:
h_i = Configuration().h_i
h_e = Configuration().h_e
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:

View File

@ -3,7 +3,7 @@ ThermalOpening module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from helpers.configuration import Configuration
from helpers.configuration_helper import ConfigurationHelper
class ThermalOpening:
@ -13,7 +13,7 @@ class ThermalOpening:
def __init__(self):
self._openable_ratio = None
self._conductivity = None
self._frame_ratio = Configuration().frame_ratio
self._frame_ratio = ConfigurationHelper().frame_ratio
self._g_value = None
self._thickness = None
self._front_side_solar_transmittance_at_normal_incidence = None
@ -56,8 +56,8 @@ class ThermalOpening:
# This ensures a more robust code that returns the overall_u_value regardless the order the parameters are read.
self._conductivity = value
if self._overall_u_value is None and self.thickness is not None:
h_i = Configuration().h_i
h_e = Configuration().h_e
h_i = ConfigurationHelper().h_i
h_e = ConfigurationHelper().h_e
r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self.thickness)
self._overall_u_value = 1 / r_value
@ -114,8 +114,8 @@ class ThermalOpening:
# This ensures a more robust code that returns the overall_u_value regardless the order the parameters are read.
self._thickness = value
if self._overall_u_value is None and self.conductivity is not None:
h_i = Configuration().h_i
h_e = Configuration().h_e
h_i = ConfigurationHelper().h_i
h_e = ConfigurationHelper().h_e
r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self.thickness)
self._overall_u_value = 1 / r_value
@ -156,7 +156,7 @@ class ThermalOpening:
@property
def overall_u_value(self):
"""
Get thermal opening overall u value
Get thermal opening overall u value in W/m2K
:return: float
"""
return self._overall_u_value
@ -164,7 +164,7 @@ class ThermalOpening:
@overall_u_value.setter
def overall_u_value(self, value):
"""
Get thermal opening overall u value
Get thermal opening overall u value in W/m2K
:param value: float
:return: None
"""

View File

@ -6,7 +6,7 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc
from typing import List, TypeVar
from city_model_structure.usage_zone import UsageZone
from city_model_structure.surface import Surface
from helpers.configuration import Configuration
from helpers.configuration_helper import ConfigurationHelper
ThermalBoundary = TypeVar('ThermalBoundary')
@ -19,12 +19,12 @@ class ThermalZone:
self._surfaces = surfaces
self._floor_area = None
self._bounded = None
self._heated = Configuration().heated
self._cooled = Configuration().cooled
self._additional_thermal_bridge_u_value = Configuration().additional_thermal_bridge_u_value
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 = Configuration().indirectly_heated_area_ratio
self._infiltration_rate_system_on = Configuration().infiltration_rate_system_on
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
@ -85,7 +85,7 @@ class ThermalZone:
@property
def additional_thermal_bridge_u_value(self):
"""
Get thermal zone additional thermal bridge u value
Get thermal zone additional thermal bridge u value W/m2K
:return: float
"""
return self._additional_thermal_bridge_u_value
@ -93,7 +93,7 @@ class ThermalZone:
@additional_thermal_bridge_u_value.setter
def additional_thermal_bridge_u_value(self, value):
"""
Set thermal zone additional thermal bridge u value
Set thermal zone additional thermal bridge u value W/m2K
:param value: float
:return: None
"""

View File

@ -1,91 +0,0 @@
"""
Configuration helper
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import configparser
from pathlib import Path
class Configuration:
"""
Configuration class
"""
def __init__(self):
base_path = Path().resolve().parent
config_file = Path(base_path / 'libs/config/configuration.ini').resolve()
self._config = configparser.ConfigParser()
self._config.read(config_file)
@property
def h_i(self):
"""
Configured internal convective coefficient in W/m2K
:return: float
"""
return self._config.getfloat('convective_fluxes', 'h_i')
@property
def h_e(self):
"""
Configured external convective coefficient in W/m2K
:return: float
"""
return self._config.getfloat('convective_fluxes', 'h_e')
@property
def frame_ratio(self):
"""
Configured frame ratio
:return: float
"""
return self._config.getfloat('windows', 'frame_ratio')
@property
def heated(self):
"""
Configured heated flag
:return: Boolean
"""
return self._config.getboolean('thermal_zones', 'heated')
@property
def cooled(self):
"""
Configured cooled flag
:return: Boolean
"""
return self._config.getboolean('thermal_zones', 'cooled')
@property
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')
@property
def indirectly_heated_area_ratio(self):
"""
Configured indirectly heated area ratio
:return: float
"""
return self._config.getfloat('thermal_zones', 'indirectly_heated_area_ratio')
@property
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')
@property
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')

View File

@ -1,165 +0,0 @@
"""
Geometry helper
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import math
import numpy as np
from trimesh import Trimesh
from trimesh import intersections
import open3d as o3d
class Geometry:
"""
Geometry helper class
"""
def __init__(self, delta=0.5):
self._delta = delta
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))
return delta <= self._delta
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 = np.rad2deg(self._delta)
difference = (s1.inclination - s2.inclination) % math.pi
if abs(difference) > delta:
return False
# s1 and s2 are at least almost parallel surfaces
# calculate distance point to plane using all the vertex
# select surface1 value for the point (X,Y,Z) where two of the values are 0
minimum_distance = self._delta + 1
parametric = s2.polygon.get_parametric()
n2 = s2.normal
for point in s1.points:
distance = abs(
(point[0] * parametric[0]) + (point[1] * parametric[1]) + (point[2] * parametric[2]) + parametric[3])
normal_module = math.sqrt(pow(n2[0], 2) + pow(n2[1], 2) + pow(n2[2], 2))
if normal_module == 0:
continue
distance = distance / normal_module
if distance < minimum_distance:
minimum_distance = distance
if minimum_distance <= self._delta:
break
if minimum_distance > self._delta or s1.intersect(s2) is None:
return False
else:
return True
@staticmethod
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
points = points.reshape(rows, 3)
if remove_last:
points = np.delete(points, rows - 1, 0)
return points
@staticmethod
def _segment_list_to_point_cloud(segment_list):
point_list = np.asarray(segment_list[0])
for segment in segment_list:
for new_point in segment:
found = False
for point in point_list:
same_point = np.allclose(new_point, point)
if same_point:
found = True
break
if not found:
point_list = np.concatenate((point_list, [new_point]))
return point_list
@staticmethod
def _point_cloud_to_mesh(point_list, normal_list):
# Return a mesh composed only by triangles
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(point_list)
pcd.normals = o3d.utility.Vector3dVector(normal_list)
distances = pcd.compute_nearest_neighbor_distance()
avg_dist = np.mean(distances)
radius = 3 * avg_dist
bpa_mesh = o3d.geometry.TriangleMesh().create_from_point_cloud_ball_pivoting(
pcd, o3d.utility.DoubleVector([radius, radius * 2]))
mesh_result = Trimesh(vertices=np.asarray(bpa_mesh.vertices), faces=np.asarray(bpa_mesh.triangles))
return mesh_result
@staticmethod
def _merge_meshes(mesh1, mesh2):
v_1 = mesh1.vertices
f_1 = mesh1.faces
v_2 = mesh2.vertices
f_2 = mesh2.faces
length = len(v_1)
v_merge = np.concatenate((v_1, v_2))
f_merge = np.asarray(f_1)
for item in f_2:
point1 = item.item(0) + length
point2 = item.item(1) + length
point3 = item.item(2) + length
surface = np.asarray([point1, point2, point3])
f_merge = np.concatenate((f_merge, [surface]))
mesh_merge = Trimesh(vertices=v_merge, faces=f_merge)
return mesh_merge
@staticmethod
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.
# 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.
normal_plane_opp = [None] * len(normal_plane)
for i in range(0, len(normal_plane)):
normal_plane_opp[i] = - normal_plane[i]
normal = [normal_plane, normal_plane_opp]
normal_opp = [normal_plane_opp, normal_plane]
mesh_final = []
for i in range(0, 2):
mesh_1 = intersections.slice_mesh_plane(mesh, normal[i], point_plane)
mesh_1_segments = intersections.mesh_plane(mesh, normal[i], point_plane)
boo = mesh.difference(mesh_1, engine='blender')
print(boo)
quit()
if len(mesh_1_segments) <= 0 or len(mesh_1.faces) == len(mesh.faces):
mesh_final.append(mesh)
break
else:
points = Geometry._segment_list_to_point_cloud(mesh_1_segments)
points_normals = [[None] * 3] * len(points)
for j in range(0, len(points_normals)):
points_normals[j] = normal_opp[i]
mesh_2 = Geometry._point_cloud_to_mesh(points, points_normals)
mesh_final.append(Geometry._merge_meshes(mesh_1, mesh_2))
return mesh_final

View File

@ -35,7 +35,7 @@ class UsBasePhysicsParameters:
archetype = self._search_archetype(building_type,
UsToLibraryTypes.yoc_to_standard(city_object.year_of_construction),
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
if archetype is None:
print(building_type, UsToLibraryTypes.yoc_to_standard(city_object.year_of_construction),

View File

@ -4,9 +4,9 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
from unittest import TestCase
import os
from pathlib import Path
from geometry.geometry_factory import GeometryFactory
import os
class TestGeometryFactory(TestCase):
@ -21,11 +21,7 @@ class TestGeometryFactory(TestCase):
self._city_gml = None
self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve()
def get_citygml(self):
"""
Retrieve the test city gml
:return: City
"""
def _get_citygml(self):
if self._city_gml is None:
file_path = (self._example_path / 'buildings.gml').resolve()
self._city_gml = GeometryFactory('citygml', file_path).city
@ -37,7 +33,7 @@ class TestGeometryFactory(TestCase):
Test the City parsing
:return: None
"""
city = self.get_citygml()
city = self._get_citygml()
self.assertIsNotNone(city.city_objects, 'city_objects is none')
for city_object in city.city_objects:
self.assertIsNotNone(city.city_object(city_object.name), 'city_object return none')
@ -52,7 +48,7 @@ class TestGeometryFactory(TestCase):
Test city objects in the city
:return: None
"""
city = self.get_citygml()
city = self._get_citygml()
for city_object in city.city_objects:
self.assertIsNotNone(city_object.name, 'city_object name is none')
self.assertIsNotNone(city_object.lod, 'city_object lod is none')
@ -80,7 +76,7 @@ class TestGeometryFactory(TestCase):
Test surfaces in city objects
:return: None
"""
city = self.get_citygml()
city = self._get_citygml()
for city_object in city.city_objects:
for surface in city_object.surfaces:
self.assertIsNotNone(surface.name, 'surface name is none')
@ -111,7 +107,7 @@ class TestGeometryFactory(TestCase):
Test thermal zones in city objects
:return: None
"""
city = self.get_citygml()
city = self._get_citygml()
for city_object in city.city_objects:
for thermal_zone in city_object.thermal_zones:
self.assertIsNotNone(thermal_zone.surfaces, 'thermal_zone surfaces is none')
@ -137,7 +133,7 @@ class TestGeometryFactory(TestCase):
Test thermal boundaries in thermal zones
:return: None
"""
city = self.get_citygml()
city = self._get_citygml()
for city_object in city.city_objects:
for thermal_zone in city_object.thermal_zones:
for thermal_boundary in thermal_zone.bounded:
@ -167,7 +163,7 @@ class TestGeometryFactory(TestCase):
Test thermal openings in thermal zones
:return: None
"""
city = self.get_citygml()
city = self._get_citygml()
for city_object in city.city_objects:
for thermal_zone in city_object.thermal_zones:
for thermal_boundary in thermal_zone.bounded:

View File

@ -13,26 +13,34 @@ class TestPhysicsFactory(TestCase):
"""
TestPhysicsFactory TestCase
"""
def setup(self) -> None:
def setUp(self) -> None:
"""
Configure test environment
:return:
"""
self._city_gml = None
self._nyc_with_physics = None
self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve()
def get_citygml(self):
def _get_citygml(self):
if self._city_gml is None:
file_path = (self._example_path / 'buildings.gml').resolve()
self._city_gml = GeometryFactory('citygml', file_path).city
self.assertIsNotNone(self._city_gml, 'city is none')
return self._city_gml
def get_city_with_physics(self):
def _get_city_with_physics(self):
if self._nyc_with_physics is None:
self._nyc_with_physics = self.get_citygml()
self._nyc_with_physics = self._get_citygml()
PhysicsFactory('us_new_york_city', self._nyc_with_physics, base_path=self._example_path)
return self._nyc_with_physics
def test_city_with_physics(self):
city = self.get_city_with_physics()
"""
Enrich the city with the physic information and verify ot
:return: None
"""
city = self._get_city_with_physics()
for city_object in city.city_objects:
self.assertIsNotNone(city_object.average_storey_height, 'average_storey_height is none')
self.assertIsNotNone(city_object.storeys_above_ground, 'storeys_above_ground is none')

View File

@ -8,22 +8,29 @@ from usage.usage_feeders.us_new_york_city_usage_parameters import UsNewYorkCityU
class UsageFactory:
"""
UsageFactory class
"""
def __init__(self, handler, city):
self._handler = handler.lower().replace(' ', '_')
self._handler = '_' + handler.lower().replace(' ', '_')
self._city = city
self.factory()
def us_new_york_city(self):
def _us_new_york_city(self):
UsNewYorkCityUsageParameters(self._city)
def ca(self):
def _ca(self):
raise Exception('Not implemented')
def de(self):
def _de(self):
DeUsageParameters(self._city)
def es(self):
def _es(self):
raise Exception('Not implemented')
def factory(self):
"""
Enrich the city with the usage information
:return: None
"""
getattr(self, self._handler, lambda: None)()

View File

@ -3,12 +3,15 @@ DeUsageParameters model the usage properties for a German building
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import xmltodict
from pathlib import Path
import xmltodict
from usage.usage_feeders.helpers.us_function_to_usage import UsFunctionToUsage
class DeUsageParameters:
"""
DeUsageParameters
"""
def __init__(self, city_objects):
self._city_objects = city_objects
@ -17,4 +20,4 @@ class DeUsageParameters:
with open(path) as xml:
self._library = xmltodict.parse(xml.read())
for city_object in city_objects:
UsFunctionToUsage.function_to_usage(city_object.function)
UsFunctionToUsage.usage(city_object.function)

View File

@ -3,13 +3,16 @@ UsBaseUsageParameters base class to model the usage properties for a building in
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import xmltodict
from pathlib import Path
import xmltodict
from city_model_structure.usage_zone import UsageZone
from city_model_structure.internal_gains import InternalGains
class UsBaseUsageParameters:
"""
UsBaseUsageParameters class
"""
def __init__(self, city, function_to_usage):
self._city = city
# ToDo: this is using the german library as a temporary approach, need to use/define a library for US
@ -17,7 +20,7 @@ class UsBaseUsageParameters:
with open(path) as xml:
self._library = xmltodict.parse(xml.read(), force_list='zoneUsageVariant')
for city_object in self._city.city_objects:
#ToDo: Right now is just one usage zone but will be multiple in the future
# ToDo: Right now is just one usage zone but will be multiple in the future
usage_zone = UsageZone()
usage_zone.usage = function_to_usage(city_object.function)
for zone_usage_type in self._library['buildingUsageLibrary']['zoneUsageType']:
@ -31,14 +34,12 @@ class UsBaseUsageParameters:
city_object.usage_zone = [usage_zone]
break
continue
else:
city_object.usage_zones = [UsBaseUsageParameters._parse_zone_usage_type(zone_usage_type, usage_zone)]
break
city_object.usage_zones = [UsBaseUsageParameters._parse_zone_usage_type(zone_usage_type, usage_zone)]
break
if city_object.usage_zones is None:
print(city_object.function)
raise Exception('Usage not found for building function')
@staticmethod
def _parse_zone_usage_type(zone_usage_type, usage_zone):
usage_zone.hours_day = zone_usage_type['occupancy']['usageHoursPerDay']

View File

@ -8,6 +8,9 @@ from usage.usage_feeders.helpers.us_pluto_to_usage import UsPlutoToUsage as Pu
class UsNewYorkCityUsageParameters(UsBaseUsageParameters):
"""
UsNewYorkCityUsageParameters class
"""
def __init__(self, city):
self._city = city
super().__init__(self._city, Pu.usage)

View File

@ -8,5 +8,8 @@ from usage.usage_feeders.helpers.us_function_to_usage import UsFunctionToUsage
class UsUsageParameters(UsBaseUsageParameters):
"""
UsUsageParameters class
"""
def __init__(self, city):
super().__init__(city, UsFunctionToUsage.usage)