parent
c60aca4c07
commit
1c8ffcfee0
|
@ -1,27 +0,0 @@
|
||||||
"""
|
|
||||||
Building module a new implementation option
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
|
|
||||||
|
|
||||||
class Building(CityObject):
|
|
||||||
"""
|
|
||||||
Building(CityObject) class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, lod, trimesh, year_of_construction, function, city_lower_corner):
|
|
||||||
surfaces = []
|
|
||||||
for face in trimesh.faces:
|
|
||||||
# todo: review for obj with windows
|
|
||||||
points = []
|
|
||||||
for vertex_index in face:
|
|
||||||
points.append(trimesh.vertices[vertex_index])
|
|
||||||
solid_polygon = Polygon(points)
|
|
||||||
perimeter_polygon = solid_polygon
|
|
||||||
surface = Surface(solid_polygon, perimeter_polygon)
|
|
||||||
surfaces.append(surface)
|
|
||||||
super().__init__(name, lod, surfaces, city_lower_corner)
|
|
|
@ -1,26 +0,0 @@
|
||||||
|
|
||||||
# from surfaces to thermal_zones
|
|
||||||
|
|
||||||
#zone_surfaces = []
|
|
||||||
# these should be the virtual internal surfaces (What about the internal walls??)
|
|
||||||
#for surface_id in zone_surfaces_ids:
|
|
||||||
# zone_surfaces.append(self.surface(surface_id))
|
|
||||||
#self._thermal_zones.append(ThermalZone(zone_surfaces))
|
|
||||||
|
|
||||||
#for t_zones in self._thermal_zones:
|
|
||||||
# t_zones.bounded = [ThermalBoundary(s, [t_zones]) for s in t_zones.surfaces]
|
|
||||||
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
from city_model_structure.attributes.plane import Plane
|
|
||||||
from city_model_structure.attributes.point import Point
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
coordinates = [np.array([1, 0, 0]), np.array([0, 0, 0]), np.array([0, 0, 1]), np.array([1, 0, 1])]
|
|
||||||
polygon = Polygon(coordinates)
|
|
||||||
origin = Point([0, 0, 0.5])
|
|
||||||
normal = np.array([0, 0, 1])
|
|
||||||
plane = Plane(normal=normal, origin=origin)
|
|
||||||
intersection, polygon_1, polygon_2 = polygon.divide(plane)
|
|
||||||
print('final polygon')
|
|
||||||
print(polygon_1.coordinates)
|
|
||||||
print(polygon_2.coordinates)
|
|
|
@ -1,45 +0,0 @@
|
||||||
"""
|
|
||||||
Node module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
"""
|
|
||||||
import uuid
|
|
||||||
from typing import List
|
|
||||||
from city_model_structure.attributes.node import Node
|
|
||||||
|
|
||||||
|
|
||||||
class Edge:
|
|
||||||
"""
|
|
||||||
Generic edge class to be used as base for the network edges
|
|
||||||
"""
|
|
||||||
def __init__(self, name, nodes=None):
|
|
||||||
if nodes is None:
|
|
||||||
nodes = []
|
|
||||||
self._name = name
|
|
||||||
self._id = None
|
|
||||||
self._nodes = nodes
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
Edge name
|
|
||||||
"""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
"""
|
|
||||||
Edge id, an universally unique identifier randomly generated
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if self._id is None:
|
|
||||||
self._id = uuid.uuid4()
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def nodes(self) -> List[Node]:
|
|
||||||
"""
|
|
||||||
Delimiting nodes for the edge
|
|
||||||
"""
|
|
||||||
return self._nodes
|
|
|
@ -1,46 +0,0 @@
|
||||||
"""
|
|
||||||
Node module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
from typing import List
|
|
||||||
from city_model_structure.attributes.edge import Edge
|
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
|
||||||
"""
|
|
||||||
Generic node class to be used as base for the network nodes
|
|
||||||
"""
|
|
||||||
def __init__(self, name, edges=None):
|
|
||||||
if edges is None:
|
|
||||||
edges = []
|
|
||||||
self._name = name
|
|
||||||
self._id = None
|
|
||||||
self._edges = edges
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
Node name
|
|
||||||
"""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
"""
|
|
||||||
Node id, an universally unique identifier randomly generated
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if self._id is None:
|
|
||||||
self._id = uuid.uuid4()
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def edges(self) -> List[Edge]:
|
|
||||||
"""
|
|
||||||
Edges delimited by the node
|
|
||||||
"""
|
|
||||||
return self._edges
|
|
|
@ -1,57 +0,0 @@
|
||||||
"""
|
|
||||||
Plane module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import TypeVar
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
Point = TypeVar('Point')
|
|
||||||
|
|
||||||
|
|
||||||
class Plane:
|
|
||||||
"""
|
|
||||||
Plane class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, origin=None, normal=None):
|
|
||||||
# todo: other options to define the plane:
|
|
||||||
# by two lines
|
|
||||||
# by three points
|
|
||||||
self._origin = origin
|
|
||||||
self._normal = normal
|
|
||||||
self._opposite_normal = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def origin(self) -> Point:
|
|
||||||
"""
|
|
||||||
Point origin of the plane
|
|
||||||
return Point
|
|
||||||
"""
|
|
||||||
if self._origin is None:
|
|
||||||
raise NotImplementedError
|
|
||||||
return self._origin
|
|
||||||
|
|
||||||
@property
|
|
||||||
def normal(self):
|
|
||||||
"""
|
|
||||||
Plane normal [x, y, z]
|
|
||||||
return np.ndarray
|
|
||||||
"""
|
|
||||||
if self._normal is None:
|
|
||||||
raise NotImplementedError
|
|
||||||
return self._normal
|
|
||||||
|
|
||||||
@property
|
|
||||||
def opposite_normal(self):
|
|
||||||
"""
|
|
||||||
Plane normal in the opposite direction [x, y, z]
|
|
||||||
return np.ndarray
|
|
||||||
"""
|
|
||||||
if self._opposite_normal is None:
|
|
||||||
coordinates = []
|
|
||||||
for coordinate in self.normal:
|
|
||||||
coordinates.append(-coordinate)
|
|
||||||
self._opposite_normal = np.array(coordinates)
|
|
||||||
return self._opposite_normal
|
|
|
@ -1,35 +0,0 @@
|
||||||
"""
|
|
||||||
Point module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import math
|
|
||||||
|
|
||||||
|
|
||||||
class Point:
|
|
||||||
"""
|
|
||||||
Point class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, coordinates):
|
|
||||||
self._coordinates = coordinates
|
|
||||||
|
|
||||||
@property
|
|
||||||
def coordinates(self):
|
|
||||||
"""
|
|
||||||
Point coordinates
|
|
||||||
"""
|
|
||||||
return self._coordinates
|
|
||||||
|
|
||||||
def distance_to_point(self, other_point):
|
|
||||||
"""
|
|
||||||
distance between points in an n-D Euclidean space
|
|
||||||
:param other_point: point or vertex
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
power = 0
|
|
||||||
for dimension in range(0, len(self.coordinates)):
|
|
||||||
power += math.pow(other_point.coordinates[dimension]-self.coordinates[dimension], 2)
|
|
||||||
distance = math.sqrt(power)
|
|
||||||
return distance
|
|
|
@ -1,646 +0,0 @@
|
||||||
"""
|
|
||||||
Polygon module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
import math
|
|
||||||
import sys
|
|
||||||
from typing import List
|
|
||||||
import numpy as np
|
|
||||||
from trimesh import Trimesh
|
|
||||||
import trimesh.intersections
|
|
||||||
from city_model_structure.attributes.point import Point
|
|
||||||
|
|
||||||
|
|
||||||
class Polygon:
|
|
||||||
"""
|
|
||||||
Polygon class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, coordinates):
|
|
||||||
|
|
||||||
self._area = None
|
|
||||||
self._points = None
|
|
||||||
self._points_list = None
|
|
||||||
self._normal = None
|
|
||||||
self._inverse = None
|
|
||||||
self._edges = None
|
|
||||||
self._coordinates = coordinates
|
|
||||||
self._triangles = None
|
|
||||||
self._vertices = None
|
|
||||||
self._faces = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def points(self) -> List[Point]:
|
|
||||||
"""
|
|
||||||
List of points belonging to the polygon [[x, y, z],...]
|
|
||||||
:return: List[Point]
|
|
||||||
"""
|
|
||||||
if self._points is None:
|
|
||||||
self._points = []
|
|
||||||
for coordinate in self.coordinates:
|
|
||||||
self._points.append(Point(coordinate))
|
|
||||||
return self._points
|
|
||||||
|
|
||||||
@property
|
|
||||||
def coordinates(self) -> List[np.ndarray]:
|
|
||||||
"""
|
|
||||||
List of points in the shape of its coordinates belonging to the polygon [[x, y, z],...]
|
|
||||||
:return: [np.ndarray]
|
|
||||||
"""
|
|
||||||
return self._coordinates
|
|
||||||
|
|
||||||
@property
|
|
||||||
def points_list(self) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
Solid surface point coordinates list [x, y, z, x, y, z,...]
|
|
||||||
:return: np.ndarray
|
|
||||||
"""
|
|
||||||
if self._points_list is None:
|
|
||||||
s = self.coordinates
|
|
||||||
self._points_list = np.reshape(s, len(s) * 3)
|
|
||||||
return self._points_list
|
|
||||||
|
|
||||||
@property
|
|
||||||
def edges(self):
|
|
||||||
"""
|
|
||||||
Polygon edges list
|
|
||||||
"""
|
|
||||||
if self._edges is None:
|
|
||||||
self._edges = []
|
|
||||||
for i in range(0, len(self.points)-1):
|
|
||||||
point_1 = self.points[i]
|
|
||||||
point_2 = self.points[i+1]
|
|
||||||
self._edges.append([point_1, point_2])
|
|
||||||
self._edges.append([self.points[len(self.points)-1], self.points[0]])
|
|
||||||
return self._edges
|
|
||||||
|
|
||||||
@property
|
|
||||||
def area(self):
|
|
||||||
"""
|
|
||||||
Surface area in square meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
# New method to calculate area
|
|
||||||
if self._area is None:
|
|
||||||
if len(self.points) < 3:
|
|
||||||
sys.stderr.write('Warning: the area of a line or point cannot be calculated 1. Area = 0\n')
|
|
||||||
return 0
|
|
||||||
alpha = 0
|
|
||||||
vec_1 = self.points[1].coordinates - self.points[0].coordinates
|
|
||||||
for i in range(2, len(self.points)):
|
|
||||||
vec_2 = self.points[i].coordinates - self.points[0].coordinates
|
|
||||||
alpha += self._angle_between_vectors(vec_1, vec_2)
|
|
||||||
if alpha == 0:
|
|
||||||
sys.stderr.write('Warning: the area of a line or point cannot be calculated 2. Area = 0\n')
|
|
||||||
return 0
|
|
||||||
horizontal_points = self._points_rotated_to_horizontal
|
|
||||||
area = 0
|
|
||||||
for i in range(0, len(horizontal_points)-1):
|
|
||||||
point = horizontal_points[i]
|
|
||||||
next_point = horizontal_points[i+1]
|
|
||||||
area += (next_point[1] + point[1]) / 2 * (next_point[0] - point[0])
|
|
||||||
next_point = horizontal_points[0]
|
|
||||||
point = horizontal_points[len(horizontal_points)-1]
|
|
||||||
area += (next_point[1] + point[1]) / 2 * (next_point[0] - point[0])
|
|
||||||
self._area = abs(area)
|
|
||||||
return self._area
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _points_rotated_to_horizontal(self):
|
|
||||||
"""
|
|
||||||
polygon points rotated to horizontal
|
|
||||||
:return: [float]
|
|
||||||
"""
|
|
||||||
z_vector = [0, 0, 1]
|
|
||||||
normal_vector = self.normal
|
|
||||||
horizontal_points = []
|
|
||||||
x = normal_vector[0]
|
|
||||||
y = normal_vector[1]
|
|
||||||
|
|
||||||
if x == 0 and y == 0:
|
|
||||||
# Already horizontal
|
|
||||||
for point in self.points:
|
|
||||||
horizontal_points.append([point.coordinates[0], point.coordinates[1], 0])
|
|
||||||
else:
|
|
||||||
alpha = self._angle_between_vectors(normal_vector, z_vector)
|
|
||||||
rotation_line = np.cross(normal_vector, z_vector)
|
|
||||||
third_axis = np.cross(normal_vector, rotation_line)
|
|
||||||
w_1 = rotation_line / np.linalg.norm(rotation_line)
|
|
||||||
w_2 = normal_vector
|
|
||||||
w_3 = third_axis / np.linalg.norm(third_axis)
|
|
||||||
rotation_matrix = np.array([[1, 0, 0],
|
|
||||||
[0, np.cos(alpha), -np.sin(alpha)],
|
|
||||||
[0, np.sin(alpha), np.cos(alpha)]])
|
|
||||||
base_matrix = np.array([w_1, w_2, w_3])
|
|
||||||
rotation_base_matrix = np.matmul(base_matrix.transpose(), rotation_matrix.transpose())
|
|
||||||
rotation_base_matrix = np.matmul(rotation_base_matrix, base_matrix)
|
|
||||||
|
|
||||||
if rotation_base_matrix is None:
|
|
||||||
sys.stderr.write('Warning: rotation base matrix returned None\n')
|
|
||||||
else:
|
|
||||||
for point in self.points:
|
|
||||||
new_point = np.matmul(rotation_base_matrix, point.coordinates)
|
|
||||||
horizontal_points.append(new_point)
|
|
||||||
return horizontal_points
|
|
||||||
|
|
||||||
@property
|
|
||||||
def normal(self) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
Surface normal vector
|
|
||||||
:return: np.ndarray
|
|
||||||
"""
|
|
||||||
if self._normal is None:
|
|
||||||
points = self.coordinates
|
|
||||||
# todo: IF THE FIRST ONE IS 0, START WITH THE NEXT
|
|
||||||
point_origin = points[len(points)-2]
|
|
||||||
vector_1 = points[len(points)-1] - point_origin
|
|
||||||
vector_2 = points[0] - point_origin
|
|
||||||
vector_3 = points[1] - point_origin
|
|
||||||
cross_product = np.cross(vector_1, vector_2)
|
|
||||||
if np.linalg.norm(cross_product) != 0:
|
|
||||||
cross_product = cross_product / np.linalg.norm(cross_product)
|
|
||||||
alpha = self._angle_between_vectors(vector_1, vector_2)
|
|
||||||
else:
|
|
||||||
cross_product = [0, 0, 0]
|
|
||||||
alpha = 0
|
|
||||||
if len(points) == 3:
|
|
||||||
return cross_product
|
|
||||||
if np.linalg.norm(cross_product) == 0:
|
|
||||||
return cross_product
|
|
||||||
alpha += self._angle(vector_2, vector_3, cross_product)
|
|
||||||
for i in range(0, len(points)-4):
|
|
||||||
vector_1 = points[i+1] - point_origin
|
|
||||||
vector_2 = points[i+2] - point_origin
|
|
||||||
alpha += self._angle(vector_1, vector_2, cross_product)
|
|
||||||
vector_1 = points[len(points) - 1] - point_origin
|
|
||||||
vector_2 = points[0] - point_origin
|
|
||||||
if alpha < 0:
|
|
||||||
cross_product = np.cross(vector_2, vector_1)
|
|
||||||
else:
|
|
||||||
cross_product = np.cross(vector_1, vector_2)
|
|
||||||
self._normal = cross_product / np.linalg.norm(cross_product)
|
|
||||||
return self._normal
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _angle(vector_1, vector_2, cross_product):
|
|
||||||
"""
|
|
||||||
alpha angle in radians
|
|
||||||
:param vector_1: [float]
|
|
||||||
:param vector_2: [float]
|
|
||||||
:param cross_product: [float]
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
accepted_normal_difference = 0.01
|
|
||||||
cross_product_next = np.cross(vector_1, vector_2)
|
|
||||||
if np.linalg.norm(cross_product_next) != 0:
|
|
||||||
cross_product_next = cross_product_next / np.linalg.norm(cross_product_next)
|
|
||||||
alpha = Polygon._angle_between_vectors(vector_1, vector_2)
|
|
||||||
else:
|
|
||||||
cross_product_next = [0, 0, 0]
|
|
||||||
alpha = 0
|
|
||||||
delta_normals = 0
|
|
||||||
for j in range(0, 3):
|
|
||||||
delta_normals += cross_product[j] - cross_product_next[j]
|
|
||||||
if np.abs(delta_normals) < accepted_normal_difference:
|
|
||||||
return alpha
|
|
||||||
return -alpha
|
|
||||||
|
|
||||||
def triangulate(self) -> List[Polygon]:
|
|
||||||
"""
|
|
||||||
triangulates a polygon following the ear clipping methodology
|
|
||||||
:return: list[triangles]
|
|
||||||
"""
|
|
||||||
# todo: review triangulate_polygon in
|
|
||||||
# https://github.com/mikedh/trimesh/blob/dad11126742e140ef46ba12f8cb8643c83356467/trimesh/creation.py#L415,
|
|
||||||
# it had a problem with a class called 'triangle', but, if solved,
|
|
||||||
# it could be a very good substitute of this method
|
|
||||||
# this method is very dirty and has an infinite loop solved with a counter!!
|
|
||||||
if self._triangles is None:
|
|
||||||
points_list = self.points_list
|
|
||||||
normal = self.normal
|
|
||||||
if np.linalg.norm(normal) == 0:
|
|
||||||
sys.stderr.write('Not able to triangulate polygon\n')
|
|
||||||
return [self]
|
|
||||||
# are points concave or convex?
|
|
||||||
total_points_list, concave_points, convex_points = self._starting_lists(points_list, normal)
|
|
||||||
|
|
||||||
# list of ears
|
|
||||||
ears = []
|
|
||||||
j = 0
|
|
||||||
while (len(concave_points) > 3 or len(convex_points) != 0) and j < 100:
|
|
||||||
j += 1
|
|
||||||
for i in range(0, len(concave_points)):
|
|
||||||
ear = self._triangle(points_list, total_points_list, concave_points[i])
|
|
||||||
rest_points = []
|
|
||||||
for points in total_points_list:
|
|
||||||
rest_points.append(list(self.coordinates[points]))
|
|
||||||
if self._is_ear(ear, rest_points):
|
|
||||||
ears.append(ear)
|
|
||||||
point_to_remove = concave_points[i]
|
|
||||||
previous_point_in_list, next_point_in_list = self._enveloping_points(point_to_remove, total_points_list)
|
|
||||||
total_points_list.remove(point_to_remove)
|
|
||||||
concave_points.remove(point_to_remove)
|
|
||||||
# Was any of the adjacent points convex? -> check if changed status to concave
|
|
||||||
for convex_point in convex_points:
|
|
||||||
if convex_point == previous_point_in_list:
|
|
||||||
concave_points, convex_points, end_loop = self._if_concave_change_status(normal, points_list,
|
|
||||||
convex_point,
|
|
||||||
total_points_list,
|
|
||||||
concave_points, convex_points,
|
|
||||||
previous_point_in_list)
|
|
||||||
if end_loop:
|
|
||||||
break
|
|
||||||
continue
|
|
||||||
if convex_point == next_point_in_list:
|
|
||||||
concave_points, convex_points, end_loop = self._if_concave_change_status(normal, points_list,
|
|
||||||
convex_point,
|
|
||||||
total_points_list,
|
|
||||||
concave_points, convex_points,
|
|
||||||
next_point_in_list)
|
|
||||||
if end_loop:
|
|
||||||
break
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
if len(total_points_list) <= 3 and len(convex_points) > 0:
|
|
||||||
sys.stderr.write('Not able to triangulate polygon\n')
|
|
||||||
return [self]
|
|
||||||
if j >= 100:
|
|
||||||
sys.stderr.write('Not able to triangulate polygon\n')
|
|
||||||
return [self]
|
|
||||||
last_ear = self._triangle(points_list, total_points_list, concave_points[1])
|
|
||||||
ears.append(last_ear)
|
|
||||||
self._triangles = ears
|
|
||||||
return self._triangles
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _starting_lists(points_list, normal) -> [List[float], List[float], List[float]]:
|
|
||||||
"""
|
|
||||||
creates the list of vertices (points) that define the polygon (total_points_list), together with other two lists
|
|
||||||
separating points between convex and concave
|
|
||||||
:param points_list: points_list
|
|
||||||
:param normal: normal
|
|
||||||
:return: list[point], list[point], list[point]
|
|
||||||
"""
|
|
||||||
concave_points = []
|
|
||||||
convex_points = []
|
|
||||||
# lists of concave and convex points
|
|
||||||
# case 1: first point
|
|
||||||
point = points_list[0:3]
|
|
||||||
previous_point = points_list[len(points_list) - 3:]
|
|
||||||
next_point = points_list[3:6]
|
|
||||||
index = 0
|
|
||||||
total_points_list = [index]
|
|
||||||
if Polygon._point_is_concave(normal, point, previous_point, next_point):
|
|
||||||
concave_points.append(index)
|
|
||||||
else:
|
|
||||||
convex_points.append(index)
|
|
||||||
# case 2: all points except first and last
|
|
||||||
for i in range(0, int((len(points_list)-6)/3)):
|
|
||||||
point = points_list[(i+1)*3:(i+2)*3]
|
|
||||||
previous_point = points_list[i*3:(i+1)*3]
|
|
||||||
next_point = points_list[(i+2)*3:(i+3)*3]
|
|
||||||
index = i+1
|
|
||||||
total_points_list.append(index)
|
|
||||||
if Polygon._point_is_concave(normal, point, previous_point, next_point):
|
|
||||||
concave_points.append(index)
|
|
||||||
else:
|
|
||||||
convex_points.append(index)
|
|
||||||
# case 3: last point
|
|
||||||
point = points_list[len(points_list) - 3:]
|
|
||||||
previous_point = points_list[len(points_list) - 6:len(points_list) - 3]
|
|
||||||
next_point = points_list[0:3]
|
|
||||||
index = int(len(points_list)/3) - 1
|
|
||||||
total_points_list.append(index)
|
|
||||||
if Polygon._point_is_concave(normal, point, previous_point, next_point):
|
|
||||||
concave_points.append(index)
|
|
||||||
else:
|
|
||||||
convex_points.append(index)
|
|
||||||
return total_points_list, concave_points, convex_points
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _triangle(points_list, total_points_list, point_position) -> Polygon:
|
|
||||||
"""
|
|
||||||
creates a triangular polygon out of three points
|
|
||||||
:param points_list: points_list
|
|
||||||
:param total_points_list: [point]
|
|
||||||
:param point_position: int
|
|
||||||
:return: polygon
|
|
||||||
"""
|
|
||||||
index = point_position * 3
|
|
||||||
previous_point_index, next_point_index = Polygon._enveloping_points_indices(point_position, total_points_list)
|
|
||||||
points = points_list[previous_point_index:previous_point_index + 3]
|
|
||||||
points = np.append(points, points_list[index:index + 3])
|
|
||||||
points = np.append(points, points_list[next_point_index:next_point_index + 3])
|
|
||||||
rows = points.size // 3
|
|
||||||
points = points.reshape(rows, 3)
|
|
||||||
triangle = Polygon(points)
|
|
||||||
return triangle
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _enveloping_points_indices(point_position, total_points_list):
|
|
||||||
"""
|
|
||||||
due to the fact that the lists are not circular, a method to find the previous and next points
|
|
||||||
of an specific one is needed
|
|
||||||
:param point_position: int
|
|
||||||
:param total_points_list: [point]
|
|
||||||
:return: int, int
|
|
||||||
"""
|
|
||||||
previous_point_index = None
|
|
||||||
next_point_index = None
|
|
||||||
if point_position == total_points_list[0]:
|
|
||||||
previous_point_index = total_points_list[len(total_points_list) - 1] * 3
|
|
||||||
next_point_index = total_points_list[1] * 3
|
|
||||||
if point_position == total_points_list[len(total_points_list) - 1]:
|
|
||||||
previous_point_index = total_points_list[len(total_points_list) - 2] * 3
|
|
||||||
next_point_index = total_points_list[0] * 3
|
|
||||||
for i in range(1, len(total_points_list)-1):
|
|
||||||
if point_position == total_points_list[i]:
|
|
||||||
previous_point_index = total_points_list[i - 1] * 3
|
|
||||||
next_point_index = total_points_list[i + 1] * 3
|
|
||||||
return previous_point_index, next_point_index
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _enveloping_points(point_to_remove, total_points_list):
|
|
||||||
"""
|
|
||||||
due to the fact that the lists are not circular, a method to find the previous and next points
|
|
||||||
of an specific one is needed
|
|
||||||
:param point_to_remove: point
|
|
||||||
:param total_points_list: [point]
|
|
||||||
:return: point, point
|
|
||||||
"""
|
|
||||||
index = total_points_list.index(point_to_remove)
|
|
||||||
if index == 0:
|
|
||||||
previous_point_in_list = total_points_list[len(total_points_list) - 1]
|
|
||||||
next_point_in_list = total_points_list[1]
|
|
||||||
elif index == len(total_points_list) - 1:
|
|
||||||
previous_point_in_list = total_points_list[len(total_points_list) - 2]
|
|
||||||
next_point_in_list = total_points_list[0]
|
|
||||||
else:
|
|
||||||
previous_point_in_list = total_points_list[index - 1]
|
|
||||||
next_point_in_list = total_points_list[index + 1]
|
|
||||||
return previous_point_in_list, next_point_in_list
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _is_ear(ear, points) -> bool:
|
|
||||||
"""
|
|
||||||
finds whether a triangle is an ear of the polygon
|
|
||||||
:param ear: polygon
|
|
||||||
:param points: [point]
|
|
||||||
:return: boolean
|
|
||||||
"""
|
|
||||||
area_ear = ear.area
|
|
||||||
for point in points:
|
|
||||||
area_points = 0
|
|
||||||
point_is_not_vertex = True
|
|
||||||
for i in range(0, 3):
|
|
||||||
if abs(np.linalg.norm(point) - np.linalg.norm(ear.coordinates[i])) < 0.0001:
|
|
||||||
point_is_not_vertex = False
|
|
||||||
break
|
|
||||||
if point_is_not_vertex:
|
|
||||||
for i in range(0, 3):
|
|
||||||
if i != 2:
|
|
||||||
new_points = ear.coordinates[i][:]
|
|
||||||
new_points = np.append(new_points, ear.coordinates[i + 1][:])
|
|
||||||
new_points = np.append(new_points, point[:])
|
|
||||||
else:
|
|
||||||
new_points = ear.coordinates[i][:]
|
|
||||||
new_points = np.append(new_points, point[:])
|
|
||||||
new_points = np.append(new_points, ear.coordinates[0][:])
|
|
||||||
rows = new_points.size // 3
|
|
||||||
new_points = new_points.reshape(rows, 3)
|
|
||||||
new_triangle = Polygon(new_points)
|
|
||||||
area_points += new_triangle.area
|
|
||||||
if abs(area_points - area_ear) < 1e-6:
|
|
||||||
# point_inside_ear = True
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _if_concave_change_status(normal, points_list, convex_point, total_points_list,
|
|
||||||
concave_points, convex_points, point_in_list) -> [List[float], List[float], bool]:
|
|
||||||
"""
|
|
||||||
checks whether an convex specific point change its status to concave after removing one ear in the polygon
|
|
||||||
returning the new convex and concave points lists together with a flag advising that the list of total points
|
|
||||||
already 3 and, therefore, the triangulation must be finished.
|
|
||||||
:param normal: normal
|
|
||||||
:param points_list: points_list
|
|
||||||
:param convex_point: int
|
|
||||||
:param total_points_list: [point]
|
|
||||||
:param concave_points: [point]
|
|
||||||
:param convex_points: [point]
|
|
||||||
:param point_in_list: int
|
|
||||||
:return: list[points], list[points], boolean
|
|
||||||
"""
|
|
||||||
end_loop = False
|
|
||||||
point = points_list[point_in_list * 3:(point_in_list + 1) * 3]
|
|
||||||
pointer = total_points_list.index(point_in_list) - 1
|
|
||||||
if pointer < 0:
|
|
||||||
pointer = len(total_points_list) - 1
|
|
||||||
previous_point = points_list[total_points_list[pointer] * 3:total_points_list[pointer] * 3 + 3]
|
|
||||||
pointer = total_points_list.index(point_in_list) + 1
|
|
||||||
if pointer >= len(total_points_list):
|
|
||||||
pointer = 0
|
|
||||||
next_point = points_list[total_points_list[pointer] * 3:total_points_list[pointer] * 3 + 3]
|
|
||||||
if Polygon._point_is_concave(normal, point, previous_point, next_point):
|
|
||||||
if concave_points[0] > convex_point:
|
|
||||||
concave_points.insert(0, convex_point)
|
|
||||||
elif concave_points[len(concave_points) - 1] < convex_point:
|
|
||||||
concave_points.append(convex_point)
|
|
||||||
else:
|
|
||||||
for point_index in range(0, len(concave_points) - 1):
|
|
||||||
if concave_points[point_index] < convex_point < concave_points[point_index + 1]:
|
|
||||||
concave_points.insert(point_index + 1, convex_point)
|
|
||||||
convex_points.remove(convex_point)
|
|
||||||
end_loop = True
|
|
||||||
return concave_points, convex_points, end_loop
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _point_is_concave(normal, point, previous_point, next_point) -> bool:
|
|
||||||
"""
|
|
||||||
returns whether a point is concave
|
|
||||||
:param normal: normal
|
|
||||||
:param point: point
|
|
||||||
:param previous_point: point
|
|
||||||
:param next_point: point
|
|
||||||
:return: boolean
|
|
||||||
"""
|
|
||||||
is_concave = False
|
|
||||||
accepted_error = 0.1
|
|
||||||
points = np.append(previous_point, point)
|
|
||||||
points = np.append(points, next_point)
|
|
||||||
rows = points.size // 3
|
|
||||||
points = points.reshape(rows, 3)
|
|
||||||
triangle = Polygon(points)
|
|
||||||
error_sum = 0
|
|
||||||
for i in range(0, len(normal)):
|
|
||||||
error_sum += triangle.normal[i] - normal[i]
|
|
||||||
if np.abs(error_sum) < accepted_error:
|
|
||||||
is_concave = True
|
|
||||||
return is_concave
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _angle_between_vectors(vec_1, vec_2):
|
|
||||||
"""
|
|
||||||
angle between vectors in radians
|
|
||||||
:param vec_1: vector
|
|
||||||
:param vec_2: vector
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if np.linalg.norm(vec_1) == 0 or np.linalg.norm(vec_2) == 0:
|
|
||||||
sys.stderr.write("Warning: impossible to calculate angle between planes' normal. Return 0\n")
|
|
||||||
return 0
|
|
||||||
cosine = np.dot(vec_1, vec_2) / np.linalg.norm(vec_1) / np.linalg.norm(vec_2)
|
|
||||||
if cosine > 1 and cosine-1 < 1e-5:
|
|
||||||
cosine = 1
|
|
||||||
elif cosine < -1 and cosine+1 > -1e-5:
|
|
||||||
cosine = -1
|
|
||||||
alpha = math.acos(cosine)
|
|
||||||
return alpha
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inverse(self):
|
|
||||||
"""
|
|
||||||
Flips the order of the coordinates
|
|
||||||
:return: [np.ndarray]
|
|
||||||
"""
|
|
||||||
if self._inverse is None:
|
|
||||||
self._inverse = self.coordinates[::-1]
|
|
||||||
return self._inverse
|
|
||||||
|
|
||||||
def divide(self, plane):
|
|
||||||
"""
|
|
||||||
Divides the polygon in two by a plane
|
|
||||||
:param plane: plane that intersects with self to divide it in two parts (Plane)
|
|
||||||
:return: Polygon, Polygon, [Point]
|
|
||||||
"""
|
|
||||||
tri_polygons = Trimesh(vertices=self.vertices, faces=self.faces)
|
|
||||||
intersection = trimesh.intersections.mesh_plane(tri_polygons, plane.normal, plane.origin.coordinates)
|
|
||||||
polys_1 = trimesh.intersections.slice_mesh_plane(tri_polygons, plane.opposite_normal, plane.origin.coordinates)
|
|
||||||
polys_2 = trimesh.intersections.slice_mesh_plane(tri_polygons, plane.normal, plane.origin.coordinates)
|
|
||||||
triangles_1 = []
|
|
||||||
for triangle in polys_1.triangles:
|
|
||||||
triangles_1.append(Polygon(triangle))
|
|
||||||
polygon_1 = self._reshape(triangles_1)
|
|
||||||
triangles_2 = []
|
|
||||||
for triangle in polys_2.triangles:
|
|
||||||
triangles_2.append(Polygon(triangle))
|
|
||||||
polygon_2 = self._reshape(triangles_2)
|
|
||||||
return polygon_1, polygon_2, intersection
|
|
||||||
|
|
||||||
def _reshape(self, triangles) -> Polygon:
|
|
||||||
edges_list = []
|
|
||||||
for i in range(0, len(triangles)):
|
|
||||||
for edge in triangles[i].edges:
|
|
||||||
if not self._edge_in_edges_list(edge, edges_list):
|
|
||||||
edges_list.append(edge)
|
|
||||||
else:
|
|
||||||
edges_list = self._remove_from_list(edge, edges_list)
|
|
||||||
points = self._order_points(edges_list)
|
|
||||||
return Polygon(points)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _edge_in_edges_list(edge, edges_list):
|
|
||||||
for edge_element in edges_list:
|
|
||||||
if (edge_element[0].distance_to_point(edge[0]) == 0 and edge_element[1].distance_to_point(edge[1]) == 0) or\
|
|
||||||
(edge_element[1].distance_to_point(edge[0]) == 0 and edge_element[0].distance_to_point(edge[1]) == 0):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _order_points(edges_list):
|
|
||||||
# todo: not sure that this method works for any case -> RECHECK
|
|
||||||
points = edges_list[0]
|
|
||||||
for _ in range(0, len(points)):
|
|
||||||
for i in range(1, len(edges_list)):
|
|
||||||
point_1 = edges_list[i][0]
|
|
||||||
point_2 = points[len(points)-1]
|
|
||||||
if point_1.distance_to_point(point_2) == 0:
|
|
||||||
points.append(edges_list[i][1])
|
|
||||||
points.remove(points[len(points)-1])
|
|
||||||
array_points = []
|
|
||||||
for point in points:
|
|
||||||
array_points.append(point.coordinates)
|
|
||||||
return np.array(array_points)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _remove_from_list(edge, edges_list):
|
|
||||||
new_list = []
|
|
||||||
for edge_element in edges_list:
|
|
||||||
if not((edge_element[0].distance_to_point(edge[0]) == 0 and edge_element[1].distance_to_point(edge[1]) == 0) or
|
|
||||||
(edge_element[1].distance_to_point(edge[0]) == 0 and edge_element[0].distance_to_point(edge[1]) == 0)):
|
|
||||||
new_list.append(edge_element)
|
|
||||||
return new_list
|
|
||||||
|
|
||||||
@property
|
|
||||||
def vertices(self) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
Polyhedron vertices
|
|
||||||
:return: np.ndarray(int)
|
|
||||||
"""
|
|
||||||
if self._vertices is None:
|
|
||||||
vertices, self._vertices = [], []
|
|
||||||
_ = [vertices.extend(s.coordinates) for s in self.triangulate()]
|
|
||||||
for vertex_1 in vertices:
|
|
||||||
found = False
|
|
||||||
for vertex_2 in self._vertices:
|
|
||||||
found = False
|
|
||||||
power = 0
|
|
||||||
for dimension in range(0, 3):
|
|
||||||
power += math.pow(vertex_2[dimension] - vertex_1[dimension], 2)
|
|
||||||
distance = math.sqrt(power)
|
|
||||||
if distance == 0:
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
self._vertices.append(vertex_1)
|
|
||||||
self._vertices = np.asarray(self._vertices)
|
|
||||||
return self._vertices
|
|
||||||
|
|
||||||
@property
|
|
||||||
def faces(self) -> List[List[int]]:
|
|
||||||
"""
|
|
||||||
Polyhedron triangular faces
|
|
||||||
:return: [face]
|
|
||||||
"""
|
|
||||||
if self._faces is None:
|
|
||||||
self._faces = []
|
|
||||||
|
|
||||||
for polygon in self.triangulate():
|
|
||||||
face = []
|
|
||||||
points = polygon.coordinates
|
|
||||||
if len(points) != 3:
|
|
||||||
sub_polygons = polygon.triangulate()
|
|
||||||
# todo: I modified this! To be checked @Guille
|
|
||||||
if len(sub_polygons) >= 1:
|
|
||||||
for sub_polygon in sub_polygons:
|
|
||||||
face = []
|
|
||||||
points = sub_polygon.coordinates
|
|
||||||
for point in points:
|
|
||||||
face.append(self._position_of(point, face))
|
|
||||||
self._faces.append(face)
|
|
||||||
else:
|
|
||||||
for point in points:
|
|
||||||
face.append(self._position_of(point, face))
|
|
||||||
self._faces.append(face)
|
|
||||||
return self._faces
|
|
||||||
|
|
||||||
def _position_of(self, point, face):
|
|
||||||
"""
|
|
||||||
position of a specific point in the list of points that define a face
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
vertices = self.vertices
|
|
||||||
for i in range(len(vertices)):
|
|
||||||
# ensure not duplicated vertex
|
|
||||||
power = 0
|
|
||||||
vertex2 = vertices[i]
|
|
||||||
for dimension in range(0, 3):
|
|
||||||
power += math.pow(vertex2[dimension] - point[dimension], 2)
|
|
||||||
distance = math.sqrt(power)
|
|
||||||
if i not in face and distance == 0:
|
|
||||||
return i
|
|
||||||
return -1
|
|
|
@ -1,252 +0,0 @@
|
||||||
"""
|
|
||||||
Polyhedron module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List, Union
|
|
||||||
import sys
|
|
||||||
import math
|
|
||||||
import numpy as np
|
|
||||||
from trimesh import Trimesh
|
|
||||||
from helpers.configuration_helper import ConfigurationHelper
|
|
||||||
|
|
||||||
|
|
||||||
class Polyhedron:
|
|
||||||
"""
|
|
||||||
Polyhedron class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, polygons):
|
|
||||||
self._polygons = polygons
|
|
||||||
self._polyhedron = None
|
|
||||||
self._triangulated_polyhedron = None
|
|
||||||
self._volume = None
|
|
||||||
self._faces = None
|
|
||||||
self._vertices = None
|
|
||||||
self._trimesh = None
|
|
||||||
self._centroid = None
|
|
||||||
self._max_z = None
|
|
||||||
self._max_y = None
|
|
||||||
self._max_x = None
|
|
||||||
self._min_z = None
|
|
||||||
self._min_y = None
|
|
||||||
self._min_x = None
|
|
||||||
|
|
||||||
def _position_of(self, point, face):
|
|
||||||
"""
|
|
||||||
position of a specific point in the list of points that define a face
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
vertices = self.vertices
|
|
||||||
for i in range(len(vertices)):
|
|
||||||
# ensure not duplicated vertex
|
|
||||||
power = 0
|
|
||||||
vertex2 = vertices[i]
|
|
||||||
for dimension in range(0, 3):
|
|
||||||
power += math.pow(vertex2[dimension] - point[dimension], 2)
|
|
||||||
distance = math.sqrt(power)
|
|
||||||
if i not in face and distance == 0:
|
|
||||||
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.coordinates) for s in self._polygons]
|
|
||||||
for vertex_1 in vertices:
|
|
||||||
found = False
|
|
||||||
for vertex_2 in self._vertices:
|
|
||||||
found = False
|
|
||||||
power = 0
|
|
||||||
for dimension in range(0, 3):
|
|
||||||
power += math.pow(vertex_2[dimension] - vertex_1[dimension], 2)
|
|
||||||
distance = math.sqrt(power)
|
|
||||||
if distance == 0:
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
self._vertices.append(vertex_1)
|
|
||||||
self._vertices = np.asarray(self._vertices)
|
|
||||||
return self._vertices
|
|
||||||
|
|
||||||
@property
|
|
||||||
def faces(self) -> List[List[int]]:
|
|
||||||
"""
|
|
||||||
Polyhedron triangular faces
|
|
||||||
:return: [face]
|
|
||||||
"""
|
|
||||||
if self._faces is None:
|
|
||||||
self._faces = []
|
|
||||||
|
|
||||||
for polygon in self._polygons:
|
|
||||||
|
|
||||||
face = []
|
|
||||||
points = polygon.coordinates
|
|
||||||
if len(points) != 3:
|
|
||||||
sub_polygons = polygon.triangulate()
|
|
||||||
# todo: I modified this! To be checked @Guille
|
|
||||||
if len(sub_polygons) >= 1:
|
|
||||||
for sub_polygon in sub_polygons:
|
|
||||||
face = []
|
|
||||||
points = sub_polygon.coordinates
|
|
||||||
for point in points:
|
|
||||||
face.append(self._position_of(point, face))
|
|
||||||
self._faces.append(face)
|
|
||||||
else:
|
|
||||||
for point in points:
|
|
||||||
face.append(self._position_of(point, face))
|
|
||||||
self._faces.append(face)
|
|
||||||
return self._faces
|
|
||||||
|
|
||||||
@property
|
|
||||||
def trimesh(self) -> Union[Trimesh, None]:
|
|
||||||
"""
|
|
||||||
Get trimesh
|
|
||||||
:return: Trimesh
|
|
||||||
"""
|
|
||||||
if self._trimesh is None:
|
|
||||||
for face in self.faces:
|
|
||||||
if len(face) != 3:
|
|
||||||
sys.stderr.write('Not able to generate trimesh\n')
|
|
||||||
return None
|
|
||||||
self._trimesh = Trimesh(vertices=self.vertices, faces=self.faces)
|
|
||||||
return self._trimesh
|
|
||||||
|
|
||||||
@property
|
|
||||||
def volume(self):
|
|
||||||
"""
|
|
||||||
Polyhedron volume in cubic meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._volume is None:
|
|
||||||
if self.trimesh is None:
|
|
||||||
self._volume = np.inf
|
|
||||||
elif not self.trimesh.is_volume:
|
|
||||||
self._volume = np.inf
|
|
||||||
else:
|
|
||||||
self._volume = self.trimesh.volume
|
|
||||||
return self._volume
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_z(self):
|
|
||||||
"""
|
|
||||||
Polyhedron maximal z value in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._max_z is None:
|
|
||||||
self._max_z = ConfigurationHelper().min_coordinate
|
|
||||||
for polygon in self._polygons:
|
|
||||||
for point in polygon.coordinates:
|
|
||||||
self._max_z = max(self._max_z, point[2])
|
|
||||||
return self._max_z
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_y(self):
|
|
||||||
"""
|
|
||||||
Polyhedron maximal y value in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._max_y is None:
|
|
||||||
self._max_y = ConfigurationHelper().min_coordinate
|
|
||||||
for polygon in self._polygons:
|
|
||||||
for point in polygon.coordinates:
|
|
||||||
if self._max_y < point[1]:
|
|
||||||
self._max_y = point[1]
|
|
||||||
return self._max_y
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_x(self):
|
|
||||||
"""
|
|
||||||
Polyhedron maximal x value in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._max_x is None:
|
|
||||||
self._max_x = ConfigurationHelper().min_coordinate
|
|
||||||
for polygon in self._polygons:
|
|
||||||
for point in polygon.coordinates:
|
|
||||||
self._max_x = max(self._max_x, point[0])
|
|
||||||
return self._max_x
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_z(self):
|
|
||||||
"""
|
|
||||||
Polyhedron minimal z value in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._min_z is None:
|
|
||||||
self._min_z = self.max_z
|
|
||||||
for polygon in self._polygons:
|
|
||||||
for point in polygon.coordinates:
|
|
||||||
if self._min_z > point[2]:
|
|
||||||
self._min_z = point[2]
|
|
||||||
return self._min_z
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_y(self):
|
|
||||||
"""
|
|
||||||
Polyhedron minimal y value in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._min_y is None:
|
|
||||||
self._min_y = self.max_y
|
|
||||||
for polygon in self._polygons:
|
|
||||||
for point in polygon.coordinates:
|
|
||||||
if self._min_y > point[1]:
|
|
||||||
self._min_y = point[1]
|
|
||||||
return self._min_y
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_x(self):
|
|
||||||
"""
|
|
||||||
Polyhedron minimal x value in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._min_x is None:
|
|
||||||
self._min_x = self.max_x
|
|
||||||
for polygon in self._polygons:
|
|
||||||
for point in polygon.coordinates:
|
|
||||||
if self._min_x > point[0]:
|
|
||||||
self._min_x = point[0]
|
|
||||||
return self._min_x
|
|
||||||
|
|
||||||
@property
|
|
||||||
def centroid(self):
|
|
||||||
"""
|
|
||||||
Polyhedron centroid
|
|
||||||
:return: [x,y,z]
|
|
||||||
"""
|
|
||||||
if self._centroid is None:
|
|
||||||
trimesh = self.trimesh
|
|
||||||
if trimesh is None:
|
|
||||||
return None
|
|
||||||
self._centroid = self.trimesh.centroid
|
|
||||||
return self._centroid
|
|
||||||
|
|
||||||
def stl_export(self, full_path):
|
|
||||||
"""
|
|
||||||
Export the polyhedron to stl given file
|
|
||||||
:param full_path: str
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self.trimesh.export(full_path, 'stl_ascii')
|
|
||||||
|
|
||||||
def obj_export(self, full_path):
|
|
||||||
"""
|
|
||||||
Export the polyhedron to obj given file
|
|
||||||
:param full_path: str
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self.trimesh.export(full_path, 'obj')
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
"""
|
|
||||||
Auxiliary function to render the polyhedron
|
|
||||||
"""
|
|
||||||
self.trimesh.show()
|
|
|
@ -1,31 +0,0 @@
|
||||||
"""
|
|
||||||
Building module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleValue:
|
|
||||||
"""
|
|
||||||
Schedule Values class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, hour, probability):
|
|
||||||
self._hour = hour
|
|
||||||
self._probability = probability
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hour(self):
|
|
||||||
"""
|
|
||||||
Get hours
|
|
||||||
:return: hour of a day
|
|
||||||
"""
|
|
||||||
return self._hour
|
|
||||||
|
|
||||||
@property
|
|
||||||
def probability(self):
|
|
||||||
"""
|
|
||||||
Get probabilities of occupants' presence
|
|
||||||
:return: occupants' presence probabilities
|
|
||||||
"""
|
|
||||||
return self._probability
|
|
|
@ -1,33 +0,0 @@
|
||||||
"""
|
|
||||||
bixi_feature module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
|
|
||||||
|
|
||||||
class BixiFeature(CityObject):
|
|
||||||
"""
|
|
||||||
BixiFeature(CityObject) class
|
|
||||||
"""
|
|
||||||
def __init__(self, lod, surfaces, name, feature_type, coordinates):
|
|
||||||
super().__init__(lod, surfaces, name, [])
|
|
||||||
self._feature_type = feature_type
|
|
||||||
self._coordinates = coordinates
|
|
||||||
self._type = 'bixi_feature'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def feature_type(self):
|
|
||||||
"""
|
|
||||||
Get type of bixi feature
|
|
||||||
:return: feature_type
|
|
||||||
"""
|
|
||||||
return self._feature_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gps_coordinates(self):
|
|
||||||
"""
|
|
||||||
Get bixi feature coordinates
|
|
||||||
:return: [x, y, z]
|
|
||||||
"""
|
|
||||||
return self._coordinates
|
|
|
@ -1,353 +0,0 @@
|
||||||
"""
|
|
||||||
Building module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
import numpy as np
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
|
||||||
from city_model_structure.building_demand.thermal_boundary import ThermalBoundary
|
|
||||||
from city_model_structure.building_demand.usage_zone import UsageZone
|
|
||||||
from city_model_structure.building_demand.storey import Storey
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
|
|
||||||
|
|
||||||
class Building(CityObject):
|
|
||||||
"""
|
|
||||||
Building(CityObject) class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, lod, surfaces, year_of_construction, function,
|
|
||||||
city_lower_corner, terrains=None):
|
|
||||||
super().__init__(name, lod, surfaces, city_lower_corner)
|
|
||||||
self._basement_heated = None
|
|
||||||
self._attic_heated = None
|
|
||||||
self._terrains = terrains
|
|
||||||
self._year_of_construction = year_of_construction
|
|
||||||
self._function = function
|
|
||||||
self._average_storey_height = None
|
|
||||||
self._storeys_above_ground = None
|
|
||||||
self._floor_area = None
|
|
||||||
self._roof_type = None
|
|
||||||
self._storeys = None
|
|
||||||
self._thermal_zones = []
|
|
||||||
self._thermal_boundaries = None
|
|
||||||
self._usage_zones = []
|
|
||||||
self._type = 'building'
|
|
||||||
self._heating = dict()
|
|
||||||
self._cooling = dict()
|
|
||||||
self._eave_height = None
|
|
||||||
self._grounds = []
|
|
||||||
self._roofs = []
|
|
||||||
self._walls = []
|
|
||||||
self._internal_walls = []
|
|
||||||
for surface_id, surface in enumerate(self.surfaces):
|
|
||||||
self._min_x = min(self._min_x, surface.lower_corner[0])
|
|
||||||
self._min_y = min(self._min_y, surface.lower_corner[1])
|
|
||||||
self._min_z = min(self._min_z, surface.lower_corner[2])
|
|
||||||
surface.id = surface_id
|
|
||||||
# todo: consider all type of surfaces, not only these four
|
|
||||||
if surface.type == 'Ground':
|
|
||||||
self._grounds.append(surface)
|
|
||||||
elif surface.type == 'Wall':
|
|
||||||
self._walls.append(surface)
|
|
||||||
elif surface.type == 'Roof':
|
|
||||||
self._roofs.append(surface)
|
|
||||||
else:
|
|
||||||
self._internal_walls.append(surface)
|
|
||||||
|
|
||||||
self._pv_plus_hp_installation = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def grounds(self) -> [Surface]:
|
|
||||||
"""
|
|
||||||
Building ground surfaces
|
|
||||||
"""
|
|
||||||
return self._grounds
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_heated(self):
|
|
||||||
"""
|
|
||||||
Get building heated flag
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
for usage_zone in self.usage_zones:
|
|
||||||
if usage_zone.is_heated:
|
|
||||||
return usage_zone.is_heated
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_cooled(self):
|
|
||||||
"""
|
|
||||||
Get building cooled flag
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
for usage_zone in self.usage_zones:
|
|
||||||
if usage_zone.is_cooled:
|
|
||||||
return usage_zone.is_cooled
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def roofs(self) -> [Surface]:
|
|
||||||
"""
|
|
||||||
Building roof surfaces
|
|
||||||
"""
|
|
||||||
return self._roofs
|
|
||||||
|
|
||||||
@property
|
|
||||||
def walls(self) -> [Surface]:
|
|
||||||
"""
|
|
||||||
Building wall surfaces
|
|
||||||
"""
|
|
||||||
return self._walls
|
|
||||||
|
|
||||||
@property
|
|
||||||
def usage_zones(self) -> List[UsageZone]:
|
|
||||||
"""
|
|
||||||
Get city object usage zones
|
|
||||||
:return: [UsageZone]
|
|
||||||
"""
|
|
||||||
if len(self._usage_zones) == 0:
|
|
||||||
for thermal_zone in self.thermal_zones:
|
|
||||||
self._usage_zones.extend(thermal_zone.usage_zones)
|
|
||||||
return self._usage_zones
|
|
||||||
|
|
||||||
@property
|
|
||||||
def terrains(self) -> List[Surface]:
|
|
||||||
"""
|
|
||||||
Get city object terrain surfaces
|
|
||||||
:return: [Surface]
|
|
||||||
"""
|
|
||||||
return self._terrains
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attic_heated(self):
|
|
||||||
"""
|
|
||||||
Get if the city object attic is heated
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
return self._attic_heated
|
|
||||||
|
|
||||||
@attic_heated.setter
|
|
||||||
def attic_heated(self, value):
|
|
||||||
"""
|
|
||||||
Set if the city object attic is heated
|
|
||||||
:param value: Boolean
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._attic_heated = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def basement_heated(self):
|
|
||||||
"""
|
|
||||||
Get if the city object basement is heated
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
return self._basement_heated
|
|
||||||
|
|
||||||
@basement_heated.setter
|
|
||||||
def basement_heated(self, value):
|
|
||||||
"""
|
|
||||||
Set if the city object basement is heated
|
|
||||||
:param value: Boolean
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._basement_heated = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
City object name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_zones(self) -> List[ThermalZone]:
|
|
||||||
"""
|
|
||||||
City object thermal zones
|
|
||||||
:return: [ThermalZone]
|
|
||||||
"""
|
|
||||||
if len(self._thermal_zones) == 0:
|
|
||||||
for storey in self.storeys:
|
|
||||||
self._thermal_zones.append(storey.thermal_zone)
|
|
||||||
return self._thermal_zones
|
|
||||||
|
|
||||||
@property
|
|
||||||
def heated_volume(self):
|
|
||||||
"""
|
|
||||||
City object heated volume in cubic meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
# ToDo: this need to be calculated based on the basement and attic heated values
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def year_of_construction(self):
|
|
||||||
"""
|
|
||||||
City object year of construction
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._year_of_construction
|
|
||||||
|
|
||||||
@property
|
|
||||||
def function(self):
|
|
||||||
"""
|
|
||||||
City object function
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._function
|
|
||||||
|
|
||||||
@function.setter
|
|
||||||
def function(self, value):
|
|
||||||
"""
|
|
||||||
Set building function
|
|
||||||
:param value: string
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._function = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def average_storey_height(self):
|
|
||||||
"""
|
|
||||||
Get city object average storey height in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._average_storey_height
|
|
||||||
|
|
||||||
@average_storey_height.setter
|
|
||||||
def average_storey_height(self, value):
|
|
||||||
"""
|
|
||||||
Set city object average storey height in meters
|
|
||||||
:param value: float
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._average_storey_height = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def storeys_above_ground(self):
|
|
||||||
"""
|
|
||||||
Get city object storeys number above ground
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._storeys_above_ground
|
|
||||||
|
|
||||||
@storeys_above_ground.setter
|
|
||||||
def storeys_above_ground(self, value):
|
|
||||||
"""
|
|
||||||
Set city object storeys number above ground
|
|
||||||
:param value: int
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self._storeys_above_ground = value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _tuple_to_point(xy_tuple):
|
|
||||||
return [xy_tuple[0], xy_tuple[1], 0.0]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def heating(self) -> dict:
|
|
||||||
"""
|
|
||||||
heating demand in Wh
|
|
||||||
:return: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
return self._heating
|
|
||||||
|
|
||||||
@heating.setter
|
|
||||||
def heating(self, value):
|
|
||||||
"""
|
|
||||||
heating demand in Wh
|
|
||||||
:param value: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
self._heating = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cooling(self) -> dict:
|
|
||||||
"""
|
|
||||||
cooling demand in Wh
|
|
||||||
:return: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
return self._cooling
|
|
||||||
|
|
||||||
@cooling.setter
|
|
||||||
def cooling(self, value):
|
|
||||||
"""
|
|
||||||
cooling demand in Wh
|
|
||||||
:param value: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
self._cooling = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def eave_height(self):
|
|
||||||
"""
|
|
||||||
building eave height in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._eave_height is None:
|
|
||||||
self._eave_height = 0
|
|
||||||
for wall in self.walls:
|
|
||||||
self._eave_height = max(self._eave_height, wall.upper_corner[2])
|
|
||||||
return self._eave_height
|
|
||||||
|
|
||||||
@property
|
|
||||||
def storeys(self) -> [Storey]:
|
|
||||||
"""
|
|
||||||
Storeys inside the building
|
|
||||||
:return: [Storey]
|
|
||||||
"""
|
|
||||||
return self._storeys
|
|
||||||
|
|
||||||
@storeys.setter
|
|
||||||
def storeys(self, value):
|
|
||||||
"""
|
|
||||||
Storeys inside the building
|
|
||||||
:param value: [Storey]
|
|
||||||
"""
|
|
||||||
self._storeys = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def roof_type(self):
|
|
||||||
"""
|
|
||||||
Roof type for the building flat or pitch
|
|
||||||
"""
|
|
||||||
if self._roof_type is None:
|
|
||||||
self._roof_type = 'flat'
|
|
||||||
for roof in self.roofs:
|
|
||||||
grads = np.rad2deg(roof.inclination)
|
|
||||||
if 355 > grads > 5:
|
|
||||||
self._roof_type = 'pitch'
|
|
||||||
break
|
|
||||||
return self._roof_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def floor_area(self):
|
|
||||||
"""
|
|
||||||
Floor area of the building m2
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._floor_area is None:
|
|
||||||
self._floor_area = 0
|
|
||||||
for surface in self.surfaces:
|
|
||||||
if surface.type == 'Ground':
|
|
||||||
self._floor_area += surface.perimeter_polygon.area
|
|
||||||
return self._floor_area
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_boundaries(self) -> List[ThermalBoundary]:
|
|
||||||
"""
|
|
||||||
List of all thermal boundaries associated to the building's thermal zones
|
|
||||||
:return: [ThermalBoundary]
|
|
||||||
"""
|
|
||||||
if self._thermal_boundaries is None:
|
|
||||||
self._thermal_boundaries = []
|
|
||||||
for thermal_zone in self.thermal_zones:
|
|
||||||
_thermal_boundary_duplicated = False
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
if len(thermal_boundary.thermal_zones) > 1:
|
|
||||||
if thermal_zone != thermal_boundary.thermal_zones[1]:
|
|
||||||
self._thermal_boundaries.append(thermal_boundary)
|
|
||||||
else:
|
|
||||||
self._thermal_boundaries.append(thermal_boundary)
|
|
||||||
return self._thermal_boundaries
|
|
|
@ -1,27 +0,0 @@
|
||||||
"""
|
|
||||||
Building module a new implementation option
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
|
|
||||||
|
|
||||||
class Building(CityObject):
|
|
||||||
"""
|
|
||||||
Building(CityObject) class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, lod, trimesh, year_of_construction, function, city_lower_corner):
|
|
||||||
surfaces = []
|
|
||||||
for face in trimesh.faces:
|
|
||||||
# todo: review for obj with windows
|
|
||||||
points = []
|
|
||||||
for vertex_index in face:
|
|
||||||
points.append(trimesh.vertices[vertex_index])
|
|
||||||
solid_polygon = Polygon(points)
|
|
||||||
perimeter_polygon = solid_polygon
|
|
||||||
surface = Surface(solid_polygon, perimeter_polygon)
|
|
||||||
surfaces.append(surface)
|
|
||||||
super().__init__(name, lod, surfaces, city_lower_corner)
|
|
|
@ -1,26 +0,0 @@
|
||||||
|
|
||||||
# from surfaces to thermal_zones
|
|
||||||
|
|
||||||
#zone_surfaces = []
|
|
||||||
# these should be the virtual internal surfaces (What about the internal walls??)
|
|
||||||
#for surface_id in zone_surfaces_ids:
|
|
||||||
# zone_surfaces.append(self.surface(surface_id))
|
|
||||||
#self._thermal_zones.append(ThermalZone(zone_surfaces))
|
|
||||||
|
|
||||||
#for t_zones in self._thermal_zones:
|
|
||||||
# t_zones.bounded = [ThermalBoundary(s, [t_zones]) for s in t_zones.surfaces]
|
|
||||||
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
from city_model_structure.attributes.plane import Plane
|
|
||||||
from city_model_structure.attributes.point import Point
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
coordinates = [np.array([1, 0, 0]), np.array([0, 0, 0]), np.array([0, 0, 1]), np.array([1, 0, 1])]
|
|
||||||
polygon = Polygon(coordinates)
|
|
||||||
origin = Point([0, 0, 0.5])
|
|
||||||
normal = np.array([0, 0, 1])
|
|
||||||
plane = Plane(normal=normal, origin=origin)
|
|
||||||
intersection, polygon_1, polygon_2 = polygon.divide(plane)
|
|
||||||
print('final polygon')
|
|
||||||
print(polygon_1.coordinates)
|
|
||||||
print(polygon_2.coordinates)
|
|
|
@ -1,81 +0,0 @@
|
||||||
"""
|
|
||||||
InternalGains module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
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: float
|
|
||||||
"""
|
|
||||||
return self._average_internal_gain
|
|
||||||
|
|
||||||
@average_internal_gain.setter
|
|
||||||
def average_internal_gain(self, value):
|
|
||||||
"""
|
|
||||||
Set internal gains average internal gain in W/m2
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._average_internal_gain = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def convective_fraction(self):
|
|
||||||
"""
|
|
||||||
Get internal gains convective fraction
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._convective_fraction
|
|
||||||
|
|
||||||
@convective_fraction.setter
|
|
||||||
def convective_fraction(self, value):
|
|
||||||
"""
|
|
||||||
Set internal gains convective fraction
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._convective_fraction = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def radiative_fraction(self):
|
|
||||||
"""
|
|
||||||
Get internal gains radiative fraction
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._radiative_fraction
|
|
||||||
|
|
||||||
@radiative_fraction.setter
|
|
||||||
def radiative_fraction(self, value):
|
|
||||||
"""
|
|
||||||
Set internal gains convective fraction
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._radiative_fraction = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def latent_fraction(self):
|
|
||||||
"""
|
|
||||||
Get internal gains latent fraction
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._latent_fraction
|
|
||||||
|
|
||||||
@latent_fraction.setter
|
|
||||||
def latent_fraction(self, value):
|
|
||||||
"""
|
|
||||||
Set internal gains latent fraction
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._latent_fraction = value
|
|
|
@ -1,59 +0,0 @@
|
||||||
"""
|
|
||||||
Layers module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
import uuid
|
|
||||||
from city_model_structure.building_demand.material import Material
|
|
||||||
|
|
||||||
|
|
||||||
class Layer:
|
|
||||||
"""
|
|
||||||
Layer class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._material = None
|
|
||||||
self._thickness = None
|
|
||||||
self._id = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
"""
|
|
||||||
Get layer id, an universally unique identifier randomly generated
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if self._id is None:
|
|
||||||
self._id = uuid.uuid4()
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@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: float
|
|
||||||
"""
|
|
||||||
return self._thickness
|
|
||||||
|
|
||||||
@thickness.setter
|
|
||||||
def thickness(self, value):
|
|
||||||
"""
|
|
||||||
Get layer thickness in meters
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._thickness = value
|
|
|
@ -1,165 +0,0 @@
|
||||||
"""
|
|
||||||
Material module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Material:
|
|
||||||
"""
|
|
||||||
Material class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._name = None
|
|
||||||
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 name(self):
|
|
||||||
"""
|
|
||||||
Get material name
|
|
||||||
:return: string
|
|
||||||
"""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, value):
|
|
||||||
"""
|
|
||||||
Set material name
|
|
||||||
:param value: string
|
|
||||||
"""
|
|
||||||
self._name = value
|
|
||||||
|
|
||||||
@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
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._conductivity = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def specific_heat(self):
|
|
||||||
"""
|
|
||||||
Get material conductivity in J/kgK
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._specific_heat
|
|
||||||
|
|
||||||
@specific_heat.setter
|
|
||||||
def specific_heat(self, value):
|
|
||||||
"""
|
|
||||||
Get material conductivity in J/kgK
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._specific_heat = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def density(self):
|
|
||||||
"""
|
|
||||||
Get material density in kg/m3
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._density
|
|
||||||
|
|
||||||
@density.setter
|
|
||||||
def density(self, value):
|
|
||||||
"""
|
|
||||||
Set material density in kg/m3
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._density = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def solar_absorptance(self):
|
|
||||||
"""
|
|
||||||
Get material solar absorptance
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._solar_absorptance
|
|
||||||
|
|
||||||
@solar_absorptance.setter
|
|
||||||
def solar_absorptance(self, value):
|
|
||||||
"""
|
|
||||||
Set material solar absorptance
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._solar_absorptance = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_absorptance(self):
|
|
||||||
"""
|
|
||||||
Get material thermal absorptance
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._thermal_absorptance
|
|
||||||
|
|
||||||
@thermal_absorptance.setter
|
|
||||||
def thermal_absorptance(self, value):
|
|
||||||
"""
|
|
||||||
Set material thermal absorptance
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._thermal_absorptance = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def visible_absorptance(self):
|
|
||||||
"""
|
|
||||||
Get material visible absorptance
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._visible_absorptance
|
|
||||||
|
|
||||||
@visible_absorptance.setter
|
|
||||||
def visible_absorptance(self, value):
|
|
||||||
"""
|
|
||||||
Set material visible absorptance
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._visible_absorptance = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def no_mass(self):
|
|
||||||
"""
|
|
||||||
Get material no mass flag
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
return self._no_mass
|
|
||||||
|
|
||||||
@no_mass.setter
|
|
||||||
def no_mass(self, value):
|
|
||||||
"""
|
|
||||||
Set material no mass flag
|
|
||||||
:param value: Boolean
|
|
||||||
"""
|
|
||||||
self._no_mass = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_resistance(self):
|
|
||||||
"""
|
|
||||||
Get material thermal resistance in m2K/W
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._thermal_resistance
|
|
||||||
|
|
||||||
@thermal_resistance.setter
|
|
||||||
def thermal_resistance(self, value):
|
|
||||||
"""
|
|
||||||
Set material thermal resistance in m2K/W
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._thermal_resistance = value
|
|
|
@ -1,217 +0,0 @@
|
||||||
"""
|
|
||||||
Occupants module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import TypeVar
|
|
||||||
import calendar as cal
|
|
||||||
|
|
||||||
UsageZone = TypeVar('UsageZone')
|
|
||||||
|
|
||||||
|
|
||||||
class Occupants:
|
|
||||||
"""
|
|
||||||
Occupants class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""
|
|
||||||
Constructor
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._heat_dissipation = None
|
|
||||||
self._occupancy_rate = None
|
|
||||||
self._occupant_type = None
|
|
||||||
self._usage_zone = None
|
|
||||||
self._occupant_schedule = None
|
|
||||||
self._number_of_occupants = None
|
|
||||||
self._arrival_time = None
|
|
||||||
self._departure_time = None
|
|
||||||
self._break_time = None
|
|
||||||
self._day_of_week = None
|
|
||||||
self._pd_of_meetings_duration = None
|
|
||||||
self._complete_year_schedule = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def heat_dissipation(self):
|
|
||||||
"""
|
|
||||||
Get heat dissipation of occupants in W/person
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._heat_dissipation
|
|
||||||
|
|
||||||
@heat_dissipation.setter
|
|
||||||
def heat_dissipation(self, value):
|
|
||||||
"""
|
|
||||||
Set heat dissipation of occupants in W/person
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._heat_dissipation = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def occupancy_rate(self):
|
|
||||||
"""
|
|
||||||
Get rate of schedules
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._occupancy_rate
|
|
||||||
|
|
||||||
@occupancy_rate.setter
|
|
||||||
def occupancy_rate(self, value):
|
|
||||||
"""
|
|
||||||
Set rate of schedules
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._occupancy_rate = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def occupant_type(self):
|
|
||||||
"""
|
|
||||||
Get type of schedules
|
|
||||||
:return: string
|
|
||||||
"""
|
|
||||||
return self._occupant_type
|
|
||||||
|
|
||||||
@occupant_type.setter
|
|
||||||
def occupant_type(self, value):
|
|
||||||
"""
|
|
||||||
Set type of schedules
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._occupant_type = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def usage_zone(self) -> UsageZone:
|
|
||||||
"""
|
|
||||||
Get the zone an occupant is in
|
|
||||||
:return: UsageZone
|
|
||||||
"""
|
|
||||||
return self._usage_zone
|
|
||||||
|
|
||||||
@property
|
|
||||||
def occupant_schedule(self):
|
|
||||||
"""
|
|
||||||
Get the schedules when an occupant is in a zone (24 values, 1 per hour of the day)
|
|
||||||
:return: [float]
|
|
||||||
"""
|
|
||||||
return self._occupant_schedule
|
|
||||||
|
|
||||||
@occupant_schedule.setter
|
|
||||||
def occupant_schedule(self, value):
|
|
||||||
"""
|
|
||||||
Set the schedules when an occupant is in a zone (24 values, 1 per hour of the day)
|
|
||||||
:param value: [float]
|
|
||||||
"""
|
|
||||||
self._occupant_schedule = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def number_of_occupants(self):
|
|
||||||
"""
|
|
||||||
Get the number of occupants
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._number_of_occupants
|
|
||||||
|
|
||||||
@number_of_occupants.setter
|
|
||||||
def number_of_occupants(self, value):
|
|
||||||
"""
|
|
||||||
Set the number of occupants
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._number_of_occupants = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def arrival_time(self):
|
|
||||||
"""
|
|
||||||
Get the arrival time of the occupant (for office building) in UTC with format YYYYMMDD HH:mm:ss
|
|
||||||
:return: time
|
|
||||||
"""
|
|
||||||
return self._arrival_time
|
|
||||||
|
|
||||||
@arrival_time.setter
|
|
||||||
def arrival_time(self, value):
|
|
||||||
"""
|
|
||||||
Set the arrival time of the occupant (for office building) in UTC with format YYYYMMDD HH:mm:ss
|
|
||||||
:param value: time
|
|
||||||
"""
|
|
||||||
self._arrival_time = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def departure_time(self):
|
|
||||||
"""
|
|
||||||
Get the departure time of the occupant (for office building) in UTC with format YYYYMMDD HH:mm:ss
|
|
||||||
:return: time
|
|
||||||
"""
|
|
||||||
return self._departure_time
|
|
||||||
|
|
||||||
@departure_time.setter
|
|
||||||
def departure_time(self, value):
|
|
||||||
"""
|
|
||||||
Set the departure time of the occupant (for office building) in UTC with format YYYYMMDD HH:mm:ss
|
|
||||||
:param value: time
|
|
||||||
"""
|
|
||||||
self._departure_time = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def break_time(self):
|
|
||||||
"""
|
|
||||||
Get the lunch or break time of the occupant (for office building) in UTC with format ????
|
|
||||||
:return: break time
|
|
||||||
"""
|
|
||||||
# todo @Sanam: define this format, is it the starting time? is it a list with both, starting and ending time?
|
|
||||||
return self._break_time
|
|
||||||
|
|
||||||
@property
|
|
||||||
def day_of_week(self):
|
|
||||||
"""
|
|
||||||
Get the day of the week (MON, TUE, WED, THU, FRI, SAT, SUN)
|
|
||||||
:return: string
|
|
||||||
"""
|
|
||||||
# todo @Sanam: is this a property or should it be a function
|
|
||||||
# to get the day of the week of an specific day of the year?
|
|
||||||
return self._day_of_week
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pd_of_meetings_duration(self):
|
|
||||||
"""
|
|
||||||
Get the probability distribution of the meeting duration
|
|
||||||
:return: ??
|
|
||||||
"""
|
|
||||||
# todo @Sanam: what format are you expecting here??
|
|
||||||
return self._pd_of_meetings_duration
|
|
||||||
|
|
||||||
@pd_of_meetings_duration.setter
|
|
||||||
def pd_of_meetings_duration(self, value):
|
|
||||||
"""
|
|
||||||
Get the probability distribution of the meeting duration
|
|
||||||
:param value: ??
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
# todo @Sanam: what format are you expecting here??
|
|
||||||
self._pd_of_meetings_duration = value
|
|
||||||
|
|
||||||
def get_complete_year_schedule(self, schedules):
|
|
||||||
"""
|
|
||||||
Get the a non-leap year (8760 h), starting on Monday schedules out of archetypal days of week
|
|
||||||
:return: [float]
|
|
||||||
"""
|
|
||||||
if self._complete_year_schedule is None:
|
|
||||||
self._complete_year_schedule = []
|
|
||||||
for i in range(1, 13):
|
|
||||||
month_range = cal.monthrange(2015, i)[1]
|
|
||||||
for day in range(1, month_range+1):
|
|
||||||
if cal.weekday(2015, i, day) < 5:
|
|
||||||
for j in range(0, 24):
|
|
||||||
week_schedule = schedules['WD'][j]
|
|
||||||
self._complete_year_schedule.append(week_schedule)
|
|
||||||
elif cal.weekday(2015, i, day) == 5:
|
|
||||||
for j in range(0, 24):
|
|
||||||
week_schedule = schedules['Sat'][j]
|
|
||||||
self._complete_year_schedule.append(week_schedule)
|
|
||||||
else:
|
|
||||||
for j in range(0, 24):
|
|
||||||
week_schedule = schedules['Sun'][j]
|
|
||||||
self._complete_year_schedule.append(week_schedule)
|
|
||||||
return self._complete_year_schedule
|
|
|
@ -1,93 +0,0 @@
|
||||||
"""
|
|
||||||
Storey module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
from typing import List
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
from city_model_structure.building_demand.thermal_boundary import ThermalBoundary
|
|
||||||
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
|
||||||
|
|
||||||
|
|
||||||
class Storey:
|
|
||||||
# todo: rethink this class for buildings with windows
|
|
||||||
"""
|
|
||||||
Storey class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, storey_surfaces, neighbours, volume):
|
|
||||||
# todo: the information of the parent surface is lost -> need to recover it
|
|
||||||
self._name = name
|
|
||||||
self._storey_surfaces = storey_surfaces
|
|
||||||
self._thermal_boundaries = None
|
|
||||||
self._virtual_surfaces = None
|
|
||||||
self._thermal_zone = None
|
|
||||||
self._neighbours = neighbours
|
|
||||||
self._volume = volume
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
Storey's name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def surfaces(self) -> List[Surface]:
|
|
||||||
"""
|
|
||||||
External surfaces enclosing the storey
|
|
||||||
:return: [Surface]
|
|
||||||
"""
|
|
||||||
return self._storey_surfaces
|
|
||||||
|
|
||||||
@property
|
|
||||||
def neighbours(self):
|
|
||||||
"""
|
|
||||||
Neighbour storeys' names
|
|
||||||
:return: [str]
|
|
||||||
"""
|
|
||||||
return self._neighbours
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_boundaries(self) -> List[ThermalBoundary]:
|
|
||||||
"""
|
|
||||||
Thermal boundaries bounding the thermal zone
|
|
||||||
:return: [ThermalBoundary]
|
|
||||||
"""
|
|
||||||
if self._thermal_boundaries is None:
|
|
||||||
self._thermal_boundaries = []
|
|
||||||
for surface in self.surfaces:
|
|
||||||
self._thermal_boundaries.append(ThermalBoundary(surface))
|
|
||||||
return self._thermal_boundaries
|
|
||||||
|
|
||||||
@property
|
|
||||||
def virtual_surfaces(self) -> List[Surface]:
|
|
||||||
"""
|
|
||||||
Internal surfaces enclosing the thermal zone
|
|
||||||
:return: [Surface]
|
|
||||||
"""
|
|
||||||
if self._virtual_surfaces is None:
|
|
||||||
self._virtual_surfaces = []
|
|
||||||
for thermal_boundary in self.thermal_boundaries:
|
|
||||||
self._virtual_surfaces.append(thermal_boundary.virtual_internal_surface)
|
|
||||||
return self._virtual_surfaces
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_zone(self) -> ThermalZone:
|
|
||||||
"""
|
|
||||||
Thermal zone inside the storey
|
|
||||||
:return: ThermalZone
|
|
||||||
"""
|
|
||||||
if self._thermal_zone is None:
|
|
||||||
self._thermal_zone = ThermalZone(self.thermal_boundaries, self.volume)
|
|
||||||
return self._thermal_zone
|
|
||||||
|
|
||||||
@property
|
|
||||||
def volume(self):
|
|
||||||
"""
|
|
||||||
Storey's volume
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._volume
|
|
|
@ -1,295 +0,0 @@
|
||||||
"""
|
|
||||||
Surface module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
import uuid
|
|
||||||
import numpy as np
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
from city_model_structure.attributes.plane import Plane
|
|
||||||
from city_model_structure.attributes.point import Point
|
|
||||||
from city_model_structure.energy_systems.pv_system import PvSystem
|
|
||||||
import helpers.constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class Surface:
|
|
||||||
"""
|
|
||||||
Surface class
|
|
||||||
"""
|
|
||||||
def __init__(self, solid_polygon, perimeter_polygon, holes_polygons=None, name=None, surface_type=None, swr=None):
|
|
||||||
self._type = surface_type
|
|
||||||
self._swr = swr
|
|
||||||
self._name = name
|
|
||||||
self._id = None
|
|
||||||
self._azimuth = None
|
|
||||||
self._inclination = None
|
|
||||||
self._area_above_ground = None
|
|
||||||
self._area_below_ground = None
|
|
||||||
self._lower_corner = None
|
|
||||||
self._upper_corner = None
|
|
||||||
self._shared_surfaces = []
|
|
||||||
self._global_irradiance = dict()
|
|
||||||
self._perimeter_polygon = perimeter_polygon
|
|
||||||
self._holes_polygons = holes_polygons
|
|
||||||
self._solid_polygon = solid_polygon
|
|
||||||
self._pv_system_installed = None
|
|
||||||
self._inverse = None
|
|
||||||
# todo: do I need it???
|
|
||||||
self._associated_thermal_boundaries = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
Surface name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if self._name is None:
|
|
||||||
self._name = str(uuid.uuid4())
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
"""
|
|
||||||
Surface id
|
|
||||||
:return str
|
|
||||||
"""
|
|
||||||
if self._id is None:
|
|
||||||
raise ValueError('Undefined surface id')
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def share_surfaces(self):
|
|
||||||
"""
|
|
||||||
Raises not implemented error
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@id.setter
|
|
||||||
def id(self, value):
|
|
||||||
"""
|
|
||||||
Surface id
|
|
||||||
"""
|
|
||||||
self._id = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def swr(self):
|
|
||||||
"""
|
|
||||||
Get surface short wave reflectance
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._swr
|
|
||||||
|
|
||||||
@swr.setter
|
|
||||||
def swr(self, value):
|
|
||||||
"""
|
|
||||||
Set surface short wave reflectance
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._swr = value
|
|
||||||
|
|
||||||
def _max_coord(self, axis):
|
|
||||||
if axis == 'x':
|
|
||||||
axis = 0
|
|
||||||
elif axis == 'y':
|
|
||||||
axis = 1
|
|
||||||
else:
|
|
||||||
axis = 2
|
|
||||||
max_coordinate = ''
|
|
||||||
for point in self.perimeter_polygon.coordinates:
|
|
||||||
if max_coordinate == '':
|
|
||||||
max_coordinate = point[axis]
|
|
||||||
elif max_coordinate < point[axis]:
|
|
||||||
max_coordinate = point[axis]
|
|
||||||
return max_coordinate
|
|
||||||
|
|
||||||
def _min_coord(self, axis):
|
|
||||||
if axis == 'x':
|
|
||||||
axis = 0
|
|
||||||
elif axis == 'y':
|
|
||||||
axis = 1
|
|
||||||
else:
|
|
||||||
axis = 2
|
|
||||||
min_coordinate = ''
|
|
||||||
for point in self.perimeter_polygon.coordinates:
|
|
||||||
if min_coordinate == '':
|
|
||||||
min_coordinate = point[axis]
|
|
||||||
elif min_coordinate > point[axis]:
|
|
||||||
min_coordinate = point[axis]
|
|
||||||
return min_coordinate
|
|
||||||
|
|
||||||
@property
|
|
||||||
def lower_corner(self):
|
|
||||||
"""
|
|
||||||
Surface's lower corner [x, y, z]
|
|
||||||
:return: [float]
|
|
||||||
"""
|
|
||||||
if self._lower_corner is None:
|
|
||||||
self._lower_corner = [self._min_coord('x'), self._min_coord('y'), self._min_coord('z')]
|
|
||||||
return self._lower_corner
|
|
||||||
|
|
||||||
@property
|
|
||||||
def upper_corner(self):
|
|
||||||
"""
|
|
||||||
Surface's upper corner [x, y, z]
|
|
||||||
:return: [float]
|
|
||||||
"""
|
|
||||||
if self._upper_corner is None:
|
|
||||||
self._upper_corner = [self._max_coord('x'), self._max_coord('y'), self._max_coord('z')]
|
|
||||||
return self._upper_corner
|
|
||||||
|
|
||||||
@property
|
|
||||||
def area_above_ground(self):
|
|
||||||
"""
|
|
||||||
Surface area above ground in square meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._area_above_ground is None:
|
|
||||||
self._area_above_ground = self.perimeter_polygon.area - self.area_below_ground
|
|
||||||
return self._area_above_ground
|
|
||||||
|
|
||||||
# todo: to be implemented when adding terrains
|
|
||||||
@property
|
|
||||||
def area_below_ground(self):
|
|
||||||
"""
|
|
||||||
Surface area below ground in square meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def azimuth(self):
|
|
||||||
"""
|
|
||||||
Surface azimuth in radians
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._azimuth is None:
|
|
||||||
normal = self.perimeter_polygon.normal
|
|
||||||
self._azimuth = np.arctan2(normal[1], normal[0])
|
|
||||||
return self._azimuth
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inclination(self):
|
|
||||||
"""
|
|
||||||
Surface inclination in radians
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._inclination is None:
|
|
||||||
self._inclination = np.arccos(self.perimeter_polygon.normal[2])
|
|
||||||
return self._inclination
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
Surface type Ground, Wall or Roof
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if self._type is None:
|
|
||||||
grad = np.rad2deg(self.inclination)
|
|
||||||
if grad >= 170:
|
|
||||||
self._type = 'Ground'
|
|
||||||
elif 80 <= grad <= 100:
|
|
||||||
self._type = 'Wall'
|
|
||||||
else:
|
|
||||||
self._type = 'Roof'
|
|
||||||
return self._type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def global_irradiance(self) -> dict:
|
|
||||||
"""
|
|
||||||
global irradiance on surface in Wh/m2
|
|
||||||
:return: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
return self._global_irradiance
|
|
||||||
|
|
||||||
@global_irradiance.setter
|
|
||||||
def global_irradiance(self, value):
|
|
||||||
"""
|
|
||||||
global irradiance on surface in Wh/m2
|
|
||||||
:param value: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
self._global_irradiance = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def perimeter_polygon(self) -> Polygon:
|
|
||||||
"""
|
|
||||||
total surface defined by the perimeter, merging solid and holes
|
|
||||||
:return: Polygon
|
|
||||||
"""
|
|
||||||
return self._perimeter_polygon
|
|
||||||
|
|
||||||
@property
|
|
||||||
def solid_polygon(self) -> Polygon:
|
|
||||||
"""
|
|
||||||
solid surface
|
|
||||||
:return: Polygon
|
|
||||||
"""
|
|
||||||
return self._solid_polygon
|
|
||||||
|
|
||||||
@property
|
|
||||||
def holes_polygons(self) -> [Polygon]:
|
|
||||||
"""
|
|
||||||
hole surfaces, a list of hole polygons found in the surface
|
|
||||||
:return: None, [] or [Polygon]
|
|
||||||
None -> not known whether holes exist in reality or not due to low level of detail of input data
|
|
||||||
[] -> no holes in the surface
|
|
||||||
[Polygon] -> one or more holes in the surface
|
|
||||||
"""
|
|
||||||
return self._holes_polygons
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pv_system_installed(self) -> PvSystem:
|
|
||||||
"""
|
|
||||||
PV system installed on the surface
|
|
||||||
:return: PvSystem
|
|
||||||
"""
|
|
||||||
return self._pv_system_installed
|
|
||||||
|
|
||||||
@pv_system_installed.setter
|
|
||||||
def pv_system_installed(self, value):
|
|
||||||
"""
|
|
||||||
PV system installed on the surface
|
|
||||||
:param value: PvSystem
|
|
||||||
"""
|
|
||||||
self._pv_system_installed = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inverse(self) -> Surface:
|
|
||||||
"""
|
|
||||||
Returns the same surface pointing backwards
|
|
||||||
:return: Surface
|
|
||||||
"""
|
|
||||||
if self._inverse is None:
|
|
||||||
new_solid_polygon = Polygon(self.solid_polygon.inverse)
|
|
||||||
new_perimeter_polygon = Polygon(self.perimeter_polygon.inverse)
|
|
||||||
new_holes_polygons = []
|
|
||||||
if self.holes_polygons is not None:
|
|
||||||
for hole in self.holes_polygons:
|
|
||||||
new_holes_polygons.append(Polygon(hole.inverse))
|
|
||||||
else:
|
|
||||||
new_holes_polygons = None
|
|
||||||
self._inverse = Surface(new_solid_polygon, new_perimeter_polygon, new_holes_polygons, cte.VIRTUAL_INTERNAL)
|
|
||||||
return self._inverse
|
|
||||||
|
|
||||||
def shared_surfaces(self):
|
|
||||||
"""
|
|
||||||
Raises not implemented error
|
|
||||||
"""
|
|
||||||
# todo: check https://trimsh.org/trimesh.collision.html as an option to implement this method
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def divide(self, z):
|
|
||||||
"""
|
|
||||||
Divides a surface at Z plane
|
|
||||||
"""
|
|
||||||
# todo: recheck this method for LoD3 (windows)
|
|
||||||
origin = Point([0, 0, z])
|
|
||||||
normal = np.array([0, 0, 1])
|
|
||||||
plane = Plane(normal=normal, origin=origin)
|
|
||||||
polygon = self.perimeter_polygon
|
|
||||||
part_1, part_2, intersection = polygon.divide(plane)
|
|
||||||
surface_child = Surface(part_1, part_1, name=self.name, surface_type=self.type)
|
|
||||||
rest_surface = Surface(part_2, part_2, name=self.name, surface_type=self.type)
|
|
||||||
return surface_child, rest_surface, intersection
|
|
|
@ -1,394 +0,0 @@
|
||||||
"""
|
|
||||||
ThermalBoundary module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from typing import List, TypeVar, Union
|
|
||||||
from city_model_structure.building_demand.layer import Layer
|
|
||||||
from city_model_structure.building_demand.thermal_opening import ThermalOpening
|
|
||||||
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
|
|
||||||
Polygon = TypeVar('Polygon')
|
|
||||||
|
|
||||||
|
|
||||||
class ThermalBoundary:
|
|
||||||
"""
|
|
||||||
ThermalBoundary class
|
|
||||||
"""
|
|
||||||
def __init__(self, surface):
|
|
||||||
self._surface = surface
|
|
||||||
self._thermal_zones = None
|
|
||||||
# ToDo: up to at least LOD2 will be just one thermal opening per Thermal boundary only if window_ratio > 0,
|
|
||||||
# review for LOD3 and LOD4
|
|
||||||
self._thermal_openings = None
|
|
||||||
self._layers = None
|
|
||||||
self._outside_solar_absorptance = None
|
|
||||||
self._outside_thermal_absorptance = None
|
|
||||||
self._outside_visible_absorptance = None
|
|
||||||
self._u_value = None
|
|
||||||
self._shortwave_reflectance = None
|
|
||||||
self._construction_name = None
|
|
||||||
self._hi = 3.5
|
|
||||||
self._he = 20
|
|
||||||
self._window_ratio = None
|
|
||||||
self._refurbishment_measure = None
|
|
||||||
self._surface_geometry = None
|
|
||||||
self._thickness = None
|
|
||||||
self._virtual_internal_surface = None
|
|
||||||
self._inside_emissivity = None
|
|
||||||
self._alpha_coefficient = None
|
|
||||||
self._radiative_coefficient = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def surface(self) -> Surface:
|
|
||||||
"""
|
|
||||||
Get the surface that belongs to the thermal boundary
|
|
||||||
:return: Surface
|
|
||||||
"""
|
|
||||||
# todo: in LoD4 this property will be a list of surfaces, not only one
|
|
||||||
return self._surface
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_zones(self) -> List[ThermalZone]:
|
|
||||||
"""
|
|
||||||
Get the thermal zones delimited by the thermal boundary
|
|
||||||
:return: [ThermalZone]
|
|
||||||
"""
|
|
||||||
return self._thermal_zones
|
|
||||||
|
|
||||||
@thermal_zones.setter
|
|
||||||
def thermal_zones(self, value):
|
|
||||||
"""
|
|
||||||
Thermal zones delimited by the thermal boundary
|
|
||||||
:param value: [ThermalZone]
|
|
||||||
"""
|
|
||||||
self._thermal_zones = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def azimuth(self):
|
|
||||||
"""
|
|
||||||
Thermal boundary azimuth in radians
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._surface.azimuth
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inclination(self):
|
|
||||||
"""
|
|
||||||
Thermal boundary inclination in radians
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._surface.inclination
|
|
||||||
|
|
||||||
@property
|
|
||||||
def area(self):
|
|
||||||
"""
|
|
||||||
Thermal boundary area in square meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
# to check the lod without depending on that parameter
|
|
||||||
if float(self.surface.solid_polygon.area) - float(self.surface.perimeter_polygon.area) < 1e-3:
|
|
||||||
area = float(self.surface.perimeter_polygon.area) * (1 - float(self.window_ratio))
|
|
||||||
else:
|
|
||||||
area = self.surface.solid_polygon.area
|
|
||||||
return area
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _total_area_including_windows(self):
|
|
||||||
"""
|
|
||||||
Thermal boundary plus windows area in square meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self.surface.perimeter_polygon.area
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thickness(self):
|
|
||||||
"""
|
|
||||||
Thermal boundary thickness in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._thickness is None:
|
|
||||||
self._thickness = 0.0
|
|
||||||
if self.layers is not None:
|
|
||||||
for layer in self.layers:
|
|
||||||
self._thickness += layer.thickness
|
|
||||||
return self._thickness
|
|
||||||
|
|
||||||
@property
|
|
||||||
def outside_solar_absorptance(self):
|
|
||||||
"""
|
|
||||||
Get thermal boundary outside solar absorptance
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._outside_solar_absorptance
|
|
||||||
|
|
||||||
@outside_solar_absorptance.setter
|
|
||||||
def outside_solar_absorptance(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal boundary outside solar absorptance
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
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: float
|
|
||||||
"""
|
|
||||||
return self._outside_thermal_absorptance
|
|
||||||
|
|
||||||
@outside_thermal_absorptance.setter
|
|
||||||
def outside_thermal_absorptance(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal boundary outside thermal absorptance
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._outside_thermal_absorptance = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def outside_visible_absorptance(self):
|
|
||||||
"""
|
|
||||||
Get thermal boundary outside visible absorptance
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._outside_visible_absorptance
|
|
||||||
|
|
||||||
@outside_visible_absorptance.setter
|
|
||||||
def outside_visible_absorptance(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal boundary outside visible absorptance
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._outside_visible_absorptance = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_openings(self) -> List[ThermalOpening]:
|
|
||||||
"""
|
|
||||||
Get thermal boundary thermal openings
|
|
||||||
:return: [ThermalOpening]
|
|
||||||
"""
|
|
||||||
if self._thermal_openings is None:
|
|
||||||
if float(self.window_ratio) > 0:
|
|
||||||
thermal_opening = ThermalOpening()
|
|
||||||
thermal_opening.area = float(self._total_area_including_windows) * float(self.window_ratio)
|
|
||||||
thermal_opening.hi = self.hi
|
|
||||||
thermal_opening.he = self.he
|
|
||||||
self._thermal_openings = [thermal_opening]
|
|
||||||
else:
|
|
||||||
self._thermal_openings = []
|
|
||||||
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 construction_name(self):
|
|
||||||
"""
|
|
||||||
Get construction name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._construction_name
|
|
||||||
|
|
||||||
@construction_name.setter
|
|
||||||
def construction_name(self, value):
|
|
||||||
"""
|
|
||||||
Set construction name
|
|
||||||
:param value: str
|
|
||||||
"""
|
|
||||||
self._construction_name = 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
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
Thermal boundary surface type
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._surface.type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def window_ratio(self):
|
|
||||||
"""
|
|
||||||
Get thermal boundary window ratio
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._window_ratio
|
|
||||||
|
|
||||||
@window_ratio.setter
|
|
||||||
def window_ratio(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal boundary window ratio
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._window_ratio = value
|
|
||||||
|
|
||||||
# todo: what if I just want to assign a number?? @Guille
|
|
||||||
@property
|
|
||||||
def u_value(self):
|
|
||||||
"""
|
|
||||||
Get 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:
|
|
||||||
h_i = self.hi
|
|
||||||
h_e = self.he
|
|
||||||
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
|
|
||||||
except TypeError:
|
|
||||||
raise Exception('Constructions layers are not initialized') from TypeError
|
|
||||||
return self._u_value
|
|
||||||
|
|
||||||
@u_value.setter
|
|
||||||
def u_value(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal boundary U-value in W/m2K
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._u_value = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def shortwave_reflectance(self):
|
|
||||||
"""
|
|
||||||
Get thermal boundary shortwave reflectance
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._shortwave_reflectance
|
|
||||||
|
|
||||||
@shortwave_reflectance.setter
|
|
||||||
def shortwave_reflectance(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal boundary shortwave reflectance
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._shortwave_reflectance = value
|
|
||||||
self._outside_solar_absorptance = 1.0 - float(value)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hi(self):
|
|
||||||
"""
|
|
||||||
Get internal convective heat transfer coefficient (W/m2K)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._hi
|
|
||||||
|
|
||||||
@hi.setter
|
|
||||||
def hi(self, value):
|
|
||||||
"""
|
|
||||||
Set internal convective heat transfer coefficient (W/m2K)
|
|
||||||
:param value: internal convective heat transfer coefficient (W/m2K)
|
|
||||||
"""
|
|
||||||
self._hi = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def he(self):
|
|
||||||
"""
|
|
||||||
Get external convective heat transfer coefficient (W/m2K)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._he
|
|
||||||
|
|
||||||
@he.setter
|
|
||||||
def he(self, value):
|
|
||||||
"""
|
|
||||||
Set external convective heat transfer coefficient (W/m2K)
|
|
||||||
:param value: external convective heat transfer coefficient (W/m2K)
|
|
||||||
"""
|
|
||||||
self._he = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def surface_geometry(self) -> Union[NotImplementedError, Polygon]:
|
|
||||||
"""
|
|
||||||
Get the polygon that defines the thermal boundary
|
|
||||||
:return: Polygon
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def virtual_internal_surface(self) -> Surface:
|
|
||||||
"""
|
|
||||||
Get the internal surface of the thermal boundary
|
|
||||||
:return: Surface
|
|
||||||
"""
|
|
||||||
if self._virtual_internal_surface is None:
|
|
||||||
self._virtual_internal_surface = self.surface.inverse
|
|
||||||
return self._virtual_internal_surface
|
|
||||||
|
|
||||||
# todo: need extract information from construction library or assume them at the beginning of workflows
|
|
||||||
@property
|
|
||||||
def inside_emissivity(self):
|
|
||||||
"""
|
|
||||||
Get the short wave emissivity factor of the thermal boundary's internal surface (-)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._inside_emissivity
|
|
||||||
|
|
||||||
@inside_emissivity.setter
|
|
||||||
def inside_emissivity(self, value):
|
|
||||||
"""
|
|
||||||
Set short wave emissivity factor of the thermal boundary's internal surface (-)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._inside_emissivity = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def alpha_coefficient(self):
|
|
||||||
"""
|
|
||||||
Get the long wave emissivity factor of the thermal boundary's internal surface (-)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._alpha_coefficient
|
|
||||||
|
|
||||||
@alpha_coefficient.setter
|
|
||||||
def alpha_coefficient(self, value):
|
|
||||||
"""
|
|
||||||
Set long wave emissivity factor of the thermal boundary's internal surface (-)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._alpha_coefficient = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def radiative_coefficient(self):
|
|
||||||
"""
|
|
||||||
Get the radiative coefficient of the thermal boundary's external surface (-)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._radiative_coefficient
|
|
||||||
|
|
||||||
@radiative_coefficient.setter
|
|
||||||
def radiative_coefficient(self, value):
|
|
||||||
"""
|
|
||||||
Set radiative coefficient of the thermal boundary's external surface (-)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._radiative_coefficient = value
|
|
|
@ -1,279 +0,0 @@
|
||||||
"""
|
|
||||||
ThermalOpening module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
Polygon = TypeVar('Polygon')
|
|
||||||
|
|
||||||
|
|
||||||
class ThermalOpening:
|
|
||||||
"""
|
|
||||||
ThermalOpening class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._area = None
|
|
||||||
self._openable_ratio = None
|
|
||||||
self._conductivity = None
|
|
||||||
self._frame_ratio = None
|
|
||||||
self._g_value = None
|
|
||||||
self._thickness = None
|
|
||||||
self._front_side_solar_transmittance_at_normal_incidence = None
|
|
||||||
self._back_side_solar_transmittance_at_normal_incidence = None
|
|
||||||
self._overall_u_value = None
|
|
||||||
self._hi = None
|
|
||||||
self._he = None
|
|
||||||
self._surface_geometry = None
|
|
||||||
self._inside_emissivity = None
|
|
||||||
self._alpha_coefficient = None
|
|
||||||
self._radiative_coefficient = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def area(self):
|
|
||||||
"""
|
|
||||||
Thermal opening area in square meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._area
|
|
||||||
|
|
||||||
@area.setter
|
|
||||||
def area(self, value):
|
|
||||||
"""
|
|
||||||
Thermal opening area in square meters setter
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._area = value
|
|
||||||
|
|
||||||
@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
|
|
||||||
:return: Exception
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def conductivity(self):
|
|
||||||
"""
|
|
||||||
Get thermal opening conductivity in W/mK
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._conductivity
|
|
||||||
|
|
||||||
@conductivity.setter
|
|
||||||
def conductivity(self, value):
|
|
||||||
"""
|
|
||||||
Get thermal opening conductivity in W/mK
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
# 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.
|
|
||||||
self._conductivity = value
|
|
||||||
if self._overall_u_value is None and self.thickness is not None:
|
|
||||||
h_i = self.hi
|
|
||||||
h_e = self.he
|
|
||||||
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: float
|
|
||||||
"""
|
|
||||||
return self._frame_ratio
|
|
||||||
|
|
||||||
@frame_ratio.setter
|
|
||||||
def frame_ratio(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal opening frame ratio
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._frame_ratio = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def g_value(self):
|
|
||||||
"""
|
|
||||||
Get thermal opening g-value
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._g_value
|
|
||||||
|
|
||||||
@g_value.setter
|
|
||||||
def g_value(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal opening g-value
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._g_value = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thickness(self):
|
|
||||||
"""
|
|
||||||
Get thermal opening thickness in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._thickness
|
|
||||||
|
|
||||||
@thickness.setter
|
|
||||||
def thickness(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal opening thickness in meters
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
# 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.
|
|
||||||
self._thickness = value
|
|
||||||
if self._overall_u_value is None and self.conductivity is not None:
|
|
||||||
h_i = self.hi
|
|
||||||
h_e = self.he
|
|
||||||
r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self.thickness)
|
|
||||||
self._overall_u_value = 1 / r_value
|
|
||||||
|
|
||||||
@property
|
|
||||||
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
|
|
||||||
|
|
||||||
@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
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
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: float
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
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: float
|
|
||||||
"""
|
|
||||||
return self._overall_u_value
|
|
||||||
|
|
||||||
@overall_u_value.setter
|
|
||||||
def overall_u_value(self, value):
|
|
||||||
"""
|
|
||||||
Get thermal opening overall U-value in W/m2K
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._overall_u_value = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hi(self):
|
|
||||||
"""
|
|
||||||
Get internal convective heat transfer coefficient (W/m2K)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._hi
|
|
||||||
|
|
||||||
@hi.setter
|
|
||||||
def hi(self, value):
|
|
||||||
"""
|
|
||||||
Set internal convective heat transfer coefficient (W/m2K)
|
|
||||||
:param value: internal convective heat transfer coefficient (W/m2K)
|
|
||||||
"""
|
|
||||||
self._hi = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def he(self):
|
|
||||||
"""
|
|
||||||
Get external convective heat transfer coefficient (W/m2K)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._he
|
|
||||||
|
|
||||||
@he.setter
|
|
||||||
def he(self, value):
|
|
||||||
"""
|
|
||||||
Set external convective heat transfer coefficient (W/m2K)
|
|
||||||
:param value: external convective heat transfer coefficient (W/m2K)
|
|
||||||
"""
|
|
||||||
self._he = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def surface_geometry(self) -> Polygon:
|
|
||||||
"""
|
|
||||||
Get the polygon that defines the thermal opening
|
|
||||||
:return: Polygon
|
|
||||||
"""
|
|
||||||
return self._surface_geometry
|
|
||||||
|
|
||||||
# todo: need extract information from construction library or assume them at the beginning of workflows
|
|
||||||
@property
|
|
||||||
def inside_emissivity(self):
|
|
||||||
"""
|
|
||||||
Get the short wave emissivity factor of the thermal opening's internal surface (-)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._inside_emissivity
|
|
||||||
|
|
||||||
@inside_emissivity.setter
|
|
||||||
def inside_emissivity(self, value):
|
|
||||||
"""
|
|
||||||
Set short wave emissivity factor of the thermal opening's internal surface (-)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._inside_emissivity = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def alpha_coefficient(self):
|
|
||||||
"""
|
|
||||||
Get the long wave emissivity factor of the thermal opening's internal surface (-)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._alpha_coefficient
|
|
||||||
|
|
||||||
@alpha_coefficient.setter
|
|
||||||
def alpha_coefficient(self, value):
|
|
||||||
"""
|
|
||||||
Set long wave emissivity factor of the thermal opening's internal surface (-)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._alpha_coefficient = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def radiative_coefficient(self):
|
|
||||||
"""
|
|
||||||
Get the radiative coefficient of the thermal opening's external surface (-)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._radiative_coefficient
|
|
||||||
|
|
||||||
@radiative_coefficient.setter
|
|
||||||
def radiative_coefficient(self, value):
|
|
||||||
"""
|
|
||||||
Set radiative coefficient of the thermal opening's external surface (-)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._radiative_coefficient = value
|
|
|
@ -1,200 +0,0 @@
|
||||||
"""
|
|
||||||
ThermalZone module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import uuid
|
|
||||||
from typing import List, TypeVar
|
|
||||||
from city_model_structure.building_demand.usage_zone import UsageZone
|
|
||||||
|
|
||||||
ThermalBoundary = TypeVar('ThermalBoundary')
|
|
||||||
Polyhedron = TypeVar('Polyhedron')
|
|
||||||
|
|
||||||
|
|
||||||
class ThermalZone:
|
|
||||||
"""
|
|
||||||
ThermalZone class
|
|
||||||
"""
|
|
||||||
def __init__(self, thermal_boundaries, volume):
|
|
||||||
self._floor_area = None
|
|
||||||
self._thermal_boundaries = thermal_boundaries
|
|
||||||
self._is_mechanically_ventilated = None
|
|
||||||
self._additional_thermal_bridge_u_value = None
|
|
||||||
self._effective_thermal_capacity = None
|
|
||||||
self._indirectly_heated_area_ratio = None
|
|
||||||
self._infiltration_rate_system_on = None
|
|
||||||
self._infiltration_rate_system_off = None
|
|
||||||
self._usage_zones = []
|
|
||||||
self._volume = volume
|
|
||||||
self._volume_geometry = None
|
|
||||||
self._id = None
|
|
||||||
self._ordinate_number = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone id, an universally unique identifier randomly generated
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if self._id is None:
|
|
||||||
self._id = uuid.uuid4()
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_mechanically_ventilated(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone mechanical ventilation flag
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
return self._is_mechanically_ventilated
|
|
||||||
|
|
||||||
@property
|
|
||||||
def floor_area(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone floor area in m2
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
if self._floor_area is None:
|
|
||||||
self._floor_area = 0
|
|
||||||
for thermal_boundary in self.thermal_boundaries:
|
|
||||||
s = thermal_boundary.surface
|
|
||||||
if s.type == 'Ground':
|
|
||||||
self._floor_area += s.perimeter_polygon.area
|
|
||||||
return self._floor_area
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_boundaries(self) -> List[ThermalBoundary]:
|
|
||||||
"""
|
|
||||||
Get thermal boundaries bounding with the thermal zone
|
|
||||||
:return: [ThermalBoundary]
|
|
||||||
"""
|
|
||||||
return self._thermal_boundaries
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_thermal_bridge_u_value(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone additional thermal bridge u value W/m2K
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._additional_thermal_bridge_u_value = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def effective_thermal_capacity(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone effective thermal capacity in J/m2K
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._effective_thermal_capacity
|
|
||||||
|
|
||||||
@effective_thermal_capacity.setter
|
|
||||||
def effective_thermal_capacity(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal zone effective thermal capacity in J/m2K
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._effective_thermal_capacity = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def indirectly_heated_area_ratio(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone indirectly heated area ratio
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
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 (ACH)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
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 (ACH)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
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 (ACH)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._infiltration_rate_system_off
|
|
||||||
|
|
||||||
@infiltration_rate_system_off.setter
|
|
||||||
def infiltration_rate_system_off(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal zone infiltration rate system on in air changes per hour (ACH)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._infiltration_rate_system_off = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def usage_zones(self) -> List[UsageZone]:
|
|
||||||
"""
|
|
||||||
Get thermal zone usage zones
|
|
||||||
:return: [UsageZone]
|
|
||||||
"""
|
|
||||||
return self._usage_zones
|
|
||||||
|
|
||||||
@usage_zones.setter
|
|
||||||
def usage_zones(self, values):
|
|
||||||
"""
|
|
||||||
Set thermal zone usage zones
|
|
||||||
:param values: [UsageZone]
|
|
||||||
"""
|
|
||||||
self._usage_zones = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def volume(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone volume
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._volume
|
|
||||||
|
|
||||||
@property
|
|
||||||
def volume_geometry(self) -> Polyhedron:
|
|
||||||
"""
|
|
||||||
Get the polyhedron defined by the thermal zone
|
|
||||||
:return: Polyhedron
|
|
||||||
"""
|
|
||||||
return self._volume_geometry
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ordinate_number(self):
|
|
||||||
"""
|
|
||||||
In case the thermal_zones need to be enumerated and their order saved, this property saves that order
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._ordinate_number
|
|
||||||
|
|
||||||
@ordinate_number.setter
|
|
||||||
def ordinate_number(self, value):
|
|
||||||
"""
|
|
||||||
Sets an specific order of the zones to be called
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._ordinate_number = value
|
|
|
@ -1,379 +0,0 @@
|
||||||
"""
|
|
||||||
UsageZone module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributors Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
import uuid
|
|
||||||
from typing import List, TypeVar
|
|
||||||
|
|
||||||
InternalGains = TypeVar('InternalGains')
|
|
||||||
Occupants = TypeVar('Occupants')
|
|
||||||
Polyhedron = TypeVar('Polyhedron')
|
|
||||||
|
|
||||||
|
|
||||||
class UsageZone:
|
|
||||||
"""
|
|
||||||
UsageZone class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._id = None
|
|
||||||
self._usage = None
|
|
||||||
self._internal_gains = None
|
|
||||||
self._heating_setpoint = None
|
|
||||||
self._heating_setback = None
|
|
||||||
self._cooling_setpoint = None
|
|
||||||
self._occupancy_density = None
|
|
||||||
self._hours_day = None
|
|
||||||
self._days_year = None
|
|
||||||
self._dhw_average_volume_pers_day = None
|
|
||||||
self._dhw_preparation_temperature = None
|
|
||||||
self._electrical_app_average_consumption_sqm_year = None
|
|
||||||
self._mechanical_air_change = None
|
|
||||||
self._occupants = None
|
|
||||||
self._heating_schedule = None
|
|
||||||
self._cooling_schedule = None
|
|
||||||
self._ventilation_schedule = None
|
|
||||||
self._schedules = None
|
|
||||||
self._volume = None
|
|
||||||
self._volume_geometry = None
|
|
||||||
self._is_heated = False
|
|
||||||
self._is_cooled = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
"""
|
|
||||||
Get usage zone id, an universally unique identifier randomly generated
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if self._id is None:
|
|
||||||
self._id = uuid.uuid4()
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@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: float
|
|
||||||
"""
|
|
||||||
return self._heating_setpoint
|
|
||||||
|
|
||||||
@heating_setpoint.setter
|
|
||||||
def heating_setpoint(self, value):
|
|
||||||
"""
|
|
||||||
Set usage zone heating set point in celsius grads
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._heating_setpoint = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def heating_setback(self):
|
|
||||||
"""
|
|
||||||
Get usage zone heating setback in celsius grads
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._heating_setback
|
|
||||||
|
|
||||||
@heating_setback.setter
|
|
||||||
def heating_setback(self, value):
|
|
||||||
"""
|
|
||||||
Set usage zone heating setback in celsius grads
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._heating_setback = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cooling_setpoint(self):
|
|
||||||
"""
|
|
||||||
Get usage zone cooling setpoint in celsius grads
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._cooling_setpoint
|
|
||||||
|
|
||||||
@cooling_setpoint.setter
|
|
||||||
def cooling_setpoint(self, value):
|
|
||||||
"""
|
|
||||||
Set usage zone cooling setpoint in celsius grads
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._cooling_setpoint = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hours_day(self):
|
|
||||||
"""
|
|
||||||
Get usage zone usage hours per day
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._hours_day
|
|
||||||
|
|
||||||
@hours_day.setter
|
|
||||||
def hours_day(self, value):
|
|
||||||
"""
|
|
||||||
Set usage zone usage hours per day
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._hours_day = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def days_year(self):
|
|
||||||
"""
|
|
||||||
Get usage zone usage days per year
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._days_year
|
|
||||||
|
|
||||||
@days_year.setter
|
|
||||||
def days_year(self, value):
|
|
||||||
"""
|
|
||||||
Set usage zone usage days per year
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._days_year = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mechanical_air_change(self):
|
|
||||||
"""
|
|
||||||
Set usage zone mechanical air change in air change per hour (ACH)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
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 (ACH)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._mechanical_air_change = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def usage(self):
|
|
||||||
"""
|
|
||||||
Get usage zone usage
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._usage
|
|
||||||
|
|
||||||
@usage.setter
|
|
||||||
def usage(self, value):
|
|
||||||
"""
|
|
||||||
Get usage zone usage
|
|
||||||
:param value: str
|
|
||||||
"""
|
|
||||||
self._usage = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def occupants(self) -> List[Occupants]:
|
|
||||||
"""
|
|
||||||
Get occupants data
|
|
||||||
:return: [Occupants]
|
|
||||||
"""
|
|
||||||
return self._occupants
|
|
||||||
|
|
||||||
@occupants.setter
|
|
||||||
def occupants(self, values):
|
|
||||||
"""
|
|
||||||
Set occupants data
|
|
||||||
:param values: [Occupants]
|
|
||||||
"""
|
|
||||||
self._occupants = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def heating_schedule(self) -> dict:
|
|
||||||
"""
|
|
||||||
Get heating schedules: list of 0, 1 that define whether the heating system should be OFF or ON
|
|
||||||
:return: dict{DataFrame(int)}
|
|
||||||
"""
|
|
||||||
return self._heating_schedule
|
|
||||||
|
|
||||||
@heating_schedule.setter
|
|
||||||
def heating_schedule(self, values):
|
|
||||||
"""
|
|
||||||
heating schedules
|
|
||||||
:param values: dict{DataFrame(int)}
|
|
||||||
"""
|
|
||||||
self._heating_schedule = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cooling_schedule(self) -> dict:
|
|
||||||
"""
|
|
||||||
Get cooling schedules: list of 0, 1 that define whether the cooling system should be OFF or ON
|
|
||||||
:return: dict{DataFrame(int)}
|
|
||||||
"""
|
|
||||||
return self._cooling_schedule
|
|
||||||
|
|
||||||
@cooling_schedule.setter
|
|
||||||
def cooling_schedule(self, values):
|
|
||||||
"""
|
|
||||||
cooling schedules
|
|
||||||
:param values: dict{DataFrame(int)}
|
|
||||||
"""
|
|
||||||
self._cooling_schedule = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ventilation_schedule(self) -> dict:
|
|
||||||
"""
|
|
||||||
Get ventilation schedules: list of 0, 1 that define whether the ventilation system should be OFF or ON
|
|
||||||
:return: dict{DataFrame(int)}
|
|
||||||
"""
|
|
||||||
return self._ventilation_schedule
|
|
||||||
|
|
||||||
@ventilation_schedule.setter
|
|
||||||
def ventilation_schedule(self, values):
|
|
||||||
"""
|
|
||||||
ventilation_schedule schedules
|
|
||||||
:param values: dict{DataFrame(int)}
|
|
||||||
"""
|
|
||||||
self._ventilation_schedule = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def schedules(self) -> dict:
|
|
||||||
"""
|
|
||||||
Get schedules of diverse issues in a dictionary
|
|
||||||
:return: dict()
|
|
||||||
"""
|
|
||||||
return self._schedules
|
|
||||||
|
|
||||||
@schedules.setter
|
|
||||||
def schedules(self, values):
|
|
||||||
"""
|
|
||||||
Set schedules of diverse issues in a dictionary
|
|
||||||
:param values: dict()
|
|
||||||
"""
|
|
||||||
self._schedules = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def occupancy_density(self):
|
|
||||||
"""
|
|
||||||
Get schedules density in persons per m2
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._occupancy_density
|
|
||||||
|
|
||||||
@occupancy_density.setter
|
|
||||||
def occupancy_density(self, values):
|
|
||||||
"""
|
|
||||||
schedules density in persons per m2
|
|
||||||
:param values: float
|
|
||||||
"""
|
|
||||||
self._occupancy_density = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dhw_average_volume_pers_day(self):
|
|
||||||
"""
|
|
||||||
Get average DHW consumption in m3 per person per day
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._dhw_average_volume_pers_day
|
|
||||||
|
|
||||||
@dhw_average_volume_pers_day.setter
|
|
||||||
def dhw_average_volume_pers_day(self, values):
|
|
||||||
"""
|
|
||||||
average DHW consumption in m3 per person per day
|
|
||||||
:param values: float
|
|
||||||
"""
|
|
||||||
self._dhw_average_volume_pers_day = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dhw_preparation_temperature(self):
|
|
||||||
"""
|
|
||||||
Get preparation temperature of the DHW in degree Celsius
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._dhw_preparation_temperature
|
|
||||||
|
|
||||||
@dhw_preparation_temperature.setter
|
|
||||||
def dhw_preparation_temperature(self, values):
|
|
||||||
"""
|
|
||||||
preparation temperature of the DHW in degree Celsius
|
|
||||||
:param values: float
|
|
||||||
"""
|
|
||||||
self._dhw_preparation_temperature = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def electrical_app_average_consumption_sqm_year(self):
|
|
||||||
"""
|
|
||||||
Get average consumption of electrical appliances in Joules hour per m2 and year (J/m2yr)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._electrical_app_average_consumption_sqm_year
|
|
||||||
|
|
||||||
@electrical_app_average_consumption_sqm_year.setter
|
|
||||||
def electrical_app_average_consumption_sqm_year(self, values):
|
|
||||||
"""
|
|
||||||
average consumption of electrical appliances in Joules per m2 and year (J/m2yr)
|
|
||||||
:param values: float
|
|
||||||
"""
|
|
||||||
self._electrical_app_average_consumption_sqm_year = values
|
|
||||||
|
|
||||||
@property
|
|
||||||
def volume_geometry(self) -> Polyhedron:
|
|
||||||
"""
|
|
||||||
Get the polyhedron defined by the usage zone
|
|
||||||
:return: Polyhedron
|
|
||||||
"""
|
|
||||||
return self._volume_geometry
|
|
||||||
|
|
||||||
@property
|
|
||||||
def volume(self):
|
|
||||||
"""
|
|
||||||
Get the volume in m3
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._volume
|
|
||||||
|
|
||||||
@volume.setter
|
|
||||||
def volume(self, value):
|
|
||||||
"""
|
|
||||||
Volume in m3 setter
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._volume = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_heated(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone heated flag
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
return self._is_heated
|
|
||||||
|
|
||||||
@is_heated.setter
|
|
||||||
def is_heated(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal zone heated flag
|
|
||||||
:param value: Boolean
|
|
||||||
"""
|
|
||||||
self._is_heated = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_cooled(self):
|
|
||||||
"""
|
|
||||||
Get thermal zone cooled flag
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
return self._is_cooled
|
|
||||||
|
|
||||||
@is_cooled.setter
|
|
||||||
def is_cooled(self, value):
|
|
||||||
"""
|
|
||||||
Set thermal zone cooled flag
|
|
||||||
:param value: Boolean
|
|
||||||
"""
|
|
||||||
self._is_cooled = value
|
|
|
@ -1,38 +0,0 @@
|
||||||
"""
|
|
||||||
BuildingsCluster module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List, TypeVar
|
|
||||||
|
|
||||||
from city_model_structure.city_objects_cluster import CityObjectsCluster
|
|
||||||
|
|
||||||
CityObject = TypeVar('CityObject')
|
|
||||||
|
|
||||||
|
|
||||||
class BuildingsCluster(CityObjectsCluster):
|
|
||||||
"""
|
|
||||||
BuildingsCluster(CityObjectsCluster) class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, city_objects):
|
|
||||||
self._cluster_type = 'buildings'
|
|
||||||
super().__init__(name, self._cluster_type, city_objects)
|
|
||||||
self._name = name
|
|
||||||
self._city_objects = city_objects
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
Cluster type
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._cluster_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city_objects(self) -> List[CityObject]:
|
|
||||||
"""
|
|
||||||
List of city objects conforming the cluster
|
|
||||||
:return: [CityObject]
|
|
||||||
"""
|
|
||||||
return self._city_objects
|
|
|
@ -1,366 +0,0 @@
|
||||||
"""
|
|
||||||
City module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
import sys
|
|
||||||
import pickle
|
|
||||||
import math
|
|
||||||
from typing import List, Union, TypeVar
|
|
||||||
import pyproj
|
|
||||||
from pyproj import Transformer
|
|
||||||
|
|
||||||
|
|
||||||
from city_model_structure.building import Building
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
from city_model_structure.city_objects_cluster import CityObjectsCluster
|
|
||||||
from city_model_structure.buildings_cluster import BuildingsCluster
|
|
||||||
from city_model_structure.parts_consisting_building import PartsConsistingBuilding
|
|
||||||
from helpers.geometry_helper import GeometryHelper
|
|
||||||
from helpers.location import Location
|
|
||||||
|
|
||||||
|
|
||||||
Path = TypeVar('Path')
|
|
||||||
|
|
||||||
|
|
||||||
class City:
|
|
||||||
"""
|
|
||||||
City class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, lower_corner, upper_corner, srs_name):
|
|
||||||
self._name = None
|
|
||||||
self._lower_corner = lower_corner
|
|
||||||
self._upper_corner = upper_corner
|
|
||||||
self._buildings = None
|
|
||||||
self._srs_name = srs_name
|
|
||||||
self._geometry = GeometryHelper()
|
|
||||||
# todo: right now extracted at city level, in the future should be extracted also at building level if exist
|
|
||||||
self._location = None
|
|
||||||
self._country_code = None
|
|
||||||
self._climate_reference_city = None
|
|
||||||
self._climate_file = None
|
|
||||||
self._latitude = None
|
|
||||||
self._longitude = None
|
|
||||||
self._time_zone = None
|
|
||||||
self._buildings_clusters = None
|
|
||||||
self._parts_consisting_buildings = None
|
|
||||||
self._city_objects_clusters = None
|
|
||||||
self._city_objects = None
|
|
||||||
|
|
||||||
def _get_location(self) -> Location:
|
|
||||||
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:
|
|
||||||
sys.stderr.write('Invalid projection reference system, please check the input data. '
|
|
||||||
'(e.g. in CityGML files: srs_name)\n')
|
|
||||||
sys.exit()
|
|
||||||
transformer = Transformer.from_crs(input_reference, gps)
|
|
||||||
coordinates = transformer.transform(self.lower_corner[0], self.lower_corner[1])
|
|
||||||
self._location = GeometryHelper.get_location(coordinates[0], coordinates[1])
|
|
||||||
return self._location
|
|
||||||
|
|
||||||
@property
|
|
||||||
def country_code(self):
|
|
||||||
"""
|
|
||||||
City country code
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._get_location().country
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
City name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._get_location().city
|
|
||||||
|
|
||||||
@property
|
|
||||||
def climate_reference_city(self):
|
|
||||||
"""
|
|
||||||
Name of the city of reference for climatic information
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._climate_reference_city
|
|
||||||
|
|
||||||
@climate_reference_city.setter
|
|
||||||
def climate_reference_city(self, value):
|
|
||||||
"""
|
|
||||||
Name of the city of reference for climatic information
|
|
||||||
:param value: str
|
|
||||||
"""
|
|
||||||
self._climate_reference_city = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def climate_file(self) -> Path:
|
|
||||||
"""
|
|
||||||
Full path of climate file
|
|
||||||
:return: Path
|
|
||||||
"""
|
|
||||||
return self._climate_file
|
|
||||||
|
|
||||||
@climate_file.setter
|
|
||||||
def climate_file(self, value):
|
|
||||||
"""
|
|
||||||
Full path of climate file
|
|
||||||
:param value: Path
|
|
||||||
"""
|
|
||||||
self._climate_file = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city_objects(self) -> Union[List[CityObject], None]:
|
|
||||||
"""
|
|
||||||
City objects belonging to the city
|
|
||||||
:return: None or [CityObject]
|
|
||||||
"""
|
|
||||||
if self._city_objects is None:
|
|
||||||
if self.city_objects_clusters is None:
|
|
||||||
self._city_objects = []
|
|
||||||
else:
|
|
||||||
self._city_objects = self.city_objects_clusters
|
|
||||||
if self.buildings is not None:
|
|
||||||
for building in self.buildings:
|
|
||||||
self._city_objects.append(building)
|
|
||||||
return self._city_objects
|
|
||||||
|
|
||||||
@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
|
|
||||||
:return: NotImplementedError
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def composting_plants(self) -> NotImplementedError:
|
|
||||||
"""
|
|
||||||
Composting plants belonging to the city
|
|
||||||
:return: NotImplementedError
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@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: [x,y,z]
|
|
||||||
"""
|
|
||||||
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 or not implemented error
|
|
||||||
"""
|
|
||||||
if new_city_object.type == 'building':
|
|
||||||
if self._buildings is None:
|
|
||||||
self._buildings = []
|
|
||||||
self._buildings.append(new_city_object)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(new_city_object.type)
|
|
||||||
|
|
||||||
def remove_city_object(self, city_object):
|
|
||||||
"""
|
|
||||||
Remove a CityObject from the city
|
|
||||||
:param city_object:CityObject
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if city_object.type != 'building':
|
|
||||||
raise NotImplementedError(city_object.type)
|
|
||||||
if self._buildings is None or self._buildings == []:
|
|
||||||
sys.stderr.write('Warning: impossible to remove city_object, the city is empty\n')
|
|
||||||
else:
|
|
||||||
if city_object in self._buildings:
|
|
||||||
self._buildings.remove(city_object)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def srs_name(self):
|
|
||||||
"""
|
|
||||||
srs name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._srs_name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, value):
|
|
||||||
"""
|
|
||||||
Set the city name
|
|
||||||
:param value:str
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._name = value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def load(city_filename) -> City:
|
|
||||||
"""
|
|
||||||
Load a city saved with city.save(city_filename)
|
|
||||||
:param city_filename: city filename
|
|
||||||
:return: City
|
|
||||||
"""
|
|
||||||
with open(city_filename, 'rb') as file:
|
|
||||||
return pickle.load(file)
|
|
||||||
|
|
||||||
def save(self, city_filename):
|
|
||||||
"""
|
|
||||||
Save a city into the given filename
|
|
||||||
:param city_filename: destination city filename
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
with open(city_filename, 'wb') as file:
|
|
||||||
pickle.dump(self, file)
|
|
||||||
|
|
||||||
def region(self, center, radius) -> City:
|
|
||||||
"""
|
|
||||||
Save a city into the given filename
|
|
||||||
:param center: specific point in space [x, y, z]
|
|
||||||
:param radius: distance to center of the sphere selected in meters
|
|
||||||
:return: selected_region_city
|
|
||||||
"""
|
|
||||||
selected_region_lower_corner = [center[0] - radius, center[1] - radius, center[2] - radius]
|
|
||||||
selected_region_upper_corner = [center[0] + radius, center[1] + radius, center[2] + radius]
|
|
||||||
selected_region_city = City(selected_region_lower_corner, selected_region_upper_corner, srs_name=self.srs_name)
|
|
||||||
selected_region_city.climate_file = self.climate_file
|
|
||||||
# selected_region_city.climate_reference_city = self.climate_reference_city
|
|
||||||
for city_object in self.city_objects:
|
|
||||||
location = city_object.centroid
|
|
||||||
if location is not None:
|
|
||||||
distance = math.sqrt(math.pow(location[0]-center[0], 2) + math.pow(location[1]-center[1], 2)
|
|
||||||
+ math.pow(location[2]-center[2], 2))
|
|
||||||
if distance < radius:
|
|
||||||
selected_region_city.add_city_object(city_object)
|
|
||||||
return selected_region_city
|
|
||||||
|
|
||||||
@property
|
|
||||||
def latitude(self):
|
|
||||||
"""
|
|
||||||
city latitude in degrees
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._latitude
|
|
||||||
|
|
||||||
@latitude.setter
|
|
||||||
def latitude(self, value):
|
|
||||||
"""
|
|
||||||
city latitude in degrees
|
|
||||||
:parameter value: float
|
|
||||||
"""
|
|
||||||
self._latitude = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def longitude(self):
|
|
||||||
"""
|
|
||||||
city longitude in degrees
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._longitude
|
|
||||||
|
|
||||||
@longitude.setter
|
|
||||||
def longitude(self, value):
|
|
||||||
"""
|
|
||||||
city longitude in degrees
|
|
||||||
:parameter value: float
|
|
||||||
"""
|
|
||||||
self._longitude = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def time_zone(self):
|
|
||||||
"""
|
|
||||||
city time_zone
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._time_zone
|
|
||||||
|
|
||||||
@time_zone.setter
|
|
||||||
def time_zone(self, value):
|
|
||||||
"""
|
|
||||||
city time_zone
|
|
||||||
:parameter value: float
|
|
||||||
"""
|
|
||||||
self._time_zone = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def buildings_clusters(self) -> Union[List[BuildingsCluster], None]:
|
|
||||||
"""
|
|
||||||
buildings clusters belonging to the city
|
|
||||||
:return: None or [BuildingsCluster]
|
|
||||||
"""
|
|
||||||
return self._buildings_clusters
|
|
||||||
|
|
||||||
@property
|
|
||||||
def parts_consisting_buildings(self) -> Union[List[PartsConsistingBuilding], None]:
|
|
||||||
"""
|
|
||||||
Parts consisting buildings belonging to the city
|
|
||||||
:return: None or [PartsConsistingBuilding]
|
|
||||||
"""
|
|
||||||
return self._parts_consisting_buildings
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city_objects_clusters(self) -> Union[List[CityObjectsCluster], None]:
|
|
||||||
"""
|
|
||||||
City objects clusters belonging to the city
|
|
||||||
:return: None or [CityObjectsCluster]
|
|
||||||
"""
|
|
||||||
if self.buildings_clusters is None:
|
|
||||||
self._city_objects_clusters = []
|
|
||||||
else:
|
|
||||||
self._city_objects_clusters = self.buildings_clusters
|
|
||||||
if self.parts_consisting_buildings is not None:
|
|
||||||
self._city_objects_clusters.append(self.parts_consisting_buildings)
|
|
||||||
return self._city_objects_clusters
|
|
||||||
|
|
||||||
def add_city_objects_cluster(self, new_city_objects_cluster):
|
|
||||||
"""
|
|
||||||
Add a CityObject to the city
|
|
||||||
:param new_city_objects_cluster:CityObjectsCluster
|
|
||||||
:return: None or NotImplementedError
|
|
||||||
"""
|
|
||||||
if new_city_objects_cluster.type == 'buildings':
|
|
||||||
if self._buildings_clusters is None:
|
|
||||||
self._buildings_clusters = []
|
|
||||||
self._buildings_clusters.append(new_city_objects_cluster)
|
|
||||||
elif new_city_objects_cluster.type == 'building_parts':
|
|
||||||
if self._parts_consisting_buildings is None:
|
|
||||||
self._parts_consisting_buildings = []
|
|
||||||
self._parts_consisting_buildings.append(new_city_objects_cluster)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
|
@ -1,229 +0,0 @@
|
||||||
"""
|
|
||||||
CityObject module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import math
|
|
||||||
from typing import List, Union
|
|
||||||
|
|
||||||
|
|
||||||
from city_model_structure.iot.sensor import Sensor
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
from city_model_structure.attributes.polyhedron import Polyhedron
|
|
||||||
from helpers.configuration_helper import ConfigurationHelper
|
|
||||||
|
|
||||||
|
|
||||||
class CityObject:
|
|
||||||
"""
|
|
||||||
class CityObject
|
|
||||||
"""
|
|
||||||
def __init__(self, name, lod, surfaces, city_lower_corner):
|
|
||||||
self._name = name
|
|
||||||
self._lod = lod
|
|
||||||
self._surfaces = surfaces
|
|
||||||
self._city_lower_corner = city_lower_corner
|
|
||||||
self._type = None
|
|
||||||
self._city_object_lower_corner = None
|
|
||||||
self._detailed_polyhedron = None
|
|
||||||
self._simplified_polyhedron = None
|
|
||||||
self._min_x = ConfigurationHelper().max_coordinate
|
|
||||||
self._min_y = ConfigurationHelper().max_coordinate
|
|
||||||
self._min_z = ConfigurationHelper().max_coordinate
|
|
||||||
self._centroid = None
|
|
||||||
self._external_temperature = dict()
|
|
||||||
self._global_horizontal = dict()
|
|
||||||
self._diffuse = dict()
|
|
||||||
self._beam = dict()
|
|
||||||
self._sensors = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def lod(self):
|
|
||||||
"""
|
|
||||||
City object level of detail 1, 2, 3 or 4
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
lod = math.log(self._lod, 2) + 1
|
|
||||||
return lod
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
city object type
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def volume(self):
|
|
||||||
"""
|
|
||||||
City object volume in cubic meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self.simplified_polyhedron.volume
|
|
||||||
|
|
||||||
@property
|
|
||||||
def detailed_polyhedron(self) -> Polyhedron:
|
|
||||||
"""
|
|
||||||
City object polyhedron including details such as holes
|
|
||||||
:return: Polyhedron
|
|
||||||
"""
|
|
||||||
if self._detailed_polyhedron is None:
|
|
||||||
polygons = []
|
|
||||||
for surface in self.surfaces:
|
|
||||||
polygons.append(surface.solid_polygon)
|
|
||||||
if surface.holes_polygons is not None:
|
|
||||||
for hole_polygon in surface.holes_polygons:
|
|
||||||
polygons.append(hole_polygon)
|
|
||||||
self._detailed_polyhedron = Polyhedron(polygons)
|
|
||||||
return self._detailed_polyhedron
|
|
||||||
|
|
||||||
@property
|
|
||||||
def simplified_polyhedron(self) -> Polyhedron:
|
|
||||||
"""
|
|
||||||
City object polyhedron, just the simple lod2 representation
|
|
||||||
:return: Polyhedron
|
|
||||||
"""
|
|
||||||
if self._simplified_polyhedron is None:
|
|
||||||
polygons = []
|
|
||||||
for surface in self.surfaces:
|
|
||||||
polygons.append(surface.perimeter_polygon)
|
|
||||||
self._simplified_polyhedron = Polyhedron(polygons)
|
|
||||||
return self._simplified_polyhedron
|
|
||||||
|
|
||||||
@property
|
|
||||||
def surfaces(self) -> List[Surface]:
|
|
||||||
"""
|
|
||||||
City object surfaces
|
|
||||||
:return: [Surface]
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
return None
|
|
||||||
|
|
||||||
def surface_by_id(self, identification_number) -> Union[Surface, None]:
|
|
||||||
"""
|
|
||||||
Get the city object surface with a given name
|
|
||||||
:param identification_number: str
|
|
||||||
:return: None or Surface
|
|
||||||
"""
|
|
||||||
for s in self.surfaces:
|
|
||||||
if str(s.id) == str(identification_number):
|
|
||||||
return s
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def centroid(self):
|
|
||||||
"""
|
|
||||||
City object centroid
|
|
||||||
:return: [x,y,z]
|
|
||||||
"""
|
|
||||||
if self._centroid is None:
|
|
||||||
self._centroid = self.simplified_polyhedron.centroid
|
|
||||||
return self._centroid
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_height(self):
|
|
||||||
"""
|
|
||||||
City object maximal height in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self.simplified_polyhedron.max_z
|
|
||||||
|
|
||||||
@property
|
|
||||||
def external_temperature(self) -> dict:
|
|
||||||
"""
|
|
||||||
external temperature surrounding the city object in grads Celsius
|
|
||||||
:return: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
return self._external_temperature
|
|
||||||
|
|
||||||
@external_temperature.setter
|
|
||||||
def external_temperature(self, value):
|
|
||||||
"""
|
|
||||||
external temperature surrounding the city object in grads Celsius
|
|
||||||
:param value: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
self._external_temperature = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def global_horizontal(self) -> dict:
|
|
||||||
"""
|
|
||||||
global horizontal radiation surrounding the city object in W/m2
|
|
||||||
:return: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
return self._global_horizontal
|
|
||||||
|
|
||||||
@global_horizontal.setter
|
|
||||||
def global_horizontal(self, value):
|
|
||||||
"""
|
|
||||||
global horizontal radiation surrounding the city object in W/m2
|
|
||||||
:param value: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
self._global_horizontal = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def diffuse(self) -> dict:
|
|
||||||
"""
|
|
||||||
diffuse radiation surrounding the city object in W/m2
|
|
||||||
:return: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
return self._diffuse
|
|
||||||
|
|
||||||
@diffuse.setter
|
|
||||||
def diffuse(self, value):
|
|
||||||
"""
|
|
||||||
diffuse radiation surrounding the city object in W/m2
|
|
||||||
:param value: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
self._diffuse = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def beam(self) -> dict:
|
|
||||||
"""
|
|
||||||
beam radiation surrounding the city object in W/m2
|
|
||||||
:return: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
return self._beam
|
|
||||||
|
|
||||||
@beam.setter
|
|
||||||
def beam(self, value):
|
|
||||||
"""
|
|
||||||
beam radiation surrounding the city object in W/m2
|
|
||||||
:param value: dict{DataFrame(float)}
|
|
||||||
"""
|
|
||||||
self._beam = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def lower_corner(self):
|
|
||||||
"""
|
|
||||||
City object lower corner coordinates [x, y, z]
|
|
||||||
"""
|
|
||||||
if self._city_object_lower_corner is None:
|
|
||||||
self._city_object_lower_corner = [self._min_x, self._min_y, self._min_z]
|
|
||||||
return self._city_object_lower_corner
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sensors(self) -> List[Sensor]:
|
|
||||||
"""
|
|
||||||
Sensor list belonging to the city object
|
|
||||||
:return: [Sensor]
|
|
||||||
"""
|
|
||||||
return self._sensors
|
|
||||||
|
|
||||||
@sensors.setter
|
|
||||||
def sensors(self, value):
|
|
||||||
"""
|
|
||||||
Sensor list belonging to the city object
|
|
||||||
:param value: [Sensor]
|
|
||||||
"""
|
|
||||||
self._sensors = value
|
|
|
@ -1,72 +0,0 @@
|
||||||
"""
|
|
||||||
CityObjectsCluster module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from abc import ABC
|
|
||||||
from typing import List
|
|
||||||
from city_model_structure.iot.sensor import Sensor
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
|
|
||||||
|
|
||||||
class CityObjectsCluster(ABC, CityObject):
|
|
||||||
"""
|
|
||||||
CityObjectsCluster(ABC) class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, cluster_type, city_objects):
|
|
||||||
self._name = name
|
|
||||||
self._cluster_type = cluster_type
|
|
||||||
self._city_objects = city_objects
|
|
||||||
self._sensors = []
|
|
||||||
self._lod = ''
|
|
||||||
super().__init__(name, self._lod, None, None)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
Cluster name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
City object cluster type raises NotImplemented error
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city_objects(self):
|
|
||||||
"""
|
|
||||||
City objects raises NotImplemented error
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def add_city_object(self, city_object) -> List[CityObject]:
|
|
||||||
"""
|
|
||||||
add new object to the cluster
|
|
||||||
:return: [CityObjects]
|
|
||||||
"""
|
|
||||||
if self._city_objects is None:
|
|
||||||
self._city_objects = [city_object]
|
|
||||||
else:
|
|
||||||
self._city_objects.append(city_object)
|
|
||||||
return self._city_objects
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sensors(self) -> List[Sensor]:
|
|
||||||
"""
|
|
||||||
Sensor list belonging to the city objects cluster
|
|
||||||
:return: [Sensor]
|
|
||||||
"""
|
|
||||||
return self._sensors
|
|
||||||
|
|
||||||
@sensors.setter
|
|
||||||
def sensors(self, value):
|
|
||||||
"""
|
|
||||||
Sensor list belonging to the city objects cluster
|
|
||||||
:param value: [Sensor]
|
|
||||||
"""
|
|
||||||
self._sensors = value
|
|
|
@ -1,33 +0,0 @@
|
||||||
"""
|
|
||||||
Composting plant module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
|
|
||||||
|
|
||||||
class CompostingPlant(CityObject):
|
|
||||||
"""
|
|
||||||
CompostingPlant(CityObject) class
|
|
||||||
"""
|
|
||||||
def __init__(self, lod, surfaces, name, waste_type, capacity):
|
|
||||||
super().__init__(lod, surfaces, name, [])
|
|
||||||
self._waste_type = waste_type
|
|
||||||
self._capacity = capacity
|
|
||||||
self._type = 'composting_plant'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def waste_type(self):
|
|
||||||
"""
|
|
||||||
Get waste_type treated in composting plant
|
|
||||||
:return: waste_type
|
|
||||||
"""
|
|
||||||
return self._waste_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def capacity(self):
|
|
||||||
"""
|
|
||||||
Get capacity of composting plant in kg
|
|
||||||
:return: capacity
|
|
||||||
"""
|
|
||||||
return self._capacity
|
|
|
@ -1,46 +0,0 @@
|
||||||
"""
|
|
||||||
heat_pump module defines a heat pump
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class HeatPump:
|
|
||||||
"""
|
|
||||||
HeatPump class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._seasonal_mean_cop = None
|
|
||||||
self._seasonal_mean_coverage_factor = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def seasonal_mean_cop(self):
|
|
||||||
"""
|
|
||||||
Get seasonal mean COP (-)
|
|
||||||
:return: real
|
|
||||||
"""
|
|
||||||
return self._seasonal_mean_cop
|
|
||||||
|
|
||||||
@seasonal_mean_cop.setter
|
|
||||||
def seasonal_mean_cop(self, value):
|
|
||||||
"""
|
|
||||||
Get seasonal mean COP (-)
|
|
||||||
:param value: real
|
|
||||||
"""
|
|
||||||
self._seasonal_mean_cop = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def seasonal_mean_coverage_factor(self):
|
|
||||||
"""
|
|
||||||
Get percentage of demand covered by the hp (-)
|
|
||||||
:return: real
|
|
||||||
"""
|
|
||||||
return self._seasonal_mean_coverage_factor
|
|
||||||
|
|
||||||
@seasonal_mean_coverage_factor.setter
|
|
||||||
def seasonal_mean_coverage_factor(self, value):
|
|
||||||
"""
|
|
||||||
Set percentage of demand covered by the hp (-)
|
|
||||||
:return: real
|
|
||||||
"""
|
|
||||||
self._seasonal_mean_coverage_factor = value
|
|
|
@ -1,114 +0,0 @@
|
||||||
"""
|
|
||||||
pv_system defines a pv system including all components: PV panels, transformer...
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class PvSystem:
|
|
||||||
"""
|
|
||||||
PvSystem class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._modules_mean_seasonal_efficiency = None
|
|
||||||
self._total_area = None
|
|
||||||
self._module_area = None
|
|
||||||
self._number_of_modules = None
|
|
||||||
self._overall_system_performance_ratio = None
|
|
||||||
self._electricity_generation = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def modules_mean_seasonal_efficiency(self):
|
|
||||||
"""
|
|
||||||
Get mean modules efficiency (-)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._modules_mean_seasonal_efficiency
|
|
||||||
|
|
||||||
@modules_mean_seasonal_efficiency.setter
|
|
||||||
def modules_mean_seasonal_efficiency(self, value):
|
|
||||||
"""
|
|
||||||
Set mean modules efficiency (-)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._modules_mean_seasonal_efficiency = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def total_area(self):
|
|
||||||
"""
|
|
||||||
Get total modules area (m2)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._total_area
|
|
||||||
|
|
||||||
@total_area.setter
|
|
||||||
def total_area(self, value):
|
|
||||||
"""
|
|
||||||
Set total modules area (m2)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._total_area = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def module_area(self):
|
|
||||||
"""
|
|
||||||
Get module area (m2)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._module_area
|
|
||||||
|
|
||||||
@module_area.setter
|
|
||||||
def module_area(self, value):
|
|
||||||
"""
|
|
||||||
Set module area (m2)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._module_area = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def number_of_modules(self):
|
|
||||||
"""
|
|
||||||
Get number of modules
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._number_of_modules
|
|
||||||
|
|
||||||
@number_of_modules.setter
|
|
||||||
def number_of_modules(self, value):
|
|
||||||
"""
|
|
||||||
Set number of modules
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._number_of_modules = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def overall_system_performance_ratio(self):
|
|
||||||
"""
|
|
||||||
Get overall system performance ratio (-)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._overall_system_performance_ratio
|
|
||||||
|
|
||||||
@overall_system_performance_ratio.setter
|
|
||||||
def overall_system_performance_ratio(self, value):
|
|
||||||
"""
|
|
||||||
Set overall system performance ratio (-)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._overall_system_performance_ratio = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def electricity_generation(self):
|
|
||||||
"""
|
|
||||||
Get electricity generation (J)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._electricity_generation
|
|
||||||
|
|
||||||
@electricity_generation.setter
|
|
||||||
def electricity_generation(self, value):
|
|
||||||
"""
|
|
||||||
Set electricity generation (J)
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._electricity_generation = value
|
|
|
@ -1,42 +0,0 @@
|
||||||
"""
|
|
||||||
Energy Sensor module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
from city_model_structure.iot.sensor import Sensor
|
|
||||||
|
|
||||||
|
|
||||||
class ConcordiaEnergySensor(Sensor):
|
|
||||||
"""
|
|
||||||
Concordia energy sensor.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
super().__init__()
|
|
||||||
self._name = name
|
|
||||||
self._interval = 5
|
|
||||||
self._interval_units = 'minutes'
|
|
||||||
self._type = 'ConcordiaEnergySensor'
|
|
||||||
self._units = 'kW'
|
|
||||||
self._measures = pd.DataFrame(columns=["Date time", "Energy consumption"])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def measures(self) -> pd.DataFrame:
|
|
||||||
"""
|
|
||||||
Sensor measures [yyyy-mm-dd, hh:mm:ss kW]
|
|
||||||
:return: DataFrame["Date time", "Energy consumption"]
|
|
||||||
"""
|
|
||||||
return self._measures
|
|
||||||
|
|
||||||
@measures.deleter
|
|
||||||
def measures(self):
|
|
||||||
self._measures.drop = None
|
|
||||||
|
|
||||||
def add_period(self, measures):
|
|
||||||
"""
|
|
||||||
Add or update a period measures to the dataframe
|
|
||||||
"""
|
|
||||||
measures = self._measures.append(measures, ignore_index=True)
|
|
||||||
self._measures = measures.drop_duplicates('Date time', keep='last')
|
|
|
@ -1,42 +0,0 @@
|
||||||
"""
|
|
||||||
Gas Flow Sensor module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
from city_model_structure.iot.sensor import Sensor
|
|
||||||
|
|
||||||
|
|
||||||
class ConcordiaGasFlowSensor(Sensor):
|
|
||||||
"""
|
|
||||||
Concordia gas flow sensor.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
super().__init__()
|
|
||||||
self._name = name
|
|
||||||
self._interval = 5
|
|
||||||
self._interval_units = 'minutes'
|
|
||||||
self._type = 'ConcordiaGasFlowSensor'
|
|
||||||
self._units = 'm3'
|
|
||||||
self._measures = pd.DataFrame(columns=["Date time", "Gas Flow Cumulative Monthly"])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def measures(self) -> pd.DataFrame:
|
|
||||||
"""
|
|
||||||
Sensor measures [yyyy-mm-dd, hh:mm:ss m3]
|
|
||||||
:return: DataFrame["Date time", "Gas Flow Cumulative Monthly"]
|
|
||||||
"""
|
|
||||||
return self._measures
|
|
||||||
|
|
||||||
@measures.deleter
|
|
||||||
def measures(self):
|
|
||||||
self._measures.drop = None
|
|
||||||
|
|
||||||
def add_period(self, measures):
|
|
||||||
"""
|
|
||||||
Add or update a period measures to the dataframe
|
|
||||||
"""
|
|
||||||
measures = self._measures.append(measures, ignore_index=True)
|
|
||||||
self._measures = measures.drop_duplicates('Date time', keep='last')
|
|
|
@ -1,42 +0,0 @@
|
||||||
"""
|
|
||||||
Temperature Sensor module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
from city_model_structure.iot.sensor import Sensor
|
|
||||||
|
|
||||||
|
|
||||||
class ConcordiaTemperatureSensor(Sensor):
|
|
||||||
"""
|
|
||||||
Concordia temperature sensor.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
super().__init__()
|
|
||||||
self._name = name
|
|
||||||
self._interval = 5
|
|
||||||
self._interval_units = 'minutes'
|
|
||||||
self._type = 'ConcordiaTemperatureSensor'
|
|
||||||
self._units = 'Celsius'
|
|
||||||
self._measures = pd.DataFrame(columns=["Date time", "Temperature"])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def measures(self) -> pd.DataFrame:
|
|
||||||
"""
|
|
||||||
Sensor measures [yyyy-mm-dd, hh:mm:ss Celsius]
|
|
||||||
:return: DataFrame["Date time", "Temperature"]
|
|
||||||
"""
|
|
||||||
return self._measures
|
|
||||||
|
|
||||||
@measures.deleter
|
|
||||||
def measures(self):
|
|
||||||
self._measures.drop = None
|
|
||||||
|
|
||||||
def add_period(self, measures):
|
|
||||||
"""
|
|
||||||
Add or update a period measures to the dataframe
|
|
||||||
"""
|
|
||||||
measures = self._measures.append(measures, ignore_index=True)
|
|
||||||
self._measures = measures.drop_duplicates('Date time', keep='last')
|
|
|
@ -1,73 +0,0 @@
|
||||||
"""
|
|
||||||
Sensor module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from helpers.location import Location
|
|
||||||
|
|
||||||
|
|
||||||
class Sensor:
|
|
||||||
"""
|
|
||||||
Sensor abstract class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._name = None
|
|
||||||
self._type = None
|
|
||||||
self._units = None
|
|
||||||
self._location = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
Get sensor name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, value):
|
|
||||||
"""
|
|
||||||
Set sensor name
|
|
||||||
:param value: str
|
|
||||||
"""
|
|
||||||
self._name = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
Get sensor type
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def units(self):
|
|
||||||
"""
|
|
||||||
Get sensor units
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._units
|
|
||||||
|
|
||||||
@property
|
|
||||||
def location(self) -> Location:
|
|
||||||
"""
|
|
||||||
Get sensor location
|
|
||||||
:return: Location
|
|
||||||
"""
|
|
||||||
return self._location
|
|
||||||
|
|
||||||
@location.setter
|
|
||||||
def location(self, value):
|
|
||||||
"""
|
|
||||||
Set sensor location
|
|
||||||
:param value: Location
|
|
||||||
"""
|
|
||||||
self._location = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def measures(self):
|
|
||||||
"""
|
|
||||||
Sensor measures
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
|
@ -1,51 +0,0 @@
|
||||||
"""
|
|
||||||
Network module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
"""
|
|
||||||
import uuid
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
from city_model_structure.attributes.edge import Edge
|
|
||||||
from city_model_structure.attributes.node import Node
|
|
||||||
|
|
||||||
|
|
||||||
class Network(CityObject):
|
|
||||||
"""
|
|
||||||
Generic network class to be used as base for the network models
|
|
||||||
"""
|
|
||||||
def __init__(self, name, edges=None, nodes=None):
|
|
||||||
super().__init__(name, 0, [], None)
|
|
||||||
if nodes is None:
|
|
||||||
nodes = []
|
|
||||||
if edges is None:
|
|
||||||
edges = []
|
|
||||||
self._id = None
|
|
||||||
self._edges = edges
|
|
||||||
self._nodes = nodes
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
"""
|
|
||||||
Network id, an universally unique identifier randomly generated
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if self._id is None:
|
|
||||||
self._id = uuid.uuid4()
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def edges(self) -> List[Edge]:
|
|
||||||
"""
|
|
||||||
Network edges
|
|
||||||
"""
|
|
||||||
return self._edges
|
|
||||||
|
|
||||||
@property
|
|
||||||
def nodes(self) -> List[Node]:
|
|
||||||
"""
|
|
||||||
Network nodes
|
|
||||||
"""
|
|
||||||
return self._nodes
|
|
|
@ -1,36 +0,0 @@
|
||||||
"""
|
|
||||||
PartsConsistingBuilding module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List, TypeVar
|
|
||||||
from city_model_structure.city_objects_cluster import CityObjectsCluster
|
|
||||||
CityObject = TypeVar('CityObject')
|
|
||||||
|
|
||||||
|
|
||||||
class PartsConsistingBuilding(CityObjectsCluster):
|
|
||||||
"""
|
|
||||||
PartsConsistingBuilding(CityObjectsCluster) class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, city_objects):
|
|
||||||
self._cluster_type = 'building_parts'
|
|
||||||
super().__init__(name, self._cluster_type, city_objects)
|
|
||||||
self._name = name
|
|
||||||
self._city_objects = city_objects
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
type of cluster
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._cluster_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city_objects(self) -> List[CityObject]:
|
|
||||||
"""
|
|
||||||
city objects that compose the cluster
|
|
||||||
:return: [CityObject]
|
|
||||||
"""
|
|
||||||
return self._city_objects
|
|
|
@ -1,44 +0,0 @@
|
||||||
"""
|
|
||||||
Subway entrance module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
|
|
||||||
|
|
||||||
class SubwayEntrance(CityObject):
|
|
||||||
"""
|
|
||||||
SubwayEntrance(CityObject) class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, latitude, longitude):
|
|
||||||
super().__init__(0, [], name, [])
|
|
||||||
self._name = name
|
|
||||||
self._latitude = latitude
|
|
||||||
self._longitude = longitude
|
|
||||||
self._type = 'subway_entrance'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def latitude(self):
|
|
||||||
# todo: to be defined the spacial point and the units
|
|
||||||
"""
|
|
||||||
Get latitude
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._latitude
|
|
||||||
|
|
||||||
@property
|
|
||||||
def longitude(self):
|
|
||||||
# todo: to be defined the spacial point and the units
|
|
||||||
"""
|
|
||||||
Get longitude
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._longitude
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
Get name
|
|
||||||
:return: string
|
|
||||||
"""
|
|
||||||
return self._name
|
|
|
@ -1,119 +0,0 @@
|
||||||
"""
|
|
||||||
Connection module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
Contributor Guille guille.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
from city_model_structure.attributes.edge import Edge
|
|
||||||
from city_model_structure.transport.lane import Lane
|
|
||||||
|
|
||||||
|
|
||||||
class Connection:
|
|
||||||
"""
|
|
||||||
Connection class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._from_edge = None
|
|
||||||
self._to_edge = None
|
|
||||||
self._from_lane = None
|
|
||||||
self._to_lane = None
|
|
||||||
self._pass = None
|
|
||||||
self._keep_clear = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def from_edge(self) -> Edge:
|
|
||||||
"""
|
|
||||||
Edge the vehicle leaves
|
|
||||||
:return: Edge
|
|
||||||
"""
|
|
||||||
return self._from_edge
|
|
||||||
|
|
||||||
@from_edge.setter
|
|
||||||
def from_edge(self, value):
|
|
||||||
"""
|
|
||||||
Edge the vehicle leaves setter
|
|
||||||
:param value: Edge
|
|
||||||
"""
|
|
||||||
self._from_edge = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def to_edge(self) -> Edge:
|
|
||||||
"""
|
|
||||||
Edge the vehicle reaches
|
|
||||||
:return: Edge
|
|
||||||
"""
|
|
||||||
return self._to_edge
|
|
||||||
|
|
||||||
@to_edge.setter
|
|
||||||
def to_edge(self, value):
|
|
||||||
"""
|
|
||||||
Edge the vehicle reaches setter
|
|
||||||
:param value: Edge
|
|
||||||
"""
|
|
||||||
self._to_edge = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def from_lane(self) -> Lane:
|
|
||||||
"""
|
|
||||||
Incoming lane
|
|
||||||
:return: Lane
|
|
||||||
"""
|
|
||||||
return self._to_lane
|
|
||||||
|
|
||||||
@from_lane.setter
|
|
||||||
def from_lane(self, value):
|
|
||||||
"""
|
|
||||||
Incoming lane setter
|
|
||||||
:param value: Lane
|
|
||||||
"""
|
|
||||||
self._from_lane = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def to_lane(self) -> Lane:
|
|
||||||
"""
|
|
||||||
Outgoing lane
|
|
||||||
:return: Lane
|
|
||||||
"""
|
|
||||||
return self._to_lane
|
|
||||||
|
|
||||||
@to_lane.setter
|
|
||||||
def to_lane(self, value):
|
|
||||||
"""
|
|
||||||
Outgoing lane setter
|
|
||||||
:param value: Lane
|
|
||||||
"""
|
|
||||||
self._to_lane = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pass_not_wait(self):
|
|
||||||
"""
|
|
||||||
if set, vehicles which pass this (lane-2-lane) connection will not wait
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
return self._pass
|
|
||||||
|
|
||||||
@pass_not_wait.setter
|
|
||||||
def pass_not_wait(self, value):
|
|
||||||
"""
|
|
||||||
pass_not_wait setter
|
|
||||||
:param value: bool
|
|
||||||
"""
|
|
||||||
self._pass = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def keep_clear(self):
|
|
||||||
"""
|
|
||||||
if set to false, vehicles which pass this (lane-2-lane) connection will not worry about blocking the intersection
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
return self._keep_clear
|
|
||||||
|
|
||||||
@keep_clear.setter
|
|
||||||
def keep_clear(self, value):
|
|
||||||
"""
|
|
||||||
keep_clear setter
|
|
||||||
:param value: bool
|
|
||||||
"""
|
|
||||||
self._keep_clear = value
|
|
|
@ -1,70 +0,0 @@
|
||||||
"""
|
|
||||||
Crossing module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
Contributor Guille guille.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
from city_model_structure.transport.traffic_node import TrafficNode
|
|
||||||
|
|
||||||
|
|
||||||
class Crossing(TrafficNode):
|
|
||||||
"""
|
|
||||||
Crossing class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, coordinates, priority, width, shape=None, edges=None):
|
|
||||||
super().__init__(name, coordinates, edges=edges, node_type='Crossing')
|
|
||||||
self._priority = priority
|
|
||||||
self._width = width
|
|
||||||
self._shape = shape
|
|
||||||
|
|
||||||
@property
|
|
||||||
def priority(self):
|
|
||||||
"""
|
|
||||||
Whether the pedestrians have priority over the vehicles (automatically set to true at tls-controlled intersections).
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
return self._priority
|
|
||||||
|
|
||||||
@priority.setter
|
|
||||||
def priority(self, value):
|
|
||||||
"""
|
|
||||||
Priority setter
|
|
||||||
:param value: bool
|
|
||||||
"""
|
|
||||||
self._priority = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def width(self):
|
|
||||||
"""
|
|
||||||
Width in m
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._width
|
|
||||||
|
|
||||||
@width.setter
|
|
||||||
def width(self, value):
|
|
||||||
"""
|
|
||||||
Width in m setter
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._width = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def shape(self) -> List[List[float]]:
|
|
||||||
"""
|
|
||||||
List of positions (positions in m)
|
|
||||||
:return: [[x, y, (z)]]
|
|
||||||
"""
|
|
||||||
return self._shape
|
|
||||||
|
|
||||||
@shape.setter
|
|
||||||
def shape(self, value):
|
|
||||||
"""
|
|
||||||
List of positions setter
|
|
||||||
:param value: [[x, y, (z)]]
|
|
||||||
"""
|
|
||||||
self._shape = value
|
|
|
@ -1,27 +0,0 @@
|
||||||
"""
|
|
||||||
Join module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
Contributor Guille guille.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from city_model_structure.transport.traffic_node import TrafficNode
|
|
||||||
|
|
||||||
|
|
||||||
class Join(TrafficNode):
|
|
||||||
"""
|
|
||||||
Join class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, coordinates, nodes):
|
|
||||||
self._nodes = nodes
|
|
||||||
edges = []
|
|
||||||
prohibitions = []
|
|
||||||
connections = []
|
|
||||||
for node in self._nodes:
|
|
||||||
edges = edges + node.edges
|
|
||||||
prohibitions = prohibitions + node.prohibitions
|
|
||||||
connections = connections + node.connections
|
|
||||||
super().__init__(name, coordinates, edges=edges, prohibitions=prohibitions, connections=connections,
|
|
||||||
node_type='Join')
|
|
|
@ -1,136 +0,0 @@
|
||||||
"""
|
|
||||||
Lane module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
class Lane:
|
|
||||||
"""
|
|
||||||
Lane class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._index = None
|
|
||||||
self._allow = None
|
|
||||||
self._disallow = None
|
|
||||||
self._change_left = None
|
|
||||||
self._change_right = None
|
|
||||||
self._speed = None
|
|
||||||
self._width = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def index(self):
|
|
||||||
"""
|
|
||||||
Lane index
|
|
||||||
The enumeration index of the lane (0 is the rightmost lane, <NUMBER_LANES>-1 is the leftmost one)
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._index
|
|
||||||
|
|
||||||
@index.setter
|
|
||||||
def index(self, value):
|
|
||||||
"""
|
|
||||||
Index setter
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._index = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def allow(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
List of allowed vehicle classes
|
|
||||||
:return: [str]
|
|
||||||
"""
|
|
||||||
return self._allow
|
|
||||||
|
|
||||||
@allow.setter
|
|
||||||
def allow(self, value):
|
|
||||||
"""
|
|
||||||
List of allowed vehicle classes setter
|
|
||||||
:param value: [str]
|
|
||||||
"""
|
|
||||||
self._allow = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def disallow(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
List of not allowed vehicle classes
|
|
||||||
:return: [str]
|
|
||||||
"""
|
|
||||||
return self._disallow
|
|
||||||
|
|
||||||
@disallow.setter
|
|
||||||
def disallow(self, value):
|
|
||||||
"""
|
|
||||||
List of not allowed vehicle classes setter
|
|
||||||
:param value: [str]
|
|
||||||
"""
|
|
||||||
self._disallow = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def change_left(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
List of vehicle classes that may change left from this lane
|
|
||||||
:return: [str]
|
|
||||||
"""
|
|
||||||
return self._change_left
|
|
||||||
|
|
||||||
@change_left.setter
|
|
||||||
def change_left(self, value):
|
|
||||||
"""
|
|
||||||
change_left setter
|
|
||||||
:param value: [str]
|
|
||||||
"""
|
|
||||||
self._change_left = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def change_right(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
List of vehicle classes that may change right from this lane
|
|
||||||
:return: [str]
|
|
||||||
"""
|
|
||||||
return self._change_right
|
|
||||||
|
|
||||||
@change_right.setter
|
|
||||||
def change_right(self, value):
|
|
||||||
"""
|
|
||||||
change_right setter
|
|
||||||
:param value: [str]
|
|
||||||
"""
|
|
||||||
self._change_right = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def speed(self):
|
|
||||||
"""
|
|
||||||
Speed in m/s
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._speed
|
|
||||||
|
|
||||||
@speed.setter
|
|
||||||
def speed(self, value):
|
|
||||||
"""
|
|
||||||
Speed in m/s setter
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._speed = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def width(self):
|
|
||||||
"""
|
|
||||||
Width in m
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._width
|
|
||||||
|
|
||||||
@width.setter
|
|
||||||
def width(self, value):
|
|
||||||
"""
|
|
||||||
Width in m setter
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._width = value
|
|
|
@ -1,125 +0,0 @@
|
||||||
"""
|
|
||||||
Phase module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
class Phase:
|
|
||||||
"""
|
|
||||||
Phase class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._duration = None
|
|
||||||
self._state = None
|
|
||||||
self._min_duration = None
|
|
||||||
self._max_duration = None
|
|
||||||
self._name = None
|
|
||||||
self._next = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def duration(self):
|
|
||||||
"""
|
|
||||||
Duration in seconds
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._duration
|
|
||||||
|
|
||||||
@duration.setter
|
|
||||||
def duration(self, value):
|
|
||||||
"""
|
|
||||||
Duration setter
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._duration = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self):
|
|
||||||
"""
|
|
||||||
List of signal states
|
|
||||||
:return: []
|
|
||||||
"""
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
@state.setter
|
|
||||||
def state(self, value):
|
|
||||||
"""
|
|
||||||
List of signal states setter
|
|
||||||
:param value: []
|
|
||||||
"""
|
|
||||||
self._state = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_duration(self):
|
|
||||||
"""
|
|
||||||
Minimum duration in seconds
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
if self._min_duration is None:
|
|
||||||
self._min_duration = self._duration
|
|
||||||
return self._min_duration
|
|
||||||
|
|
||||||
@min_duration.setter
|
|
||||||
def min_duration(self, value):
|
|
||||||
"""
|
|
||||||
Minimum duration setter
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._min_duration = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_duration(self):
|
|
||||||
"""
|
|
||||||
Maximum duration in seconds
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
if self._max_duration is None:
|
|
||||||
self._max_duration = self._duration
|
|
||||||
return self._max_duration
|
|
||||||
|
|
||||||
@max_duration.setter
|
|
||||||
def max_duration(self, value):
|
|
||||||
"""
|
|
||||||
Maximum duration setter
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._max_duration = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""
|
|
||||||
Phase name
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, value):
|
|
||||||
"""
|
|
||||||
Phase name setter
|
|
||||||
:param value: str
|
|
||||||
"""
|
|
||||||
self._name = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def next(self) -> List[int]:
|
|
||||||
"""
|
|
||||||
The next phase in the cycle after the current.
|
|
||||||
This is useful when adding extra transition phases to a traffic light plan which are not part of every cycle.
|
|
||||||
Traffic lights of type 'actuated' can make use of a list of indices for selecting among alternative
|
|
||||||
successor phases.
|
|
||||||
:return: [int]
|
|
||||||
"""
|
|
||||||
return self._next
|
|
||||||
|
|
||||||
@next.setter
|
|
||||||
def next(self, value):
|
|
||||||
"""
|
|
||||||
Next setter
|
|
||||||
:param value: [int]
|
|
||||||
"""
|
|
||||||
self._next = value
|
|
|
@ -1,135 +0,0 @@
|
||||||
"""
|
|
||||||
Edge module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
Contributor Guille guille.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
from city_model_structure.attributes.edge import Edge
|
|
||||||
from city_model_structure.transport.lane import Lane
|
|
||||||
|
|
||||||
|
|
||||||
class TrafficEdge(Edge):
|
|
||||||
"""
|
|
||||||
Edge class
|
|
||||||
Each edge is unidirectional and starts at the "from" node and ends at the "to" node
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, nodes, priority, speed, lanes, length, allows=None, disallows=None, sidewalk_width=None,
|
|
||||||
edge_type='TrafficEdge'):
|
|
||||||
super().__init__(name, nodes)
|
|
||||||
self._edge_type = edge_type
|
|
||||||
self._lanes = lanes
|
|
||||||
self._priority = priority
|
|
||||||
self._speed = speed
|
|
||||||
self._length = length
|
|
||||||
self._allows = allows
|
|
||||||
self._disallows = disallows
|
|
||||||
self._sidewalk_width = sidewalk_width
|
|
||||||
|
|
||||||
@property
|
|
||||||
def edge_type(self):
|
|
||||||
"""
|
|
||||||
The name of a edge type
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._edge_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def lanes(self) -> List[Lane]:
|
|
||||||
"""
|
|
||||||
List of lanes on an edge
|
|
||||||
:return: List[Lane]
|
|
||||||
"""
|
|
||||||
return self._lanes
|
|
||||||
|
|
||||||
@lanes.setter
|
|
||||||
def lanes(self, value):
|
|
||||||
"""
|
|
||||||
List of lanes on an edge setter
|
|
||||||
:param value: List[Lane]
|
|
||||||
"""
|
|
||||||
self._lanes = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def priority(self):
|
|
||||||
"""
|
|
||||||
A number, which determines the priority between different road types.
|
|
||||||
It starts with one; higher numbers represent more important roads.
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._priority
|
|
||||||
|
|
||||||
@priority.setter
|
|
||||||
def priority(self, value):
|
|
||||||
"""
|
|
||||||
Priority setter
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._priority = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def speed(self):
|
|
||||||
"""
|
|
||||||
The speed limit in m/s
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._speed
|
|
||||||
|
|
||||||
@speed.setter
|
|
||||||
def speed(self, value):
|
|
||||||
"""
|
|
||||||
The speed limit in m/s setter
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._speed = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def length(self):
|
|
||||||
"""
|
|
||||||
Length in m
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._length
|
|
||||||
|
|
||||||
@length.setter
|
|
||||||
def length(self, value):
|
|
||||||
"""
|
|
||||||
Length in m setter
|
|
||||||
:param value: float
|
|
||||||
"""
|
|
||||||
self._length = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def allows(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
List of allowed vehicle classes
|
|
||||||
:return: [str]
|
|
||||||
"""
|
|
||||||
return self._allows
|
|
||||||
|
|
||||||
@allows.setter
|
|
||||||
def allows(self, value):
|
|
||||||
"""
|
|
||||||
List of allowed vehicle classes setter
|
|
||||||
:param value: [str]
|
|
||||||
"""
|
|
||||||
self._allows = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def disallows(self) -> List[str]:
|
|
||||||
"""
|
|
||||||
List of not allowed vehicle classes
|
|
||||||
:return: [str]
|
|
||||||
"""
|
|
||||||
return self._disallows
|
|
||||||
|
|
||||||
@disallows.setter
|
|
||||||
def disallows(self, value):
|
|
||||||
"""
|
|
||||||
List of not allowed vehicle classes setter
|
|
||||||
:param value: [str]
|
|
||||||
"""
|
|
||||||
self._disallows = value
|
|
|
@ -1,70 +0,0 @@
|
||||||
"""
|
|
||||||
Traffic light module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
Contributor Guille guille.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
from city_model_structure.transport.phase import Phase
|
|
||||||
from city_model_structure.transport.traffic_node import TrafficNode
|
|
||||||
|
|
||||||
|
|
||||||
class TrafficLight(TrafficNode):
|
|
||||||
"""
|
|
||||||
Traffic light class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, coordinates, offset, phases=None, edges=None, right_on_red=False):
|
|
||||||
super().__init__(name, coordinates, edges=edges, node_type='TrafficLight')
|
|
||||||
if phases is None:
|
|
||||||
phases = []
|
|
||||||
self._right_on_red = right_on_red
|
|
||||||
self._offset = offset
|
|
||||||
self._phases = phases
|
|
||||||
|
|
||||||
@property
|
|
||||||
def right_on_red(self):
|
|
||||||
"""
|
|
||||||
Return if is possible to turn right if the traffic light is red
|
|
||||||
"""
|
|
||||||
return self._right_on_red
|
|
||||||
|
|
||||||
@right_on_red.setter
|
|
||||||
def right_on_red(self, value):
|
|
||||||
"""
|
|
||||||
Set if is possible to turn right if the traffic light is red
|
|
||||||
"""
|
|
||||||
self._right_on_red = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def offset(self):
|
|
||||||
"""
|
|
||||||
The initial time offset of the program
|
|
||||||
:return: int
|
|
||||||
"""
|
|
||||||
return self._offset
|
|
||||||
|
|
||||||
@offset.setter
|
|
||||||
def offset(self, value):
|
|
||||||
"""
|
|
||||||
The initial time offset of the program setter
|
|
||||||
:param value: int
|
|
||||||
"""
|
|
||||||
self._offset = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def phases(self) -> List[Phase]:
|
|
||||||
"""
|
|
||||||
Phases of the traffic light logic
|
|
||||||
:return: [Phase]
|
|
||||||
"""
|
|
||||||
return self._phases
|
|
||||||
|
|
||||||
@phases.setter
|
|
||||||
def phases(self, value):
|
|
||||||
"""
|
|
||||||
Phases setter
|
|
||||||
:param value: [Phase]
|
|
||||||
"""
|
|
||||||
self._phases = value
|
|
|
@ -1,25 +0,0 @@
|
||||||
"""
|
|
||||||
Traffic network module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
Contributor Guille guille.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from city_model_structure.network import Network
|
|
||||||
|
|
||||||
|
|
||||||
class TrafficNetwork(Network):
|
|
||||||
"""
|
|
||||||
TrafficNetwork(CityObject) class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, edges=None, nodes=None):
|
|
||||||
super().__init__(name, edges, nodes)
|
|
||||||
self._type = "TrafficNetwork"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
"""
|
|
||||||
Network type
|
|
||||||
"""
|
|
||||||
return self._type
|
|
|
@ -1,84 +0,0 @@
|
||||||
"""
|
|
||||||
Node module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
Contributor Guille guille.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from city_model_structure.attributes.edge import Edge
|
|
||||||
from city_model_structure.attributes.node import Node
|
|
||||||
from city_model_structure.attributes.point import Point
|
|
||||||
|
|
||||||
|
|
||||||
from city_model_structure.transport.connection import Connection
|
|
||||||
|
|
||||||
|
|
||||||
class TrafficNode(Node):
|
|
||||||
"""
|
|
||||||
Node class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, coordinates, node_type='TrafficNode', edges=None, prohibitions=None, connections=None):
|
|
||||||
super().__init__(name, edges)
|
|
||||||
if connections is None:
|
|
||||||
connections = []
|
|
||||||
if prohibitions is None:
|
|
||||||
prohibitions = []
|
|
||||||
self._coordinates = coordinates
|
|
||||||
self._prohibitions = prohibitions
|
|
||||||
self._connections = connections
|
|
||||||
self._node_type = node_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def node_type(self):
|
|
||||||
"""
|
|
||||||
The name of a node type
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._node_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def coordinates(self) -> Point:
|
|
||||||
"""
|
|
||||||
The x,y,z - Node coordinates
|
|
||||||
:return: Point
|
|
||||||
"""
|
|
||||||
return self._coordinates
|
|
||||||
|
|
||||||
@coordinates.setter
|
|
||||||
def coordinates(self, value):
|
|
||||||
"""
|
|
||||||
The x,y,z - Node coordinates setter
|
|
||||||
:param value: Point
|
|
||||||
"""
|
|
||||||
self._coordinates = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def prohibitions(self) -> List[(Edge, Edge)]:
|
|
||||||
"""
|
|
||||||
return a list of forbidden edges tuples meaning you can not move from the first edge to the second
|
|
||||||
"""
|
|
||||||
return self._prohibitions
|
|
||||||
|
|
||||||
@prohibitions.setter
|
|
||||||
def prohibitions(self, value):
|
|
||||||
"""
|
|
||||||
Set the prohibitions tuples for this node
|
|
||||||
"""
|
|
||||||
self._prohibitions = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def connections(self) -> List[Connection]:
|
|
||||||
"""
|
|
||||||
Return a list of connections for the node
|
|
||||||
"""
|
|
||||||
return self._connections
|
|
||||||
|
|
||||||
@connections.setter
|
|
||||||
def connections(self, value):
|
|
||||||
"""
|
|
||||||
Set the connections for this node
|
|
||||||
"""
|
|
||||||
self._connections = value
|
|
|
@ -1,36 +0,0 @@
|
||||||
"""
|
|
||||||
Walkway node module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
Contributor Milad milad.aghamohamadnia@concordia.ca
|
|
||||||
Contributor Guille guille.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
from city_model_structure.transport.traffic_node import TrafficNode
|
|
||||||
|
|
||||||
|
|
||||||
class WalkwayNode(TrafficNode):
|
|
||||||
"""
|
|
||||||
WalkwayNode class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, coordinates, edges=None, shape=None):
|
|
||||||
super().__init__(name, coordinates, edges=edges, node_type='WalkwayNode')
|
|
||||||
self._shape = shape
|
|
||||||
|
|
||||||
@property
|
|
||||||
def shape(self) -> List[List[float]]:
|
|
||||||
"""
|
|
||||||
List of positions (positions in m)
|
|
||||||
:return: [[x, y, (z)]]
|
|
||||||
"""
|
|
||||||
return self._shape
|
|
||||||
|
|
||||||
@shape.setter
|
|
||||||
def shape(self, value):
|
|
||||||
"""
|
|
||||||
List of positions setter
|
|
||||||
:param value: [[x, y, (z)]]
|
|
||||||
"""
|
|
||||||
self._shape = value
|
|
|
@ -1,33 +0,0 @@
|
||||||
"""
|
|
||||||
Tree module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from city_model_structure.city_object import CityObject
|
|
||||||
|
|
||||||
|
|
||||||
class Tree(CityObject):
|
|
||||||
"""
|
|
||||||
Tree(CityObject) class
|
|
||||||
"""
|
|
||||||
def __init__(self, lod, surfaces, name, height, canopy):
|
|
||||||
super().__init__(lod, surfaces, name, [])
|
|
||||||
self._height = height
|
|
||||||
self._canopy = canopy
|
|
||||||
self._type = 'tree'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def height(self):
|
|
||||||
"""
|
|
||||||
Get height of tree in meters
|
|
||||||
:return: height
|
|
||||||
"""
|
|
||||||
return self._height
|
|
||||||
|
|
||||||
@property
|
|
||||||
def canopy(self):
|
|
||||||
"""
|
|
||||||
Get canopy of tree
|
|
||||||
:return: canopy
|
|
||||||
"""
|
|
||||||
return self._canopy
|
|
|
@ -1,84 +0,0 @@
|
||||||
"""
|
|
||||||
ExportsFactory export a city into several formats
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from exports.formats.energy_ade import EnergyAde
|
|
||||||
from exports.formats.idf import Idf
|
|
||||||
from exports.formats.obj import Obj
|
|
||||||
from exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
|
|
||||||
from exports.formats.stl import Stl
|
|
||||||
|
|
||||||
|
|
||||||
class ExportsFactory:
|
|
||||||
"""
|
|
||||||
Exports factory class
|
|
||||||
"""
|
|
||||||
def __init__(self, export_type, city, path):
|
|
||||||
self._city = city
|
|
||||||
self._export_type = '_' + export_type.lower()
|
|
||||||
self._path = path
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _citygml(self):
|
|
||||||
"""
|
|
||||||
Export to citygml_classes with application domain extensions
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _energy_ade(self):
|
|
||||||
"""
|
|
||||||
Export to citygml_classes with application domain extensions
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
return EnergyAde(self._city, self._path)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _stl(self):
|
|
||||||
"""
|
|
||||||
Export the city geometry to stl
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
return Stl(self._city, self._path)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _obj(self):
|
|
||||||
"""
|
|
||||||
Export the city geometry to obj
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
return Obj(self._city, self._path)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _grounded_obj(self):
|
|
||||||
"""
|
|
||||||
Export the city geometry to obj
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
return Obj(self._city, self._path).to_ground_points()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _idf(self):
|
|
||||||
"""
|
|
||||||
Export the city to Energy+ idf format
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
idf_data_path = (Path(__file__).parent / './formats/idf_files/').resolve()
|
|
||||||
# todo: create a get epw file function based on the city
|
|
||||||
weather_path = (Path(__file__).parent / '../data/weather/epw/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve()
|
|
||||||
return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _sra(self):
|
|
||||||
return SimplifiedRadiosityAlgorithm(self._city, (self._path / f'{self._city.name}_sra.xml'))
|
|
||||||
|
|
||||||
def export(self):
|
|
||||||
"""
|
|
||||||
Export the city model structure to the given export type
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
return getattr(self, self._export_type, lambda: None)
|
|
|
@ -1,378 +0,0 @@
|
||||||
"""
|
|
||||||
ExportsFactory export a city into several formats
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
import xmltodict
|
|
||||||
import helpers.constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class EnergyAde:
|
|
||||||
"""
|
|
||||||
Export the city to citygml + energy ade
|
|
||||||
"""
|
|
||||||
def __init__(self, city, path):
|
|
||||||
self._city = city
|
|
||||||
self._path = path
|
|
||||||
self._surface_members = None
|
|
||||||
self._export()
|
|
||||||
|
|
||||||
def _export(self):
|
|
||||||
energy_ade = {
|
|
||||||
'core:CityModel': {
|
|
||||||
'@xmlns:brid': 'http://www.opengis.net/citygml/bridge/2.0',
|
|
||||||
'@xmlns:tran': 'http://www.opengis.net/citygml/transportation/2.0',
|
|
||||||
'@xmlns:frn': 'http://www.opengis.net/citygml/cityfurniture/2.0',
|
|
||||||
'@xmlns:wtr': 'http://www.opengis.net/citygml/waterbody/2.0',
|
|
||||||
'@xmlns:sch': 'http://www.ascc.net/xml/schematron',
|
|
||||||
'@xmlns:veg': 'http://www.opengis.net/citygml/vegetation/2.0',
|
|
||||||
'@xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
|
||||||
'@xmlns:tun': 'http://www.opengis.net/citygml/tunnel/2.0',
|
|
||||||
'@xmlns:tex': 'http://www.opengis.net/citygml/texturedsurface/2.0',
|
|
||||||
'@xmlns:gml': 'http://www.opengis.net/gml',
|
|
||||||
'@xmlns:genobj': 'http://www.opengis.net/citygml/generics/2.0',
|
|
||||||
'@xmlns:dem': 'http://www.opengis.net/citygml/relief/2.0',
|
|
||||||
'@xmlns:app': 'http://www.opengis.net/citygml/appearance/2.0',
|
|
||||||
'@xmlns:luse': 'http://www.opengis.net/citygml/landuse/2.0',
|
|
||||||
'@xmlns:xAL': 'urn:oasis:names:tc:ciq:xsdschema:xAL:2.0',
|
|
||||||
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
||||||
'@xmlns:smil20lang': 'http://www.w3.org/2001/SMIL20/Language',
|
|
||||||
'@xmlns:pbase': 'http://www.opengis.net/citygml/profiles/base/2.0',
|
|
||||||
'@xmlns:smil20': 'http://www.w3.org/2001/SMIL20/',
|
|
||||||
'@xmlns:bldg': 'http://www.opengis.net/citygml/building/2.0',
|
|
||||||
'@xmlns:energy': "http://www.sig3d.org/citygml/2.0/energy/1.0",
|
|
||||||
'@xmlns:core': 'http://www.opengis.net/citygml/2.0',
|
|
||||||
'@xmlns:grp': 'http://www.opengis.net/citygml/cityobjectgroup/2.0',
|
|
||||||
'gml:boundedBy': {
|
|
||||||
'gml:Envelope': {
|
|
||||||
'@srsName': self._city.srs_name,
|
|
||||||
'@srsDimension': 3,
|
|
||||||
'gml:lowerCorner': ' '.join([str(e) for e in self._city.lower_corner]),
|
|
||||||
'gml:upperCorner': ' '.join([str(e) for e in self._city.upper_corner])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildings = []
|
|
||||||
for building in self._city.buildings:
|
|
||||||
building_dic = {
|
|
||||||
'bldg:Building': {
|
|
||||||
'@gml:id': building.name,
|
|
||||||
'gml:description': f'Building {building.name} at {self._city.name}',
|
|
||||||
'gml:name': f'{building.name}',
|
|
||||||
'core:creationDate': datetime.datetime.now().strftime('%Y-%m-%d')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
building_dic = EnergyAde._measures(building, building_dic)
|
|
||||||
building_dic = self._building_geometry(building, building_dic, self._city)
|
|
||||||
building_dic['bldg:Building']['energy:volume'] = {
|
|
||||||
'energy:VolumeType': {
|
|
||||||
'energy:type': 'grossVolume',
|
|
||||||
'energy:value': {
|
|
||||||
'@uom': 'm3',
|
|
||||||
'energy:value': building.volume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
building_dic['bldg:Building']['energy:referencePoint'] = {
|
|
||||||
'gml:Point': {
|
|
||||||
'@srsName': self._city.srs_name,
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:Pos': f'{" ".join(map(str, building.centroid))}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
building_dic['bldg:Building']['energy:thermalZone'] = self._thermal_zones(building, self._city)
|
|
||||||
buildings.append(building_dic)
|
|
||||||
|
|
||||||
energy_ade['core:CityModel']['core:cityObjectMember'] = buildings
|
|
||||||
|
|
||||||
file_name = self._city.name + '_ade.gml'
|
|
||||||
file_path = Path(self._path / file_name).resolve()
|
|
||||||
with open(file_path, 'w') as file:
|
|
||||||
file.write(xmltodict.unparse(energy_ade, pretty=True, short_empty_elements=True))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _measures(building, building_dic):
|
|
||||||
# todo: this method is only for year and insel need to be generalized
|
|
||||||
measures = []
|
|
||||||
measure = EnergyAde._measure(building.heating, cte.YEAR, 'Energy demand heating', 'INSEL')
|
|
||||||
if measure is not None:
|
|
||||||
measures.append(measure)
|
|
||||||
measure = EnergyAde._measure(building.cooling, cte.YEAR, 'Energy demand cooling', 'INSEL')
|
|
||||||
if measure is not None:
|
|
||||||
measures.append(measure)
|
|
||||||
if len(measures) != 0:
|
|
||||||
building_dic['bldg:Building']['genobj:measureAttribute'] = measures
|
|
||||||
|
|
||||||
demands = []
|
|
||||||
for key in building.heating:
|
|
||||||
if key != cte.YEAR:
|
|
||||||
demand = EnergyAde._demand(building.heating, key, 'Heating energy', 'INSEL')
|
|
||||||
demands.append(demand)
|
|
||||||
|
|
||||||
for key in building.cooling:
|
|
||||||
if key != cte.YEAR:
|
|
||||||
demand = EnergyAde._demand(building.cooling, key, 'Cooling energy', 'INSEL')
|
|
||||||
demands.append(demand)
|
|
||||||
if len(demands) != 0:
|
|
||||||
building_dic['bldg:Building']['energy:demands'] = demands
|
|
||||||
|
|
||||||
return building_dic
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _measure(measure_dict, key_value, name, source):
|
|
||||||
measure = None
|
|
||||||
if key_value in measure_dict:
|
|
||||||
measure = {
|
|
||||||
'@name': name,
|
|
||||||
'genobj:value': {
|
|
||||||
'@uom': 'kWh',
|
|
||||||
'#text': ' '.join([str(e / 1000) for e in measure_dict[key_value][source]])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return measure
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _demand(measure_dict, key_value, description, source):
|
|
||||||
demand = {
|
|
||||||
'energy:EnergyDemand': {
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'energy:energyAmount': {
|
|
||||||
'energy:RegularTimeSeries': {
|
|
||||||
'energy:variableProperties': {
|
|
||||||
'energy:TimeValuesProperties': {
|
|
||||||
'energy:acquisitionMethod': 'simulation',
|
|
||||||
'energy:source': source,
|
|
||||||
'energy:thematicDescription': description,
|
|
||||||
},
|
|
||||||
'energy:timeInterval': {
|
|
||||||
'@unit': key_value,
|
|
||||||
'#text': '1',
|
|
||||||
},
|
|
||||||
'energy:values': {
|
|
||||||
'@uom': 'kWh',
|
|
||||||
'#text': ' '.join([str(float(e) / 1000) for e in measure_dict[key_value][source]])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'energy:endUse': 'spaceHeating'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return demand
|
|
||||||
|
|
||||||
def _building_geometry(self, building, building_dic, city):
|
|
||||||
|
|
||||||
building_dic['bldg:Building']['bldg:function'] = building.function
|
|
||||||
building_dic['bldg:Building']['bldg:usage'] = ', '.join([u.usage for u in building.usage_zones])
|
|
||||||
building_dic['bldg:Building']['bldg:yearOfConstruction'] = building.year_of_construction
|
|
||||||
building_dic['bldg:Building']['bldg:roofType'] = building.roof_type
|
|
||||||
building_dic['bldg:Building']['bldg:measuredHeight'] = {
|
|
||||||
'@uom': 'm',
|
|
||||||
'#text': f'{building.max_height}'
|
|
||||||
}
|
|
||||||
building_dic['bldg:Building']['bldg:storeysAboveGround'] = building.storeys_above_ground
|
|
||||||
|
|
||||||
if building.lod == 1:
|
|
||||||
building_dic = self._lod1(building, building_dic, city)
|
|
||||||
elif building.lod == 2:
|
|
||||||
building_dic = self._lod2(building, building_dic, city)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError('Only lod 1 and 2 can be exported')
|
|
||||||
return building_dic
|
|
||||||
|
|
||||||
def _lod1(self, building, building_dic, city):
|
|
||||||
raise NotImplementedError('Only lod 1 and 2 can be exported')
|
|
||||||
|
|
||||||
def _lod2(self, building, building_dic, city):
|
|
||||||
self._surface_members = []
|
|
||||||
boundaries = [{
|
|
||||||
'gml:Envelope': {
|
|
||||||
'@srsName': city.srs_name,
|
|
||||||
'@srsDimension': 3,
|
|
||||||
'gml:lowerCorner': ' '.join([str(e) for e in city.lower_corner]),
|
|
||||||
'gml:upperCorner': ' '.join([str(e) for e in city.upper_corner])
|
|
||||||
}}]
|
|
||||||
for surface in building.surfaces:
|
|
||||||
surface_member = {'@xlink:href': f'#PolyId{surface.name}'}
|
|
||||||
self._surface_members.append(surface_member)
|
|
||||||
if surface.type == 'Wall':
|
|
||||||
surface_type = 'bldg:WallSurface'
|
|
||||||
elif surface.type == 'Ground':
|
|
||||||
surface_type = 'bldg:GroundSurface'
|
|
||||||
else:
|
|
||||||
surface_type = 'bldg:RoofSurface'
|
|
||||||
surface_dic = {
|
|
||||||
surface_type: {
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:name': f'{surface.name} ({surface.type})',
|
|
||||||
'gml:boundedBy': {
|
|
||||||
'gml:Envelope': {
|
|
||||||
'@srsName': city.srs_name,
|
|
||||||
'gml:lowerCorner': f'{surface.lower_corner[0]} {surface.lower_corner[1]}'
|
|
||||||
f' {surface.lower_corner[2]}',
|
|
||||||
'gml:upperCorner': f'{surface.upper_corner[0]} {surface.upper_corner[1]}'
|
|
||||||
f' {surface.upper_corner[2]}'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'bldg:lod2MultiSurface': {
|
|
||||||
'gml:MultiSurface': {
|
|
||||||
'@srsName': city.srs_name,
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'surfaceMember': {
|
|
||||||
'gml:Polygon': {
|
|
||||||
'@srsName': city.srs_name,
|
|
||||||
'@gml:id': f'PolyId{surface.name}',
|
|
||||||
'gml:exterior': {
|
|
||||||
'gml:LinearRing': {
|
|
||||||
'@gml:id': f'PolyId{surface.name}_0',
|
|
||||||
'gml:posList': {
|
|
||||||
'@srsDimension': '3',
|
|
||||||
'@count': len(surface.solid_polygon.coordinates) + 1,
|
|
||||||
'#text': f'{" ".join(map(str, surface.solid_polygon.points_list))} '
|
|
||||||
f'{" ".join(map(str, surface.solid_polygon.coordinates[0]))}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
boundaries.append(surface_dic)
|
|
||||||
building_dic['bldg:Building']['bldg:lod2Solid'] = {
|
|
||||||
'gml:Solid': {
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:exterior': {
|
|
||||||
'gml:CompositeSurface': {
|
|
||||||
'@srsName': city.srs_name,
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:surfaceMember': self._surface_members
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
building_dic['bldg:Building']['gml:boundedBy'] = boundaries
|
|
||||||
return building_dic
|
|
||||||
|
|
||||||
def _thermal_zones(self, building, city):
|
|
||||||
thermal_zones = []
|
|
||||||
for index, thermal_zone in enumerate(building.thermal_zones):
|
|
||||||
usage_zones = []
|
|
||||||
for usage_zone in thermal_zone.usage_zones:
|
|
||||||
usage_zones.append({'@xlink:href': f'#GML_{usage_zone.id}'})
|
|
||||||
thermal_zone_dic = {
|
|
||||||
'energy:ThermalZone': {
|
|
||||||
'@gml:id': f'GML_{thermal_zone.id}',
|
|
||||||
'gml:name': f'Thermal zone {index} in {building.name} building',
|
|
||||||
'energy:contains': [],
|
|
||||||
'energy:floorArea': {
|
|
||||||
'energy:FloorArea': {
|
|
||||||
'energy:type': 'grossFloorArea',
|
|
||||||
'energy:value': {
|
|
||||||
'@uom': 'm2',
|
|
||||||
'#text': f'{thermal_zone.floor_area}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'energy:volume': {
|
|
||||||
'energy:VolumeType': {
|
|
||||||
'energy:type': 'grossVolume',
|
|
||||||
'energy:value': {
|
|
||||||
'@uom': 'm3',
|
|
||||||
# todo: for now we have just one thermal zone, therefore is the building volume, this need to be changed
|
|
||||||
'#text': f'{building.volume}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'energy:isCooled': f'{thermal_zone.is_cooled}',
|
|
||||||
'energy:isHeated': f'{thermal_zone.is_heated}',
|
|
||||||
'energy:volumeGeometry': {
|
|
||||||
'gml:Solid': {
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:exterior': {
|
|
||||||
'gml:CompositeSurface': {
|
|
||||||
'@srsName': f'{city.srs_name}',
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:surfaceMember': self._surface_members
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'energy:boundedBy': self._thermal_boundaries(city, thermal_zone)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thermal_zone_dic['energy:ThermalZone']['energy:contains'] = usage_zones
|
|
||||||
thermal_zones.append(thermal_zone_dic)
|
|
||||||
return thermal_zones
|
|
||||||
|
|
||||||
def _thermal_boundaries(self, city, thermal_zone):
|
|
||||||
thermal_boundaries = []
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
thermal_boundary_dic = {
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:name': f'{thermal_boundary.construction_name}',
|
|
||||||
'energy:thermalBoundaryType': thermal_boundary.type,
|
|
||||||
'energy:azumuth': {
|
|
||||||
'@uom': 'rad',
|
|
||||||
'#text': f'{thermal_boundary.azimuth}'
|
|
||||||
},
|
|
||||||
'energy:inclination': {
|
|
||||||
'@uom': 'rad',
|
|
||||||
'#text': f'{thermal_boundary.inclination}'
|
|
||||||
},
|
|
||||||
'energy:area': {
|
|
||||||
'@uom': 'm2',
|
|
||||||
'#text': f'{thermal_boundary.area}'
|
|
||||||
},
|
|
||||||
'energy:surfaceGeometry': {
|
|
||||||
'gml:MultiSurface': {
|
|
||||||
'@srsName': city.srs_name,
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:surfaceMember': {
|
|
||||||
'gml:Polygon': {
|
|
||||||
'@srsName': city.srs_name,
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:exterior': {
|
|
||||||
'gml:LinearRing': {
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'gml:posList': {
|
|
||||||
'@srsDimension': '3',
|
|
||||||
'@count': len(thermal_boundary.surface.solid_polygon.coordinates) + 1,
|
|
||||||
'#text': f'{" ".join(map(str, thermal_boundary.surface.solid_polygon.points_list))} '
|
|
||||||
f'{" ".join(map(str, thermal_boundary.surface.solid_polygon.coordinates[0]))}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
construction = []
|
|
||||||
opening_construction = []
|
|
||||||
if thermal_boundary.layers is not None:
|
|
||||||
construction.append(uuid.uuid4())
|
|
||||||
thermal_boundary_dic['energy:construction'] = {
|
|
||||||
'@xlink:href': f'#GML_{construction[0]}'
|
|
||||||
}
|
|
||||||
if thermal_boundary.thermal_openings is not None:
|
|
||||||
for opening in thermal_boundary.thermal_openings:
|
|
||||||
opening_construction.append(uuid.uuid4())
|
|
||||||
thermal_boundary_dic['energy:contains'] = {
|
|
||||||
'energy:ThermalOpening': {
|
|
||||||
'@gml:id': f'GML_{uuid.uuid4()}',
|
|
||||||
'energy:construction': f'#GML_{opening_construction}',
|
|
||||||
'energy:surfaceGeometry': {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thermal_boundaries.append(thermal_boundary_dic)
|
|
||||||
return thermal_boundaries
|
|
|
@ -1,278 +0,0 @@
|
||||||
"""
|
|
||||||
TestOccupancyFactory test and validate the city model structure schedules parameters
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Soroush Samareh Abolhassani - soroush.samarehabolhassani@mail.concordia.ca
|
|
||||||
"""
|
|
||||||
from geomeppy import IDF
|
|
||||||
|
|
||||||
|
|
||||||
class Idf:
|
|
||||||
"""
|
|
||||||
Export city to IDF
|
|
||||||
"""
|
|
||||||
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
|
|
||||||
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
|
|
||||||
_SURFACE = 'BUILDINGSURFACE:DETAILED'
|
|
||||||
_CONSTRUCTION = 'CONSTRUCTION'
|
|
||||||
_MATERIAL = 'MATERIAL'
|
|
||||||
_MATERIAL_NOMASS = 'MATERIAL:NOMASS'
|
|
||||||
_ROUGHNESS = 'MediumRough'
|
|
||||||
_HOURLY_SCHEDULE = 'SCHEDULE:DAY:HOURLY'
|
|
||||||
_ZONE = 'ZONE'
|
|
||||||
_LIGHTS = 'LIGHTS'
|
|
||||||
_PEOPLE = 'PEOPLE'
|
|
||||||
_ELECTRIC_EQUIPMENT = 'ELECTRICEQUIPMENT'
|
|
||||||
_INFILTRATION = 'ZONEINFILTRATION:DESIGNFLOWRATE'
|
|
||||||
_BUILDING_SURFACE = 'BuildingSurfaceDetailed'
|
|
||||||
_SCHEDULE_LIMIT = 'SCHEDULETYPELIMITS'
|
|
||||||
_ON_OFF = 'On/Off'
|
|
||||||
_FRACTION = 'Fraction'
|
|
||||||
_ANY_NUMBER = 'Any Number'
|
|
||||||
_CONTINUOUS = 'CONTINUOUS'
|
|
||||||
_DISCRETE = 'DISCRETE'
|
|
||||||
|
|
||||||
idf_surfaces = {
|
|
||||||
# todo: make an enum for all the surface types
|
|
||||||
'Wall': 'wall',
|
|
||||||
'Ground': 'floor',
|
|
||||||
'Roof': 'roof'
|
|
||||||
}
|
|
||||||
idf_usage = {
|
|
||||||
# todo: make an enum for all the usage types
|
|
||||||
'residential': 'residential_building'
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, export_type="Surfaces"):
|
|
||||||
self._city = city
|
|
||||||
self._output_path = str((output_path / f'{city.name}.idf').resolve())
|
|
||||||
self._export_type = export_type
|
|
||||||
self._idd_file_path = str(idd_file_path)
|
|
||||||
self._idf_file_path = str(idf_file_path)
|
|
||||||
self._epw_file_path = str(epw_file_path)
|
|
||||||
IDF.setiddname(self._idd_file_path)
|
|
||||||
self._idf = IDF(self._idf_file_path, self._epw_file_path)
|
|
||||||
self._idf.newidfobject(self._SCHEDULE_LIMIT, Name=self._ANY_NUMBER)
|
|
||||||
self._idf.newidfobject(self._SCHEDULE_LIMIT, Name=self._FRACTION, Lower_Limit_Value=0.0, Upper_Limit_Value=1.0,
|
|
||||||
Numeric_Type=self._CONTINUOUS)
|
|
||||||
self._idf.newidfobject(self._SCHEDULE_LIMIT, Name=self._ON_OFF, Lower_Limit_Value=0, Upper_Limit_Value=1,
|
|
||||||
Numeric_Type=self._DISCRETE)
|
|
||||||
self._export()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _matrix_to_list(points):
|
|
||||||
points_list = []
|
|
||||||
for point in points:
|
|
||||||
point_tuple = (point[0], point[1], point[2])
|
|
||||||
points_list.append(point_tuple)
|
|
||||||
return points_list
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _matrix_to_2d_list(points):
|
|
||||||
points_list = []
|
|
||||||
for point in points:
|
|
||||||
point_tuple = (point[0], point[1])
|
|
||||||
points_list.append(point_tuple)
|
|
||||||
return points_list
|
|
||||||
|
|
||||||
def _add_material(self, layer):
|
|
||||||
for material in self._idf.idfobjects[self._MATERIAL]:
|
|
||||||
if material.Name == layer.material.name:
|
|
||||||
return
|
|
||||||
for material in self._idf.idfobjects[self._MATERIAL_NOMASS]:
|
|
||||||
if material.Name == layer.material.name:
|
|
||||||
return
|
|
||||||
if layer.material.no_mass:
|
|
||||||
self._idf.newidfobject(self._MATERIAL_NOMASS,
|
|
||||||
Name=layer.material.name,
|
|
||||||
Roughness=self._ROUGHNESS,
|
|
||||||
Thermal_Resistance=layer.material.thermal_resistance,
|
|
||||||
Thermal_Absorptance=layer.material.thermal_absorptance,
|
|
||||||
Solar_Absorptance=layer.material.solar_absorptance,
|
|
||||||
Visible_Absorptance=layer.material.visible_absorptance
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._idf.newidfobject(self._MATERIAL,
|
|
||||||
Name=layer.material.name,
|
|
||||||
Roughness=self._ROUGHNESS,
|
|
||||||
Thickness=layer.thickness,
|
|
||||||
Conductivity=layer.material.conductivity,
|
|
||||||
Density=layer.material.density,
|
|
||||||
Specific_Heat=layer.material.specific_heat,
|
|
||||||
Thermal_Absorptance=layer.material.thermal_absorptance,
|
|
||||||
Solar_Absorptance=layer.material.solar_absorptance,
|
|
||||||
Visible_Absorptance=layer.material.visible_absorptance
|
|
||||||
)
|
|
||||||
|
|
||||||
def _add_schedule(self, usage_zone, schedule_type, limit_name='Any Number'):
|
|
||||||
for schedule in self._idf.idfobjects[self._HOURLY_SCHEDULE]:
|
|
||||||
if schedule.Name == f'{schedule_type} schedules {usage_zone.usage}':
|
|
||||||
return
|
|
||||||
if usage_zone.schedules is None or schedule_type not in usage_zone.schedules:
|
|
||||||
# there are no schedule for this type
|
|
||||||
return
|
|
||||||
schedule = self._idf.newidfobject(self._HOURLY_SCHEDULE, Name=f'{schedule_type} schedules {usage_zone.usage}')
|
|
||||||
schedule.Schedule_Type_Limits_Name = limit_name
|
|
||||||
|
|
||||||
schedule.Hour_1 = usage_zone.schedules[schedule_type]["WD"][0]
|
|
||||||
schedule.Hour_2 = usage_zone.schedules[schedule_type]["WD"][1]
|
|
||||||
schedule.Hour_3 = usage_zone.schedules[schedule_type]["WD"][2]
|
|
||||||
schedule.Hour_4 = usage_zone.schedules[schedule_type]["WD"][3]
|
|
||||||
schedule.Hour_5 = usage_zone.schedules[schedule_type]["WD"][4]
|
|
||||||
schedule.Hour_6 = usage_zone.schedules[schedule_type]["WD"][5]
|
|
||||||
schedule.Hour_7 = usage_zone.schedules[schedule_type]["WD"][6]
|
|
||||||
schedule.Hour_8 = usage_zone.schedules[schedule_type]["WD"][7]
|
|
||||||
schedule.Hour_9 = usage_zone.schedules[schedule_type]["WD"][8]
|
|
||||||
schedule.Hour_10 = usage_zone.schedules[schedule_type]["WD"][9]
|
|
||||||
schedule.Hour_11 = usage_zone.schedules[schedule_type]["WD"][10]
|
|
||||||
schedule.Hour_12 = usage_zone.schedules[schedule_type]["WD"][11]
|
|
||||||
schedule.Hour_13 = usage_zone.schedules[schedule_type]["WD"][12]
|
|
||||||
schedule.Hour_14 = usage_zone.schedules[schedule_type]["WD"][13]
|
|
||||||
schedule.Hour_15 = usage_zone.schedules[schedule_type]["WD"][14]
|
|
||||||
schedule.Hour_16 = usage_zone.schedules[schedule_type]["WD"][15]
|
|
||||||
schedule.Hour_17 = usage_zone.schedules[schedule_type]["WD"][16]
|
|
||||||
schedule.Hour_18 = usage_zone.schedules[schedule_type]["WD"][17]
|
|
||||||
schedule.Hour_19 = usage_zone.schedules[schedule_type]["WD"][18]
|
|
||||||
schedule.Hour_20 = usage_zone.schedules[schedule_type]["WD"][19]
|
|
||||||
schedule.Hour_21 = usage_zone.schedules[schedule_type]["WD"][20]
|
|
||||||
schedule.Hour_22 = usage_zone.schedules[schedule_type]["WD"][21]
|
|
||||||
schedule.Hour_23 = usage_zone.schedules[schedule_type]["WD"][22]
|
|
||||||
schedule.Hour_24 = usage_zone.schedules[schedule_type]["WD"][23]
|
|
||||||
|
|
||||||
def _add_construction(self, thermal_boundary):
|
|
||||||
for construction in self._idf.idfobjects[self._CONSTRUCTION]:
|
|
||||||
if construction.Name == thermal_boundary.construction_name:
|
|
||||||
return
|
|
||||||
|
|
||||||
if thermal_boundary.layers is None:
|
|
||||||
for material in self._idf.idfobjects[self._MATERIAL]:
|
|
||||||
if material.Name == "DefaultMaterial":
|
|
||||||
return
|
|
||||||
self._idf.set_default_constructions()
|
|
||||||
return
|
|
||||||
for layer in thermal_boundary.layers:
|
|
||||||
self._add_material(layer)
|
|
||||||
layers = thermal_boundary.layers
|
|
||||||
# The constructions should have at least one layer
|
|
||||||
_kwargs = {"Name": thermal_boundary.construction_name, "Outside_Layer": layers[0].material.name}
|
|
||||||
for i in range(1, len(layers) - 1):
|
|
||||||
_kwargs[f'Layer_{i + 1}'] = layers[1].material.name
|
|
||||||
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
|
||||||
|
|
||||||
def _add_zone(self, usage_zone):
|
|
||||||
for zone in self._idf.idfobjects['ZONE']:
|
|
||||||
if zone.Name == usage_zone.id:
|
|
||||||
return
|
|
||||||
# todo: what does we need to define a zone in energy plus?
|
|
||||||
self._idf.newidfobject(self._ZONE, Name=usage_zone.id, Volume=usage_zone.volume)
|
|
||||||
self._add_heating_system(usage_zone)
|
|
||||||
|
|
||||||
def _add_thermostat(self, usage_zone):
|
|
||||||
thermostat_name = f'Thermostat {usage_zone.usage}'
|
|
||||||
for thermostat in self._idf.idfobjects[self._THERMOSTAT]:
|
|
||||||
if thermostat.Name == thermostat_name:
|
|
||||||
return thermostat
|
|
||||||
return self._idf.newidfobject(self._THERMOSTAT,
|
|
||||||
Name=thermostat_name,
|
|
||||||
Constant_Heating_Setpoint=usage_zone.heating_setpoint,
|
|
||||||
Constant_Cooling_Setpoint=usage_zone.cooling_setpoint)
|
|
||||||
|
|
||||||
def _add_heating_system(self, usage_zone):
|
|
||||||
for air_system in self._idf.idfobjects[self._IDEAL_LOAD_AIR_SYSTEM]:
|
|
||||||
if air_system.Zone_Name == usage_zone.id:
|
|
||||||
return
|
|
||||||
thermostat = self._add_thermostat(usage_zone)
|
|
||||||
self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM,
|
|
||||||
Zone_Name=usage_zone.id,
|
|
||||||
Cooling_Availability_Schedule_Name=f'Refrigeration schedules {usage_zone.usage}',
|
|
||||||
Template_Thermostat_Name=thermostat.Name)
|
|
||||||
|
|
||||||
def _add_occupancy(self, usage_zone):
|
|
||||||
self._idf.newidfobject(self._PEOPLE,
|
|
||||||
Name=f'{usage_zone.id}_occupancy',
|
|
||||||
Zone_or_ZoneList_Name=usage_zone.id,
|
|
||||||
Number_of_People_Schedule_Name=f'Occupancy schedules {usage_zone.usage}',
|
|
||||||
Number_of_People_Calculation_Method="People",
|
|
||||||
Number_of_People=500, # todo: get people from where?
|
|
||||||
Fraction_Radiant=0.3, # todo: howto get this from InternalGains
|
|
||||||
Activity_Level_Schedule_Name='occupant schedules'
|
|
||||||
)
|
|
||||||
|
|
||||||
def _add_equipment(self, usage_zone):
|
|
||||||
self._idf.newidfobject(self._ELECTRIC_EQUIPMENT,
|
|
||||||
Name=f'{usage_zone.id}_electricload',
|
|
||||||
Zone_or_ZoneList_Name=usage_zone.id,
|
|
||||||
Schedule_Name=f'Electrical schedules {usage_zone.usage}', # todo: add electrical schedules
|
|
||||||
Design_Level_Calculation_Method='EquipmentLevel',
|
|
||||||
Design_Level=566000 # todo: change it from usage catalog
|
|
||||||
)
|
|
||||||
|
|
||||||
def _add_infiltration(self, usage_zone):
|
|
||||||
for zone in self._idf.idfobjects["ZONE"]:
|
|
||||||
if zone.Name == f'{usage_zone.id}_infiltration':
|
|
||||||
return
|
|
||||||
self._idf.newidfobject(self._INFILTRATION,
|
|
||||||
Name=f'{usage_zone.id}_infiltration',
|
|
||||||
Zone_or_ZoneList_Name=usage_zone.id,
|
|
||||||
Schedule_Name=f'Infiltration schedules {usage_zone.usage}',
|
|
||||||
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
|
|
||||||
Air_Changes_per_Hour=0.35, # todo: change it from usage catalog
|
|
||||||
Constant_Term_Coefficient=0.606, # todo: change it from usage catalog
|
|
||||||
Temperature_Term_Coefficient=3.6359996E-02, # todo: change it from usage catalog
|
|
||||||
Velocity_Term_Coefficient=0.1177165, # todo: change it from usage catalog
|
|
||||||
Velocity_Squared_Term_Coefficient=0.0000000E+00 # todo: change it from usage catalog
|
|
||||||
)
|
|
||||||
|
|
||||||
def _export(self):
|
|
||||||
"""
|
|
||||||
Export the idf file into the given path
|
|
||||||
export type = "Surfaces|Block"
|
|
||||||
"""
|
|
||||||
for building in self._city.buildings:
|
|
||||||
for usage_zone in building.usage_zones:
|
|
||||||
self._add_schedule(usage_zone, "Infiltration")
|
|
||||||
self._add_schedule(usage_zone, "Lights")
|
|
||||||
self._add_schedule(usage_zone, "Occupancy")
|
|
||||||
self._add_schedule(usage_zone, "Refrigeration", self._ON_OFF)
|
|
||||||
|
|
||||||
self._add_zone(usage_zone)
|
|
||||||
self._add_heating_system(usage_zone)
|
|
||||||
# self._add_infiltration(usage_zone)
|
|
||||||
# self._add_occupancy(usage_zone)
|
|
||||||
for thermal_zone in building.thermal_zones:
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
self._add_construction(thermal_boundary)
|
|
||||||
|
|
||||||
if self._export_type == "Surfaces":
|
|
||||||
self._add_surfaces(building)
|
|
||||||
else:
|
|
||||||
self._add_block(building)
|
|
||||||
self._idf.match()
|
|
||||||
self._idf.saveas(str(self._output_path))
|
|
||||||
|
|
||||||
def _add_block(self, building):
|
|
||||||
_points = self._matrix_to_2d_list(building.foot_print.coordinates)
|
|
||||||
self._idf.add_block(name=building.name, coordinates=_points, height=building.max_height,
|
|
||||||
num_stories=int(building.storeys_above_ground))
|
|
||||||
|
|
||||||
for surface in self._idf.idfobjects[self._SURFACE]:
|
|
||||||
for thermal_zone in building.thermal_zones:
|
|
||||||
for boundary in thermal_zone.thermal_boundaries:
|
|
||||||
if surface.Type == self.idf_surfaces[boundary.surface.type]:
|
|
||||||
surface.Construction_Name = boundary.construction_name
|
|
||||||
break
|
|
||||||
for usage_zone in thermal_zone.usage_zones:
|
|
||||||
surface.Zone_Name = usage_zone.id
|
|
||||||
break
|
|
||||||
break
|
|
||||||
self._idf.intersect_match()
|
|
||||||
|
|
||||||
def _add_surfaces(self, building):
|
|
||||||
for thermal_zone in building.thermal_zones:
|
|
||||||
for boundary in thermal_zone.thermal_boundaries:
|
|
||||||
idf_surface_type = self.idf_surfaces[boundary.surface.type]
|
|
||||||
for usage_zone in thermal_zone.usage_zones:
|
|
||||||
surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.surface.name}',
|
|
||||||
Surface_Type=idf_surface_type, Zone_Name=usage_zone.id,
|
|
||||||
Construction_Name=boundary.construction_name)
|
|
||||||
coordinates = self._matrix_to_list(boundary.surface.solid_polygon.coordinates)
|
|
||||||
surface.setcoords(coordinates)
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,151 +0,0 @@
|
||||||
! Minimal.idf
|
|
||||||
! Basic file description: This is a minimal configuration necessary to run.
|
|
||||||
! Highlights: Illustrates minimal items necessary to perform run.
|
|
||||||
! BUILDING, SURFACEGEOMETRY, LOCATION and DESIGNDAY (or RUNPERIOD) are the absolute minimal required input objects.
|
|
||||||
! TIME STEP IN HOUR is included so as to not get warning error.
|
|
||||||
! Including two design days, Run Control object and RunPeriod to facilitate use.
|
|
||||||
! Although not incredibly useful, this could be used as a weather/solar calculator.
|
|
||||||
! Simulation Location/Run: Denver is included. Any could be used.
|
|
||||||
! Building: None.
|
|
||||||
!
|
|
||||||
! Internal gains description: None.
|
|
||||||
!
|
|
||||||
! HVAC: None.
|
|
||||||
!
|
|
||||||
|
|
||||||
Version,9.5;
|
|
||||||
|
|
||||||
Timestep,4;
|
|
||||||
|
|
||||||
Building,
|
|
||||||
None, !- Name
|
|
||||||
0.0000000E+00, !- North Axis {deg}
|
|
||||||
Suburbs, !- Terrain
|
|
||||||
0.04, !- Loads Convergence Tolerance Value {W}
|
|
||||||
0.40, !- Temperature Convergence Tolerance Value {deltaC}
|
|
||||||
FullInteriorAndExterior, !- Solar Distribution
|
|
||||||
25, !- Maximum Number of Warmup Days
|
|
||||||
6; !- Minimum Number of Warmup Days
|
|
||||||
|
|
||||||
GlobalGeometryRules,
|
|
||||||
UpperLeftCorner, !- Starting Vertex Position
|
|
||||||
CounterClockWise, !- Vertex Entry Direction
|
|
||||||
World; !- Coordinate System
|
|
||||||
|
|
||||||
Site:Location,
|
|
||||||
DENVER_STAPLETON_CO_USA_WMO_724690, !- Name
|
|
||||||
39.77, !- Latitude {deg}
|
|
||||||
-104.87, !- Longitude {deg}
|
|
||||||
-7.00, !- Time Zone {hr}
|
|
||||||
1611.00; !- Elevation {m}
|
|
||||||
|
|
||||||
! DENVER_STAPLETON_CO_USA Annual Heating Design Conditions Wind Speed=2.3m/s Wind Dir=180
|
|
||||||
! Coldest Month=December
|
|
||||||
! DENVER_STAPLETON_CO_USA Annual Heating 99.6%, MaxDB=-20°C
|
|
||||||
|
|
||||||
SizingPeriod:DesignDay,
|
|
||||||
DENVER_STAPLETON Ann Htg 99.6% Condns DB, !- Name
|
|
||||||
12, !- Month
|
|
||||||
21, !- Day of Month
|
|
||||||
WinterDesignDay, !- Day Type
|
|
||||||
-20, !- Maximum Dry-Bulb Temperature {C}
|
|
||||||
0.0, !- Daily Dry-Bulb Temperature Range {deltaC}
|
|
||||||
, !- Dry-Bulb Temperature Range Modifier Type
|
|
||||||
, !- Dry-Bulb Temperature Range Modifier Day Schedule Name
|
|
||||||
Wetbulb, !- Humidity Condition Type
|
|
||||||
-20, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C}
|
|
||||||
, !- Humidity Condition Day Schedule Name
|
|
||||||
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
|
|
||||||
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
|
|
||||||
, !- Daily Wet-Bulb Temperature Range {deltaC}
|
|
||||||
83411., !- Barometric Pressure {Pa}
|
|
||||||
2.3, !- Wind Speed {m/s}
|
|
||||||
180, !- Wind Direction {deg}
|
|
||||||
No, !- Rain Indicator
|
|
||||||
No, !- Snow Indicator
|
|
||||||
No, !- Daylight Saving Time Indicator
|
|
||||||
ASHRAEClearSky, !- Solar Model Indicator
|
|
||||||
, !- Beam Solar Day Schedule Name
|
|
||||||
, !- Diffuse Solar Day Schedule Name
|
|
||||||
, !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless}
|
|
||||||
, !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless}
|
|
||||||
0.00; !- Sky Clearness
|
|
||||||
|
|
||||||
! DENVER_STAPLETON Annual Cooling Design Conditions Wind Speed=4m/s Wind Dir=120
|
|
||||||
! Hottest Month=July
|
|
||||||
! DENVER_STAPLETON_CO_USA Annual Cooling (DB=>MWB) .4%, MaxDB=34.1°C MWB=15.8°C
|
|
||||||
|
|
||||||
SizingPeriod:DesignDay,
|
|
||||||
DENVER_STAPLETON Ann Clg .4% Condns DB=>MWB, !- Name
|
|
||||||
7, !- Month
|
|
||||||
21, !- Day of Month
|
|
||||||
SummerDesignDay, !- Day Type
|
|
||||||
34.1, !- Maximum Dry-Bulb Temperature {C}
|
|
||||||
15.2, !- Daily Dry-Bulb Temperature Range {deltaC}
|
|
||||||
, !- Dry-Bulb Temperature Range Modifier Type
|
|
||||||
, !- Dry-Bulb Temperature Range Modifier Day Schedule Name
|
|
||||||
Wetbulb, !- Humidity Condition Type
|
|
||||||
15.8, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C}
|
|
||||||
, !- Humidity Condition Day Schedule Name
|
|
||||||
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
|
|
||||||
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
|
|
||||||
, !- Daily Wet-Bulb Temperature Range {deltaC}
|
|
||||||
83411., !- Barometric Pressure {Pa}
|
|
||||||
4, !- Wind Speed {m/s}
|
|
||||||
120, !- Wind Direction {deg}
|
|
||||||
No, !- Rain Indicator
|
|
||||||
No, !- Snow Indicator
|
|
||||||
No, !- Daylight Saving Time Indicator
|
|
||||||
ASHRAEClearSky, !- Solar Model Indicator
|
|
||||||
, !- Beam Solar Day Schedule Name
|
|
||||||
, !- Diffuse Solar Day Schedule Name
|
|
||||||
, !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless}
|
|
||||||
, !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless}
|
|
||||||
1.00; !- Sky Clearness
|
|
||||||
|
|
||||||
RunPeriod,
|
|
||||||
Run Period 1, !- Name
|
|
||||||
1, !- Begin Month
|
|
||||||
1, !- Begin Day of Month
|
|
||||||
, !- Begin Year
|
|
||||||
12, !- End Month
|
|
||||||
31, !- End Day of Month
|
|
||||||
, !- End Year
|
|
||||||
Tuesday, !- Day of Week for Start Day
|
|
||||||
Yes, !- Use Weather File Holidays and Special Days
|
|
||||||
Yes, !- Use Weather File Daylight Saving Period
|
|
||||||
No, !- Apply Weekend Holiday Rule
|
|
||||||
Yes, !- Use Weather File Rain Indicators
|
|
||||||
Yes; !- Use Weather File Snow Indicators
|
|
||||||
|
|
||||||
SimulationControl,
|
|
||||||
No, !- Do Zone Sizing Calculation
|
|
||||||
No, !- Do System Sizing Calculation
|
|
||||||
No, !- Do Plant Sizing Calculation
|
|
||||||
Yes, !- Run Simulation for Sizing Periods
|
|
||||||
No, !- Run Simulation for Weather File Run Periods
|
|
||||||
No, !- Do HVAC Sizing Simulation for Sizing Periods
|
|
||||||
1; !- Maximum Number of HVAC Sizing Simulation Passes
|
|
||||||
|
|
||||||
Output:VariableDictionary,Regular;
|
|
||||||
|
|
||||||
Output:Variable,*,Site Outdoor Air Drybulb Temperature,Timestep;
|
|
||||||
|
|
||||||
Output:Variable,*,Site Outdoor Air Wetbulb Temperature,Timestep;
|
|
||||||
|
|
||||||
Output:Variable,*,Site Outdoor Air Dewpoint Temperature,Timestep;
|
|
||||||
|
|
||||||
Output:Variable,*,Site Solar Azimuth Angle,Timestep;
|
|
||||||
|
|
||||||
Output:Variable,*,Site Solar Altitude Angle,Timestep;
|
|
||||||
|
|
||||||
Output:Variable,*,Site Direct Solar Radiation Rate per Area,Timestep;
|
|
||||||
|
|
||||||
Output:Variable,*,Site Diffuse Solar Radiation Rate per Area,Timestep;
|
|
||||||
|
|
||||||
OutputControl:Table:Style,
|
|
||||||
HTML; !- Column Separator
|
|
||||||
|
|
||||||
Output:Table:SummaryReports,
|
|
||||||
AllSummary; !- Report 1 Name
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
"""
|
|
||||||
export a city into Obj format
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import trimesh.exchange.obj
|
|
||||||
from exports.formats.triangular import Triangular
|
|
||||||
from imports.geometry_factory import GeometryFactory
|
|
||||||
|
|
||||||
|
|
||||||
class Obj(Triangular):
|
|
||||||
"""
|
|
||||||
Export to obj format
|
|
||||||
"""
|
|
||||||
def __init__(self, city, path):
|
|
||||||
super().__init__(city, path, 'obj')
|
|
||||||
|
|
||||||
def to_ground_points(self):
|
|
||||||
"""
|
|
||||||
Move closer to the origin
|
|
||||||
"""
|
|
||||||
file_name_in = self._city.name + '.' + self._triangular_format
|
|
||||||
file_name_out = self._city.name + '_ground.' + self._triangular_format
|
|
||||||
file_path_in = (Path(self._path).resolve() / file_name_in).resolve()
|
|
||||||
file_path_out = (Path(self._path).resolve() / file_name_out).resolve()
|
|
||||||
scene = GeometryFactory('obj', file_path_in).scene
|
|
||||||
scene.rezero()
|
|
||||||
obj_file = trimesh.exchange.obj.export_obj(scene)
|
|
||||||
with open(file_path_out, 'w') as file:
|
|
||||||
file.write(obj_file)
|
|
|
@ -1,88 +0,0 @@
|
||||||
"""
|
|
||||||
Simplified Radiosity Algorithm
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guillermo.GutierrezMorote@concordia.ca
|
|
||||||
"""
|
|
||||||
import xmltodict
|
|
||||||
|
|
||||||
|
|
||||||
class SimplifiedRadiosityAlgorithm:
|
|
||||||
"""
|
|
||||||
Export to SRA format
|
|
||||||
"""
|
|
||||||
def __init__(self, city, file_name, begin_month=1, begin_day=1, end_month=12, end_day=31):
|
|
||||||
self._file_name = file_name
|
|
||||||
self._begin_month = begin_month
|
|
||||||
self._begin_day = begin_day
|
|
||||||
self._end_month = end_month
|
|
||||||
self._end_day = end_day
|
|
||||||
self._city = city
|
|
||||||
self._export()
|
|
||||||
|
|
||||||
def _correct_point(self, point):
|
|
||||||
# correct the x, y, z values into the reference frame set by city lower_corner
|
|
||||||
x = point[0] - self._city.lower_corner[0]
|
|
||||||
y = point[1] - self._city.lower_corner[1]
|
|
||||||
z = point[2] - self._city.lower_corner[2]
|
|
||||||
return [x, y, z]
|
|
||||||
|
|
||||||
def _export(self, target_buildings_names=None):
|
|
||||||
buildings = []
|
|
||||||
for building_index, building in enumerate(self._city.buildings):
|
|
||||||
if target_buildings_names is None:
|
|
||||||
simulate = 'true'
|
|
||||||
else:
|
|
||||||
simulate = 'false'
|
|
||||||
for name in target_buildings_names:
|
|
||||||
if building.name == name:
|
|
||||||
simulate = 'true'
|
|
||||||
building_dict = {
|
|
||||||
'@Name': f'{building.name}',
|
|
||||||
'@id': f'{building_index}',
|
|
||||||
'@key': f'{building.name}',
|
|
||||||
'@Simulate': f'{simulate}'
|
|
||||||
}
|
|
||||||
walls, roofs, floors = [], [], []
|
|
||||||
for surface in building.surfaces:
|
|
||||||
surface_dict = {
|
|
||||||
'@id': f'{surface.id}',
|
|
||||||
'@ShortWaveReflectance': f'{surface.swr}'
|
|
||||||
}
|
|
||||||
for point_index, point in enumerate(surface.perimeter_polygon.coordinates):
|
|
||||||
point = self._correct_point(point)
|
|
||||||
surface_dict[f'V{point_index}'] = {
|
|
||||||
'@x': f'{point[0]}',
|
|
||||||
'@y': f'{point[1]}',
|
|
||||||
'@z': f'{point[2]}'
|
|
||||||
}
|
|
||||||
if surface.type == 'Wall':
|
|
||||||
walls.append(surface_dict)
|
|
||||||
elif surface.type == 'Roof':
|
|
||||||
roofs.append(surface_dict)
|
|
||||||
else:
|
|
||||||
floors.append(surface_dict)
|
|
||||||
building_dict['Wall'] = walls
|
|
||||||
building_dict['Roof'] = roofs
|
|
||||||
building_dict['Floor'] = floors
|
|
||||||
buildings.append(building_dict)
|
|
||||||
sra = {
|
|
||||||
'CitySim': {
|
|
||||||
'@name': f'{self._file_name.name}',
|
|
||||||
'Simulation': {
|
|
||||||
'@beginMonth': f'{self._begin_month}',
|
|
||||||
'@beginDay': f'{self._begin_day}',
|
|
||||||
'@endMonth': f'{self._end_month}',
|
|
||||||
'@endDay': f'{self._end_day}',
|
|
||||||
},
|
|
||||||
'Climate': {
|
|
||||||
'@location': f'{self._city.climate_file}',
|
|
||||||
'@city': f'{self._city.climate_reference_city}'
|
|
||||||
},
|
|
||||||
'District': {
|
|
||||||
'FarFieldObstructions': None,
|
|
||||||
'Building': buildings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with open(self._file_name, "w") as file:
|
|
||||||
file.write(xmltodict.unparse(sra, pretty=True, short_empty_elements=True))
|
|
|
@ -1,15 +0,0 @@
|
||||||
"""
|
|
||||||
export a city into Stl format
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from exports.formats.triangular import Triangular
|
|
||||||
|
|
||||||
|
|
||||||
class Stl(Triangular):
|
|
||||||
"""
|
|
||||||
Export to STL
|
|
||||||
"""
|
|
||||||
def __init__(self, city, path):
|
|
||||||
super().__init__(city, path, 'stl', write_mode='wb')
|
|
|
@ -1,31 +0,0 @@
|
||||||
"""
|
|
||||||
export a city from trimesh into Triangular format (obj or stl)
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
from trimesh import Trimesh
|
|
||||||
|
|
||||||
|
|
||||||
class Triangular:
|
|
||||||
"""
|
|
||||||
Superclass to export to triangular format (STL or OBJ)
|
|
||||||
"""
|
|
||||||
def __init__(self, city, path, triangular_format, write_mode='w'):
|
|
||||||
self._city = city
|
|
||||||
self._path = path
|
|
||||||
self._triangular_format = triangular_format
|
|
||||||
self._write_mode = write_mode
|
|
||||||
self._export()
|
|
||||||
|
|
||||||
def _export(self):
|
|
||||||
if self._city.name is None:
|
|
||||||
self._city.name = 'unknown_city'
|
|
||||||
file_name = self._city.name + '.' + self._triangular_format
|
|
||||||
file_path = (Path(self._path).resolve() / file_name).resolve()
|
|
||||||
trimesh = Trimesh()
|
|
||||||
for building in self._city.buildings:
|
|
||||||
trimesh = trimesh.union(building.simplified_polyhedron.trimesh)
|
|
||||||
|
|
||||||
with open(file_path, self._write_mode) as file:
|
|
||||||
file.write(trimesh.export(file_type=self._triangular_format))
|
|
|
@ -1,41 +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 ConfigurationHelper:
|
|
||||||
"""
|
|
||||||
Configuration class
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
config_file = Path(Path(__file__).parent.parent / 'config/configuration.ini').resolve()
|
|
||||||
self._config = configparser.ConfigParser()
|
|
||||||
self._config.read(config_file)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_location_distance_for_shared_walls(self):
|
|
||||||
"""
|
|
||||||
Configured maximal distance between attributes to consider that they may share walls in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._config.getfloat('buildings', 'max_location_distance_for_shared_walls')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_coordinate(self) -> float:
|
|
||||||
"""
|
|
||||||
Configured minimal coordinate value
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._config.getfloat('buildings', 'min_coordinate')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_coordinate(self) -> float:
|
|
||||||
"""
|
|
||||||
Configured maximal coordinate value
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._config.getfloat('buildings', 'max_coordinate')
|
|
|
@ -1,57 +0,0 @@
|
||||||
"""
|
|
||||||
Constant module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# universal constants
|
|
||||||
KELVIN = 273.15
|
|
||||||
|
|
||||||
# time
|
|
||||||
MINUTE = 'minute'
|
|
||||||
HOUR = 'hour'
|
|
||||||
DAY = 'day'
|
|
||||||
WEEK = 'week'
|
|
||||||
MONTH = 'month'
|
|
||||||
YEAR = 'year'
|
|
||||||
|
|
||||||
# todo: modify code to use global constants instead of hard-coded values
|
|
||||||
# surface types
|
|
||||||
WALL = 'Wall'
|
|
||||||
GROUND_WALL = 'Ground wall'
|
|
||||||
GROUND = 'Ground'
|
|
||||||
ATTIC_FLOOR = 'Attic floor'
|
|
||||||
ROOF = 'Roof'
|
|
||||||
INTERIOR_SLAB = 'Interior slab'
|
|
||||||
INTERIOR_WALL = 'Interior wall'
|
|
||||||
VIRTUAL_INTERNAL = 'Virtual internal'
|
|
||||||
WINDOW = 'Window'
|
|
||||||
DOOR = 'Door'
|
|
||||||
SKYLIGHT = 'Skylight'
|
|
||||||
|
|
||||||
# todo: homogenize function and usage!!
|
|
||||||
# function
|
|
||||||
RESIDENTIAL = 'residential'
|
|
||||||
SFH = 'single family house'
|
|
||||||
MFH = 'multifamily house'
|
|
||||||
HOTEL = 'hotel'
|
|
||||||
HOSPITAL = 'hospital'
|
|
||||||
OUTPATIENT = 'outpatient'
|
|
||||||
COMMERCIAL = 'commercial'
|
|
||||||
STRIP_MALL = 'strip mall'
|
|
||||||
WAREHOUSE = 'warehouse'
|
|
||||||
PRIMARY_SCHOOL = 'primary school'
|
|
||||||
SECONDARY_SCHOOL = 'secondary school'
|
|
||||||
OFFICE = 'office'
|
|
||||||
LARGE_OFFICE = 'large office'
|
|
||||||
OFFICE_WORKSHOP = 'office/workshop'
|
|
||||||
|
|
||||||
# usage
|
|
||||||
INDUSTRY = 'industry'
|
|
||||||
OFFICE_ADMINISTRATION = 'office and administration'
|
|
||||||
HEALTH_CARE = 'health care'
|
|
||||||
RETAIL = 'retail'
|
|
||||||
HALL = 'hall'
|
|
||||||
RESTAURANT = 'restaurant'
|
|
||||||
EDUCATION = 'education'
|
|
|
@ -1,99 +0,0 @@
|
||||||
"""
|
|
||||||
Enrich city
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from imports.construction_factory import ConstructionFactory
|
|
||||||
from imports.usage_factory import UsageFactory
|
|
||||||
from imports.schedules_factory import SchedulesFactory
|
|
||||||
|
|
||||||
|
|
||||||
class EnrichCity:
|
|
||||||
"""
|
|
||||||
Enrich city
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, city):
|
|
||||||
self._city = city
|
|
||||||
self._enriched_city = None
|
|
||||||
self._errors = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def errors(self) -> [str]:
|
|
||||||
"""
|
|
||||||
Error list
|
|
||||||
"""
|
|
||||||
return self._errors
|
|
||||||
|
|
||||||
def enriched_city(self, construction_format=None, usage_format=None, schedules_format=None):
|
|
||||||
"""
|
|
||||||
Enrich the city with the given formats
|
|
||||||
:return: City
|
|
||||||
"""
|
|
||||||
if self._enriched_city is None:
|
|
||||||
self._errors = []
|
|
||||||
print('original:', len(self._city.buildings))
|
|
||||||
if construction_format is not None:
|
|
||||||
self._enriched_city = self._construction(construction_format)
|
|
||||||
if len(self._errors) != 0:
|
|
||||||
return self._enriched_city
|
|
||||||
if usage_format is not None:
|
|
||||||
self._enriched_city = self._usage(usage_format)
|
|
||||||
if len(self._errors) != 0:
|
|
||||||
return self._enriched_city
|
|
||||||
if schedules_format is not None:
|
|
||||||
self._enriched_city = self._schedules(schedules_format)
|
|
||||||
if len(self._errors) != 0:
|
|
||||||
return self._enriched_city
|
|
||||||
self._enriched_city = self._city
|
|
||||||
return self._enriched_city
|
|
||||||
|
|
||||||
def _construction(self, construction_format):
|
|
||||||
|
|
||||||
# todo: in construction factory, when adding the values to the thermal zones,
|
|
||||||
# these are created using the just read storeys_above_ground -> review where to assign this value!!
|
|
||||||
ConstructionFactory(construction_format, self._city).enrich()
|
|
||||||
for building in self._city.buildings:
|
|
||||||
# infiltration_rate_system_off is a mandatory parameter.
|
|
||||||
# If it is not returned, extract the building from the calculation list
|
|
||||||
if building.thermal_zones[0].infiltration_rate_system_off is None:
|
|
||||||
self._city.remove_city_object(building)
|
|
||||||
if self._city.buildings is None:
|
|
||||||
self._errors.append('no archetype found per construction')
|
|
||||||
self._enriched_city = self._city
|
|
||||||
return self._enriched_city
|
|
||||||
print('enriched with construction:', len(self._city.buildings))
|
|
||||||
return self._city
|
|
||||||
|
|
||||||
def _usage(self, usage_format):
|
|
||||||
UsageFactory(usage_format, self._city).enrich()
|
|
||||||
for building in self._city.buildings:
|
|
||||||
# At least one thermal zone must be created.
|
|
||||||
# If it is not created, extract the building from the calculation list
|
|
||||||
if len(building.usage_zones) <= 0:
|
|
||||||
self._city.remove_city_object(building)
|
|
||||||
if self._city.buildings is None:
|
|
||||||
self._errors.append('no archetype found per usage')
|
|
||||||
self._enriched_city = self._city
|
|
||||||
return self._enriched_city
|
|
||||||
print('enriched with usage:', len(self._city.buildings))
|
|
||||||
return self._city
|
|
||||||
|
|
||||||
def _schedules(self, schedules_format):
|
|
||||||
SchedulesFactory(schedules_format, self._city).enrich()
|
|
||||||
for building in self._city.buildings:
|
|
||||||
counter_schedules = 0
|
|
||||||
for usage_zone in building.usage_zones:
|
|
||||||
# At least one schedule must be created at each thermal zone.
|
|
||||||
# If it is not created, extract the building from the calculation list
|
|
||||||
if len(usage_zone.schedules) > 0:
|
|
||||||
counter_schedules += 1
|
|
||||||
if counter_schedules < len(building.usage_zones):
|
|
||||||
self._city.remove_city_object(building)
|
|
||||||
if self._city.buildings is None:
|
|
||||||
self._errors.append('no archetype found per usage')
|
|
||||||
self._enriched_city = self._city
|
|
||||||
return self._enriched_city
|
|
||||||
print('enriched with occupancy:', len(self._city.buildings))
|
|
||||||
return self._city
|
|
|
@ -1,196 +0,0 @@
|
||||||
"""
|
|
||||||
Geometry helper
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import math
|
|
||||||
import numpy as np
|
|
||||||
import requests
|
|
||||||
from trimesh import Trimesh
|
|
||||||
from trimesh import intersections
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
from city_model_structure.attributes.polyhedron import Polyhedron
|
|
||||||
from helpers.location import Location
|
|
||||||
from helpers.configuration_helper import ConfigurationHelper
|
|
||||||
|
|
||||||
|
|
||||||
class GeometryHelper:
|
|
||||||
"""
|
|
||||||
Geometry helper class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, delta=0, area_delta=0):
|
|
||||||
self._delta = delta
|
|
||||||
self._area_delta = area_delta
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def adjacent_locations(location1, location2):
|
|
||||||
"""
|
|
||||||
Determine when two attributes may be adjacent or not based in the dis
|
|
||||||
:param location1:
|
|
||||||
:param location2:
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
max_distance = ConfigurationHelper().max_location_distance_for_shared_walls
|
|
||||||
return GeometryHelper.distance_between_points(location1, location2) < max_distance
|
|
||||||
|
|
||||||
def almost_same_area(self, a1, a2):
|
|
||||||
"""
|
|
||||||
Compare two areas and decides if they are almost equal (absolute error under delta)
|
|
||||||
:param a1
|
|
||||||
:param a2
|
|
||||||
:return: Boolean
|
|
||||||
"""
|
|
||||||
if a1 == 0 or a2 == 0:
|
|
||||||
return False
|
|
||||||
delta = math.fabs(a1 - a2)
|
|
||||||
return delta <= self._area_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
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def segment_list_to_trimesh(lines) -> Trimesh:
|
|
||||||
"""
|
|
||||||
Transform a list of segments into a Trimesh
|
|
||||||
"""
|
|
||||||
line_points = [lines[0][0], lines[0][1]]
|
|
||||||
lines.remove(lines[0])
|
|
||||||
while len(lines) > 1:
|
|
||||||
i = 0
|
|
||||||
for line in lines:
|
|
||||||
i += 1
|
|
||||||
if GeometryHelper.distance_between_points(line[0], line_points[len(line_points) - 1]) < 1e-8:
|
|
||||||
line_points.append(line[1])
|
|
||||||
lines.pop(i - 1)
|
|
||||||
break
|
|
||||||
if GeometryHelper.distance_between_points(line[1], line_points[len(line_points) - 1]) < 1e-8:
|
|
||||||
line_points.append(line[0])
|
|
||||||
lines.pop(i - 1)
|
|
||||||
break
|
|
||||||
polyhedron = Polyhedron(Polygon(line_points).triangulate())
|
|
||||||
trimesh = Trimesh(polyhedron.vertices, polyhedron.faces)
|
|
||||||
return trimesh
|
|
||||||
|
|
||||||
@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)
|
|
||||||
mesh_merge.fix_normals()
|
|
||||||
|
|
||||||
return mesh_merge
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def divide_mesh_by_plane(trimesh, normal_plane, point_plane):
|
|
||||||
"""
|
|
||||||
Divide a mesh by a plane
|
|
||||||
:param trimesh: 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.
|
|
||||||
# todo: review split method in https://github.com/mikedh/trimesh/issues/235,
|
|
||||||
# once triangulate_polygon in Polygon class is solved
|
|
||||||
|
|
||||||
normal_plane_opp = [None] * len(normal_plane)
|
|
||||||
for i in range(0, len(normal_plane)):
|
|
||||||
normal_plane_opp[i] = - normal_plane[i]
|
|
||||||
|
|
||||||
section_1 = intersections.slice_mesh_plane(trimesh, normal_plane, point_plane)
|
|
||||||
if section_1 is None:
|
|
||||||
return [trimesh]
|
|
||||||
lines = list(intersections.mesh_plane(trimesh, normal_plane, point_plane))
|
|
||||||
cap = GeometryHelper.segment_list_to_trimesh(lines)
|
|
||||||
trimesh_1 = GeometryHelper._merge_meshes(section_1, cap)
|
|
||||||
|
|
||||||
section_2 = intersections.slice_mesh_plane(trimesh, normal_plane_opp, point_plane)
|
|
||||||
if section_2 is None:
|
|
||||||
return [trimesh_1]
|
|
||||||
trimesh_2 = GeometryHelper._merge_meshes(section_2, cap)
|
|
||||||
|
|
||||||
return [trimesh_1, trimesh_2]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_location(latitude, longitude) -> Location:
|
|
||||||
"""
|
|
||||||
Get Location from latitude and longitude
|
|
||||||
"""
|
|
||||||
url = 'https://nominatim.openstreetmap.org/reverse?lat={latitude}&lon={longitude}&format=json'
|
|
||||||
response = requests.get(url.format(latitude=latitude, longitude=longitude))
|
|
||||||
if response.status_code != 200:
|
|
||||||
# This means something went wrong.
|
|
||||||
raise Exception('GET /tasks/ {}'.format(response.status_code))
|
|
||||||
|
|
||||||
response = response.json()
|
|
||||||
city = 'Unknown'
|
|
||||||
country = 'ca'
|
|
||||||
if 'city' in response['address']:
|
|
||||||
city = response['address']['city']
|
|
||||||
if 'country_code' in response['address']:
|
|
||||||
country = response['address']['country_code']
|
|
||||||
return Location(country, city)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def distance_between_points(vertex1, vertex2):
|
|
||||||
"""
|
|
||||||
distance between points in an n-D Euclidean space
|
|
||||||
:param vertex1: point or vertex
|
|
||||||
:param vertex2: point or vertex
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
power = 0
|
|
||||||
for dimension in range(0, len(vertex1)):
|
|
||||||
power += math.pow(vertex2[dimension]-vertex1[dimension], 2)
|
|
||||||
distance = math.sqrt(power)
|
|
||||||
return distance
|
|
|
@ -1,29 +0,0 @@
|
||||||
"""
|
|
||||||
Location module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Location:
|
|
||||||
"""
|
|
||||||
Location
|
|
||||||
"""
|
|
||||||
def __init__(self, country, city):
|
|
||||||
self._country = country
|
|
||||||
self._city = city
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city(self):
|
|
||||||
"""
|
|
||||||
City name
|
|
||||||
"""
|
|
||||||
return self._city
|
|
||||||
|
|
||||||
@property
|
|
||||||
def country(self):
|
|
||||||
"""
|
|
||||||
Country code
|
|
||||||
"""
|
|
||||||
return self._country
|
|
|
@ -1,137 +0,0 @@
|
||||||
"""
|
|
||||||
monthly_to_hourly_demand module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import calendar as cal
|
|
||||||
import pandas as pd
|
|
||||||
from city_model_structure.building_demand.occupants import Occupants
|
|
||||||
import helpers.constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class MonthlyToHourlyDemand:
|
|
||||||
"""
|
|
||||||
MonthlyToHourlyDemand class
|
|
||||||
"""
|
|
||||||
def __init__(self, building, conditioning_seasons):
|
|
||||||
self._hourly_heating = pd.DataFrame()
|
|
||||||
self._hourly_cooling = pd.DataFrame()
|
|
||||||
self._building = building
|
|
||||||
self._conditioning_seasons = conditioning_seasons
|
|
||||||
|
|
||||||
def hourly_heating(self, key):
|
|
||||||
"""
|
|
||||||
hourly distribution of the monthly heating of a building
|
|
||||||
:param key: string
|
|
||||||
:return: [hourly_heating]
|
|
||||||
"""
|
|
||||||
# todo: this method and the insel model have to be reviewed for more than one thermal zone
|
|
||||||
external_temp = self._building.external_temperature[cte.HOUR]
|
|
||||||
# todo: review index depending on how the schedules are defined, either 8760 or 24 hours
|
|
||||||
for usage_zone in self._building.usage_zones:
|
|
||||||
temp_set = float(usage_zone.heating_setpoint)-3
|
|
||||||
temp_back = float(usage_zone.heating_setback)-3
|
|
||||||
# todo: if these are data frames, then they should be called as (Occupancy should be in low case):
|
|
||||||
# usage_zone.schedules.Occupancy
|
|
||||||
# self._conditioning_seasons.heating
|
|
||||||
occupancy = Occupants().get_complete_year_schedule(usage_zone.schedules['Occupancy'])
|
|
||||||
heating_schedule = self._conditioning_seasons['heating']
|
|
||||||
|
|
||||||
hourly_heating = []
|
|
||||||
i = 0
|
|
||||||
j = 0
|
|
||||||
temp_grad_day = []
|
|
||||||
for month in range(1, 13):
|
|
||||||
temp_grad_month = 0
|
|
||||||
month_range = cal.monthrange(2015, month)[1]
|
|
||||||
for _ in range(1, month_range+1):
|
|
||||||
external_temp_med = 0
|
|
||||||
for hour in range(0, 24):
|
|
||||||
external_temp_med += external_temp[key][i]/24
|
|
||||||
for hour in range(0, 24):
|
|
||||||
if external_temp_med < temp_set and heating_schedule[month-1] == 1:
|
|
||||||
if occupancy[hour] > 0:
|
|
||||||
hdd = temp_set - external_temp[key][i]
|
|
||||||
if hdd < 0:
|
|
||||||
hdd = 0
|
|
||||||
temp_grad_day.append(hdd)
|
|
||||||
else:
|
|
||||||
hdd = temp_back - external_temp[key][i]
|
|
||||||
if hdd < 0:
|
|
||||||
hdd = 0
|
|
||||||
temp_grad_day.append(hdd)
|
|
||||||
else:
|
|
||||||
temp_grad_day.append(0)
|
|
||||||
|
|
||||||
temp_grad_month += temp_grad_day[i]
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
for _ in range(1, month_range + 1):
|
|
||||||
for hour in range(0, 24):
|
|
||||||
monthly_demand = self._building.heating[cte.MONTH][month-1]
|
|
||||||
if monthly_demand == 'NaN':
|
|
||||||
monthly_demand = 0
|
|
||||||
if temp_grad_month == 0:
|
|
||||||
hourly_demand = 0
|
|
||||||
else:
|
|
||||||
hourly_demand = float(monthly_demand)*float(temp_grad_day[j])/float(temp_grad_month)
|
|
||||||
hourly_heating.append(hourly_demand)
|
|
||||||
j += 1
|
|
||||||
self._hourly_heating = pd.DataFrame(data=hourly_heating, columns=['monthly to hourly'])
|
|
||||||
return self._hourly_heating
|
|
||||||
|
|
||||||
def hourly_cooling(self, key):
|
|
||||||
"""
|
|
||||||
hourly distribution of the monthly cooling of a building
|
|
||||||
:param key: string
|
|
||||||
:return: [hourly_cooling]
|
|
||||||
"""
|
|
||||||
# todo: this method and the insel model have to be reviewed for more than one thermal zone
|
|
||||||
external_temp = self._building.external_temperature[cte.HOUR]
|
|
||||||
# todo: review index depending on how the schedules are defined, either 8760 or 24 hours
|
|
||||||
for usage_zone in self._building.usage_zones:
|
|
||||||
temp_set = float(usage_zone.cooling_setpoint)
|
|
||||||
temp_back = 100
|
|
||||||
occupancy = Occupants().get_complete_year_schedule(usage_zone.schedules['Occupancy'])
|
|
||||||
cooling_schedule = self._conditioning_seasons['cooling']
|
|
||||||
|
|
||||||
hourly_cooling = []
|
|
||||||
i = 0
|
|
||||||
j = 0
|
|
||||||
temp_grad_day = []
|
|
||||||
for month in range(1, 13):
|
|
||||||
temp_grad_month = 0
|
|
||||||
month_range = cal.monthrange(2015, month)[1]
|
|
||||||
for _ in range(1, month_range[1] + 1):
|
|
||||||
for hour in range(0, 24):
|
|
||||||
if external_temp[key][i] > temp_set and cooling_schedule[month - 1] == 1:
|
|
||||||
if occupancy[hour] > 0:
|
|
||||||
cdd = external_temp[key][i] - temp_set
|
|
||||||
if cdd < 0:
|
|
||||||
cdd = 0
|
|
||||||
temp_grad_day.append(cdd)
|
|
||||||
else:
|
|
||||||
cdd = external_temp[key][i] - temp_back
|
|
||||||
if cdd < 0:
|
|
||||||
cdd = 0
|
|
||||||
temp_grad_day.append(cdd)
|
|
||||||
else:
|
|
||||||
temp_grad_day.append(0)
|
|
||||||
|
|
||||||
temp_grad_month += temp_grad_day[i]
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
for _ in range(1, month_range[1] + 1):
|
|
||||||
for hour in range(0, 24):
|
|
||||||
# monthly_demand = self._building.heating[cte.MONTH]['INSEL'][month-1]
|
|
||||||
monthly_demand = self._building.cooling[cte.MONTH][month - 1]
|
|
||||||
if monthly_demand == 'NaN':
|
|
||||||
monthly_demand = 0
|
|
||||||
if temp_grad_month == 0:
|
|
||||||
hourly_demand = 0
|
|
||||||
else:
|
|
||||||
hourly_demand = float(monthly_demand) * float(temp_grad_day[j]) / float(temp_grad_month)
|
|
||||||
hourly_cooling.append(hourly_demand)
|
|
||||||
j += 1
|
|
||||||
self._hourly_cooling = pd.DataFrame(data=hourly_cooling, columns=['monthly to hourly'])
|
|
||||||
return self._hourly_cooling
|
|
|
@ -1,157 +0,0 @@
|
||||||
"""
|
|
||||||
Storeys generation helper
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import math
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from helpers import constants as cte
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
from city_model_structure.attributes.point import Point
|
|
||||||
from city_model_structure.building_demand.storey import Storey
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
|
|
||||||
|
|
||||||
class StoreysGeneration:
|
|
||||||
"""
|
|
||||||
StoreysGeneration
|
|
||||||
"""
|
|
||||||
def __init__(self, building, divide_in_storeys=False):
|
|
||||||
self._building = building
|
|
||||||
self._divide_in_storeys = divide_in_storeys
|
|
||||||
self._storeys = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def storeys(self) -> [Storey]:
|
|
||||||
"""
|
|
||||||
subsections of building trimesh by storey in case of no interiors defined
|
|
||||||
:return: [Storey]
|
|
||||||
"""
|
|
||||||
number_of_storeys, height = self._calculate_number_storeys_and_height(self._building.average_storey_height,
|
|
||||||
self._building.eave_height,
|
|
||||||
self._building.storeys_above_ground)
|
|
||||||
number_of_storeys = 1
|
|
||||||
if not self._divide_in_storeys or number_of_storeys == 1:
|
|
||||||
return [Storey('storey_0', self._building.surfaces, [None, None], self._building.volume)]
|
|
||||||
|
|
||||||
if number_of_storeys == 0:
|
|
||||||
raise Exception('Number of storeys cannot be 0')
|
|
||||||
|
|
||||||
storeys = []
|
|
||||||
surfaces_child_last_storey = []
|
|
||||||
rest_surfaces = []
|
|
||||||
|
|
||||||
total_volume = 0
|
|
||||||
for i in range(0, number_of_storeys - 1):
|
|
||||||
name = 'storey_' + str(i)
|
|
||||||
surfaces_child = []
|
|
||||||
if i == 0:
|
|
||||||
neighbours = [None, 'storey_1']
|
|
||||||
for surface in self._building.surfaces:
|
|
||||||
if surface.type == cte.GROUND:
|
|
||||||
surfaces_child.append(surface)
|
|
||||||
else:
|
|
||||||
rest_surfaces.append(surface)
|
|
||||||
else:
|
|
||||||
neighbours = ['storey_' + str(i - 1), 'storey_' + str(i + 1)]
|
|
||||||
height_division = self._building.lower_corner[2] + height * (i + 1)
|
|
||||||
intersections = []
|
|
||||||
for surface in rest_surfaces:
|
|
||||||
if surface.type == cte.ROOF:
|
|
||||||
if height_division >= surface.upper_corner[2] > height_division - height:
|
|
||||||
surfaces_child.append(surface)
|
|
||||||
else:
|
|
||||||
surfaces_child_last_storey.append(surface)
|
|
||||||
else:
|
|
||||||
surface_child, rest_surface, intersection = surface.divide(height_division)
|
|
||||||
surfaces_child.append(surface_child)
|
|
||||||
intersections.extend(intersection)
|
|
||||||
if i == number_of_storeys - 2:
|
|
||||||
surfaces_child_last_storey.append(rest_surface)
|
|
||||||
points = []
|
|
||||||
for intersection in intersections:
|
|
||||||
points.append(intersection[1])
|
|
||||||
coordinates = self._intersections_to_coordinates(intersections)
|
|
||||||
polygon = Polygon(coordinates)
|
|
||||||
ceiling = Surface(polygon, polygon, surface_type=cte.INTERIOR_SLAB)
|
|
||||||
surfaces_child.append(ceiling)
|
|
||||||
volume = ceiling.area_above_ground * height
|
|
||||||
total_volume += volume
|
|
||||||
storeys.append(Storey(name, surfaces_child, neighbours, volume))
|
|
||||||
name = 'storey_' + str(number_of_storeys - 1)
|
|
||||||
neighbours = ['storey_' + str(number_of_storeys - 2), None]
|
|
||||||
volume = self._building.volume - total_volume
|
|
||||||
if volume < 0:
|
|
||||||
raise Exception('Error in storeys creation, volume of last storey cannot be lower that 0')
|
|
||||||
storeys.append(Storey(name, surfaces_child_last_storey, neighbours, volume))
|
|
||||||
return storeys
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _calculate_number_storeys_and_height(average_storey_height, eave_height, storeys_above_ground):
|
|
||||||
if average_storey_height is None:
|
|
||||||
if storeys_above_ground is None or storeys_above_ground <= 0:
|
|
||||||
sys.stderr.write('Warning: not enough information to divide building into storeys, '
|
|
||||||
'either number of storeys or average storey height must be provided.\n')
|
|
||||||
return 0, 0
|
|
||||||
number_of_storeys = int(storeys_above_ground)
|
|
||||||
height = eave_height / number_of_storeys
|
|
||||||
else:
|
|
||||||
height = float(average_storey_height)
|
|
||||||
if storeys_above_ground is not None:
|
|
||||||
number_of_storeys = int(storeys_above_ground)
|
|
||||||
else:
|
|
||||||
number_of_storeys = math.floor(float(eave_height) / height) + 1
|
|
||||||
last_storey_height = float(eave_height) - height*(number_of_storeys-1)
|
|
||||||
if last_storey_height < 0.3*height:
|
|
||||||
number_of_storeys -= 1
|
|
||||||
return number_of_storeys, height
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _intersections_to_coordinates(edges_list):
|
|
||||||
# todo: this method is not robust, the while loop needs to be improved
|
|
||||||
points = [Point(edges_list[0][0]), Point(edges_list[0][1])]
|
|
||||||
found_edges = []
|
|
||||||
j = 0
|
|
||||||
while j < len(points)-1:
|
|
||||||
for i in range(1, len(edges_list)):
|
|
||||||
if i not in found_edges:
|
|
||||||
point_2 = points[len(points) - 1]
|
|
||||||
point_1 = Point(edges_list[i][0])
|
|
||||||
found = False
|
|
||||||
if point_1.distance_to_point(point_2) <= 1e-10:
|
|
||||||
points.append(Point(edges_list[i][1]))
|
|
||||||
found_edges.append(i)
|
|
||||||
found = True
|
|
||||||
if not found:
|
|
||||||
point_1 = Point(edges_list[i][1])
|
|
||||||
if point_1.distance_to_point(point_2) <= 1e-10:
|
|
||||||
points.append(Point(edges_list[i][0]))
|
|
||||||
found_edges.append(i)
|
|
||||||
j += 1
|
|
||||||
|
|
||||||
points.remove(points[len(points)-1])
|
|
||||||
array_points = []
|
|
||||||
for point in points:
|
|
||||||
array_points.append(point.coordinates)
|
|
||||||
return np.array(array_points)
|
|
||||||
|
|
||||||
def assign_thermal_zones_delimited_by_thermal_boundaries(self):
|
|
||||||
"""
|
|
||||||
During storeys creation, the thermal boundaries and zones are also created.
|
|
||||||
It is afterwards needed to define which zones are delimited by each thermal boundary
|
|
||||||
"""
|
|
||||||
for storey in self._building.storeys:
|
|
||||||
for thermal_boundary in storey.thermal_boundaries:
|
|
||||||
if thermal_boundary.surface.type != cte.INTERIOR_WALL or thermal_boundary.surface.type != cte.INTERIOR_SLAB:
|
|
||||||
# external thermal boundary -> only one thermal zone
|
|
||||||
thermal_zones = [storey.thermal_zone]
|
|
||||||
else:
|
|
||||||
# internal thermal boundary -> two thermal zones
|
|
||||||
grad = np.rad2deg(thermal_boundary.surface.inclination)
|
|
||||||
if grad >= 170:
|
|
||||||
thermal_zones = [storey.thermal_zone, storey.neighbours[0]]
|
|
||||||
else:
|
|
||||||
thermal_zones = [storey.neighbours[1], storey.thermal_zone]
|
|
||||||
thermal_boundary.thermal_zones = thermal_zones
|
|
|
@ -1,79 +0,0 @@
|
||||||
"""
|
|
||||||
CaPhysicsParameters import the construction and material information for Canada
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from imports.construction.helpers.construction_helper import ConstructionHelper
|
|
||||||
from imports.construction.nrel_physics_interface import NrelPhysicsInterface
|
|
||||||
from imports.construction.helpers.storeys_generation import StoreysGeneration
|
|
||||||
|
|
||||||
|
|
||||||
class CaPhysicsParameters(NrelPhysicsInterface):
|
|
||||||
"""
|
|
||||||
CaPhysicsParameters class
|
|
||||||
"""
|
|
||||||
def __init__(self, city, base_path):
|
|
||||||
super().__init__(base_path, 'ca_constructions_reduced.xml', 'ca_archetypes_reduced.xml')
|
|
||||||
self._city = city
|
|
||||||
|
|
||||||
def enrich_buildings(self):
|
|
||||||
"""
|
|
||||||
Returns the city with the construction parameters assigned to the buildings
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
city = self._city
|
|
||||||
# it is assumed that all buildings have the same archetypes' keys
|
|
||||||
for building in city.buildings:
|
|
||||||
archetype = self._search_archetype(ConstructionHelper.nrcan_from_function(building.function),
|
|
||||||
building.year_of_construction)
|
|
||||||
if archetype is None:
|
|
||||||
sys.stderr.write(f'Building {building.name} has unknown archetype for building function: '
|
|
||||||
f'{ConstructionHelper.nrcan_from_function(building.function)} '
|
|
||||||
f'and building year of construction: {building.year_of_construction}\n')
|
|
||||||
continue
|
|
||||||
|
|
||||||
self._create_storeys(building, archetype)
|
|
||||||
self._assign_values(building, archetype)
|
|
||||||
|
|
||||||
def _search_archetype(self, function, year_of_construction):
|
|
||||||
for building_archetype in self._building_archetypes:
|
|
||||||
a_ft = str(building_archetype.archetype_keys['@function'])
|
|
||||||
a_pc = str(building_archetype.archetype_keys['@periodOfConstruction'])
|
|
||||||
a_yc1 = int(a_pc.split(sep='-')[0])
|
|
||||||
a_yc2 = int(a_pc.split(sep='-')[1])
|
|
||||||
if a_ft == str(function):
|
|
||||||
if a_yc1 <= int(year_of_construction) <= a_yc2:
|
|
||||||
return building_archetype
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _assign_values(self, building, archetype):
|
|
||||||
for thermal_zone in building.thermal_zones:
|
|
||||||
thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value
|
|
||||||
thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity
|
|
||||||
thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio
|
|
||||||
thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on
|
|
||||||
thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
construction_type = ConstructionHelper.nrcan_construction_types[thermal_boundary.type]
|
|
||||||
thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type)
|
|
||||||
thermal_boundary.u_value = thermal_boundary_archetype.overall_u_value
|
|
||||||
thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance
|
|
||||||
thermal_boundary.construction_name = thermal_boundary_archetype.construction_name
|
|
||||||
thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio
|
|
||||||
if thermal_boundary.thermal_openings is not None:
|
|
||||||
for thermal_opening in thermal_boundary.thermal_openings:
|
|
||||||
if thermal_boundary_archetype.thermal_opening is not None:
|
|
||||||
thermal_opening_archetype = thermal_boundary_archetype.thermal_opening
|
|
||||||
thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio
|
|
||||||
thermal_opening.g_value = thermal_opening_archetype.g_value
|
|
||||||
thermal_opening.overall_u_value = thermal_opening_archetype.overall_u_value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_storeys(building, archetype):
|
|
||||||
building.average_storey_height = archetype.average_storey_height
|
|
||||||
building.storeys_above_ground = archetype.storeys_above_ground
|
|
||||||
storeys_generation = StoreysGeneration(building)
|
|
||||||
storeys = storeys_generation.storeys
|
|
||||||
building.storeys = storeys
|
|
||||||
storeys_generation.assign_thermal_zones_delimited_by_thermal_boundaries()
|
|
|
@ -1,97 +0,0 @@
|
||||||
"""
|
|
||||||
NrelBuildingArchetype stores construction information by building archetypes
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from typing import List
|
|
||||||
from imports.construction.data_classes.nrel_thermal_boundary_archetype import NrelThermalBoundaryArchetype
|
|
||||||
|
|
||||||
|
|
||||||
class NrelBuildingArchetype:
|
|
||||||
"""
|
|
||||||
NrelBuildingArchetype class
|
|
||||||
"""
|
|
||||||
def __init__(self, archetype_keys, average_storey_height, storeys_above_ground, effective_thermal_capacity,
|
|
||||||
additional_thermal_bridge_u_value, indirectly_heated_area_ratio, infiltration_rate_system_off,
|
|
||||||
infiltration_rate_system_on, thermal_boundary_archetypes):
|
|
||||||
self._archetype_keys = archetype_keys
|
|
||||||
self._average_storey_height = average_storey_height
|
|
||||||
self._storeys_above_ground = storeys_above_ground
|
|
||||||
self._effective_thermal_capacity = effective_thermal_capacity
|
|
||||||
self._additional_thermal_bridge_u_value = additional_thermal_bridge_u_value
|
|
||||||
self._indirectly_heated_area_ratio = indirectly_heated_area_ratio
|
|
||||||
self._infiltration_rate_system_off = infiltration_rate_system_off
|
|
||||||
self._infiltration_rate_system_on = infiltration_rate_system_on
|
|
||||||
self._thermal_boundary_archetypes = thermal_boundary_archetypes
|
|
||||||
|
|
||||||
@property
|
|
||||||
def archetype_keys(self) -> {}:
|
|
||||||
"""
|
|
||||||
Get keys that define the archetype
|
|
||||||
:return: dictionary
|
|
||||||
"""
|
|
||||||
return self._archetype_keys
|
|
||||||
|
|
||||||
@property
|
|
||||||
def average_storey_height(self):
|
|
||||||
"""
|
|
||||||
Get archetype's building storey height in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._average_storey_height
|
|
||||||
|
|
||||||
@property
|
|
||||||
def storeys_above_ground(self):
|
|
||||||
"""
|
|
||||||
Get archetype's building storey height in meters
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._storeys_above_ground
|
|
||||||
|
|
||||||
@property
|
|
||||||
def effective_thermal_capacity(self):
|
|
||||||
"""
|
|
||||||
Get archetype's effective thermal capacity in J/m2K
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._effective_thermal_capacity
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_thermal_bridge_u_value(self):
|
|
||||||
"""
|
|
||||||
Get archetype's additional U value due to thermal bridges in W/m2K
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._additional_thermal_bridge_u_value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def indirectly_heated_area_ratio(self):
|
|
||||||
"""
|
|
||||||
Get archetype's indirectly heated area ratio
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._indirectly_heated_area_ratio
|
|
||||||
|
|
||||||
@property
|
|
||||||
def infiltration_rate_system_off(self):
|
|
||||||
"""
|
|
||||||
Get archetype's infiltration rate when conditioning systems OFF in air changes per hour (ACH)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._infiltration_rate_system_off
|
|
||||||
|
|
||||||
@property
|
|
||||||
def infiltration_rate_system_on(self):
|
|
||||||
"""
|
|
||||||
Get archetype's infiltration rate when conditioning systems ON in air changes per hour (ACH)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._infiltration_rate_system_on
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_boundary_archetypes(self) -> List[NrelThermalBoundaryArchetype]:
|
|
||||||
"""
|
|
||||||
Get thermal boundary archetypes associated to the building archetype
|
|
||||||
:return: list of boundary archetypes
|
|
||||||
"""
|
|
||||||
return self._thermal_boundary_archetypes
|
|
|
@ -1,63 +0,0 @@
|
||||||
"""
|
|
||||||
NrelLayerArchetype stores layer and materials information, complementing the NrelBuildingArchetype class
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NrelLayerArchetype:
|
|
||||||
"""
|
|
||||||
NrelLayerArchetype class
|
|
||||||
"""
|
|
||||||
def __init__(self, name, solar_absorptance, thermal_absorptance, visible_absorptance, thickness=None,
|
|
||||||
conductivity=None, specific_heat=None, density=None, no_mass=False, thermal_resistance=None):
|
|
||||||
self._thickness = thickness
|
|
||||||
self._conductivity = conductivity
|
|
||||||
self._specific_heat = specific_heat
|
|
||||||
self._density = density
|
|
||||||
self._solar_absorptance = solar_absorptance
|
|
||||||
self._thermal_absorptance = thermal_absorptance
|
|
||||||
self._visible_absorptance = visible_absorptance
|
|
||||||
self._no_mass = no_mass
|
|
||||||
self._name = name
|
|
||||||
self._thermal_resistance = thermal_resistance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thickness(self):
|
|
||||||
return self._thickness
|
|
||||||
|
|
||||||
@property
|
|
||||||
def conductivity(self):
|
|
||||||
return self._conductivity
|
|
||||||
|
|
||||||
@property
|
|
||||||
def specific_heat(self):
|
|
||||||
return self._specific_heat
|
|
||||||
|
|
||||||
@property
|
|
||||||
def density(self):
|
|
||||||
return self._density
|
|
||||||
|
|
||||||
@property
|
|
||||||
def solar_absorptance(self):
|
|
||||||
return self._solar_absorptance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_absorptance(self):
|
|
||||||
return self._thermal_absorptance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def visible_absorptance(self):
|
|
||||||
return self._visible_absorptance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def no_mass(self) -> bool:
|
|
||||||
return self._no_mass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_resistance(self):
|
|
||||||
return self._thermal_resistance
|
|
|
@ -1,95 +0,0 @@
|
||||||
"""
|
|
||||||
NrelThermalBoundaryArchetype stores thermal boundaries information, complementing the NrelBuildingArchetype class
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from imports.construction.data_classes.nrel_layer_archetype import NrelLayerArchetype
|
|
||||||
from imports.construction.data_classes.nrel_thermal_opening_archetype import NrelThermalOpeningArchetype
|
|
||||||
|
|
||||||
|
|
||||||
class NrelThermalBoundaryArchetype:
|
|
||||||
"""
|
|
||||||
NrelThermalBoundaryArchetype class
|
|
||||||
"""
|
|
||||||
def __init__(self, boundary_type, window_ratio, construction_name, layers, thermal_opening,
|
|
||||||
outside_solar_absorptance=None, outside_thermal_absorptance=None, outside_visible_absorptance=None,
|
|
||||||
overall_u_value=None):
|
|
||||||
self._boundary_type = boundary_type
|
|
||||||
self._outside_solar_absorptance = outside_solar_absorptance
|
|
||||||
self._outside_thermal_absorptance = outside_thermal_absorptance
|
|
||||||
self._outside_visible_absorptance = outside_visible_absorptance
|
|
||||||
self._window_ratio = window_ratio
|
|
||||||
self._construction_name = construction_name
|
|
||||||
self._overall_u_value = overall_u_value
|
|
||||||
self._layers = layers
|
|
||||||
self._thermal_opening = thermal_opening
|
|
||||||
|
|
||||||
@property
|
|
||||||
def boundary_type(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._boundary_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def outside_solar_absorptance(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._outside_solar_absorptance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def outside_thermal_absorptance(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._outside_thermal_absorptance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def outside_visible_absorptance(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._outside_visible_absorptance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def window_ratio(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._window_ratio
|
|
||||||
|
|
||||||
@property
|
|
||||||
def construction_name(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._construction_name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def layers(self) -> List[NrelLayerArchetype]:
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._layers
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thermal_opening(self) -> NrelThermalOpeningArchetype:
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._thermal_opening
|
|
||||||
|
|
||||||
@property
|
|
||||||
def overall_u_value(self):
|
|
||||||
return self._overall_u_value
|
|
|
@ -1,54 +0,0 @@
|
||||||
"""
|
|
||||||
NrelThermalOpeningArchetype stores thermal openings information, complementing the NrelBuildingArchetype class
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NrelThermalOpeningArchetype:
|
|
||||||
"""
|
|
||||||
NrelThermalOpeningArchetype class
|
|
||||||
"""
|
|
||||||
def __init__(self, conductivity=None, frame_ratio=None, g_value=None, thickness=None,
|
|
||||||
back_side_solar_transmittance_at_normal_incidence=None,
|
|
||||||
front_side_solar_transmittance_at_normal_incidence=None, shgc=None, overall_u_value=None):
|
|
||||||
self._conductivity = conductivity
|
|
||||||
self._frame_ratio = frame_ratio
|
|
||||||
self._g_value = g_value
|
|
||||||
self._thickness = thickness
|
|
||||||
self._back_side_solar_transmittance_at_normal_incidence = back_side_solar_transmittance_at_normal_incidence
|
|
||||||
self._front_side_solar_transmittance_at_normal_incidence = front_side_solar_transmittance_at_normal_incidence
|
|
||||||
self._shgc = shgc
|
|
||||||
self._overall_u_value = overall_u_value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def conductivity(self):
|
|
||||||
return self._conductivity
|
|
||||||
|
|
||||||
@property
|
|
||||||
def frame_ratio(self):
|
|
||||||
return self._frame_ratio
|
|
||||||
|
|
||||||
@property
|
|
||||||
def g_value(self):
|
|
||||||
return self._g_value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thickness(self):
|
|
||||||
return self._thickness
|
|
||||||
|
|
||||||
@property
|
|
||||||
def back_side_solar_transmittance_at_normal_incidence(self):
|
|
||||||
return self._back_side_solar_transmittance_at_normal_incidence
|
|
||||||
|
|
||||||
@property
|
|
||||||
def front_side_solar_transmittance_at_normal_incidence(self):
|
|
||||||
return self._front_side_solar_transmittance_at_normal_incidence
|
|
||||||
|
|
||||||
@property
|
|
||||||
def shgc(self):
|
|
||||||
return self._shgc
|
|
||||||
|
|
||||||
@property
|
|
||||||
def overall_u_value(self):
|
|
||||||
return self._overall_u_value
|
|
|
@ -1,152 +0,0 @@
|
||||||
"""
|
|
||||||
Construction helper
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from helpers import constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class ConstructionHelper:
|
|
||||||
"""
|
|
||||||
Construction helper
|
|
||||||
"""
|
|
||||||
# NREL
|
|
||||||
function_to_nrel = {
|
|
||||||
cte.RESIDENTIAL: 'residential',
|
|
||||||
cte.SFH: 'single family house',
|
|
||||||
cte.MFH: 'multifamily house',
|
|
||||||
cte.HOTEL: 'hotel',
|
|
||||||
cte.HOSPITAL: 'hospital',
|
|
||||||
cte.OUTPATIENT: 'outpatient',
|
|
||||||
cte.COMMERCIAL: 'commercial',
|
|
||||||
cte.STRIP_MALL: 'strip mall',
|
|
||||||
cte.WAREHOUSE: 'warehouse',
|
|
||||||
cte.PRIMARY_SCHOOL: 'primary school',
|
|
||||||
cte.SECONDARY_SCHOOL: 'secondary school',
|
|
||||||
cte.OFFICE: 'office',
|
|
||||||
cte.LARGE_OFFICE: 'large office'
|
|
||||||
}
|
|
||||||
nrel_function_default_value = 'residential'
|
|
||||||
nrel_standards = {
|
|
||||||
'ASHRAE Std189': 1,
|
|
||||||
'ASHRAE 90.1_2004': 2
|
|
||||||
}
|
|
||||||
reference_city_to_nrel_climate_zone = {
|
|
||||||
'Miami': 'ASHRAE_2004:1A',
|
|
||||||
'Houston': 'ASHRAE_2004:2A',
|
|
||||||
'Phoenix': 'ASHRAE_2004:2B',
|
|
||||||
'Atlanta': 'ASHRAE_2004:3A',
|
|
||||||
'Los Angeles': 'ASHRAE_2004:3B',
|
|
||||||
'Las Vegas': 'ASHRAE_2004:3B',
|
|
||||||
'San Francisco': 'ASHRAE_2004:3C',
|
|
||||||
'Baltimore': 'ASHRAE_2004:4A',
|
|
||||||
'Albuquerque': 'ASHRAE_2004:4B',
|
|
||||||
'Seattle': 'ASHRAE_2004:4C',
|
|
||||||
'Chicago': 'ASHRAE_2004:5A',
|
|
||||||
'Boulder': 'ASHRAE_2004:5B',
|
|
||||||
'Minneapolis': 'ASHRAE_2004:6A',
|
|
||||||
'Helena': 'ASHRAE_2004:6B',
|
|
||||||
'Duluth': 'ASHRAE_2004:7A',
|
|
||||||
'Fairbanks': 'ASHRAE_2004:8A'
|
|
||||||
}
|
|
||||||
nrel_window_types = [cte.WINDOW, cte.DOOR, cte.SKYLIGHT]
|
|
||||||
nrel_construction_types = {
|
|
||||||
cte.WALL: 'exterior wall',
|
|
||||||
cte.INTERIOR_WALL: 'interior wall',
|
|
||||||
cte.GROUND_WALL: 'ground wall',
|
|
||||||
cte.GROUND: 'exterior slab',
|
|
||||||
cte.ATTIC_FLOOR: 'attic floor',
|
|
||||||
cte.INTERIOR_SLAB: 'interior slab',
|
|
||||||
cte.ROOF: 'roof'
|
|
||||||
}
|
|
||||||
|
|
||||||
# NRCAN
|
|
||||||
function_to_nrcan = {
|
|
||||||
cte.RESIDENTIAL: 'residential',
|
|
||||||
cte.SFH: 'single family house',
|
|
||||||
cte.MFH: 'multifamily house',
|
|
||||||
cte.HOTEL: 'hotel',
|
|
||||||
cte.HOSPITAL: 'hospital',
|
|
||||||
cte.OUTPATIENT: 'outpatient',
|
|
||||||
cte.COMMERCIAL: 'commercial',
|
|
||||||
cte.STRIP_MALL: 'strip mall',
|
|
||||||
cte.WAREHOUSE: 'warehouse',
|
|
||||||
cte.PRIMARY_SCHOOL: 'primary school',
|
|
||||||
cte.SECONDARY_SCHOOL: 'secondary school',
|
|
||||||
cte.OFFICE: 'office',
|
|
||||||
cte.LARGE_OFFICE: 'large office',
|
|
||||||
cte.OFFICE_WORKSHOP: 'residential'
|
|
||||||
}
|
|
||||||
nrcan_function_default_value = 'residential'
|
|
||||||
nrcan_window_types = [cte.WINDOW]
|
|
||||||
nrcan_construction_types = {
|
|
||||||
cte.WALL: 'wall',
|
|
||||||
cte.GROUND_WALL: 'basement_wall',
|
|
||||||
cte.GROUND: 'floor',
|
|
||||||
cte.ATTIC_FLOOR: 'attic floor',
|
|
||||||
cte.INTERIOR_SLAB: 'floor',
|
|
||||||
cte.ROOF: 'roof'
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def nrel_from_function(function):
|
|
||||||
"""
|
|
||||||
Get NREL function from the given internal function key
|
|
||||||
:param function: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return ConstructionHelper.function_to_nrel[function]
|
|
||||||
except KeyError:
|
|
||||||
sys.stderr.write('Error: keyword not found. Returned default NREL function "residential"\n')
|
|
||||||
return ConstructionHelper.nrel_function_default_value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def yoc_to_nrel_standard(year_of_construction):
|
|
||||||
"""
|
|
||||||
Year of construction to NREL standard
|
|
||||||
:param year_of_construction: int
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
if int(year_of_construction) < 2009:
|
|
||||||
standard = 'ASHRAE 90.1_2004'
|
|
||||||
else:
|
|
||||||
standard = 'ASHRAE 189.1_2009'
|
|
||||||
return standard
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def city_to_reference_city(city):
|
|
||||||
"""
|
|
||||||
City name to reference city
|
|
||||||
:param city: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
# ToDo: Dummy function that needs to be implemented
|
|
||||||
reference_city = 'Baltimore'
|
|
||||||
if city is not None:
|
|
||||||
reference_city = 'Baltimore'
|
|
||||||
return reference_city
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def city_to_nrel_climate_zone(city):
|
|
||||||
"""
|
|
||||||
City name to NREL climate zone
|
|
||||||
:param city: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
reference_city = ConstructionHelper.city_to_reference_city(city)
|
|
||||||
return ConstructionHelper.reference_city_to_nrel_climate_zone[reference_city]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def nrcan_from_function(function):
|
|
||||||
"""
|
|
||||||
Get NREL function from the given internal function key
|
|
||||||
:param function: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return ConstructionHelper.function_to_nrcan[function]
|
|
||||||
except KeyError:
|
|
||||||
sys.stderr.write('Error: keyword not found. Returned default NRCAN function "residential"\n')
|
|
||||||
return ConstructionHelper.nrcan_function_default_value
|
|
|
@ -1,157 +0,0 @@
|
||||||
"""
|
|
||||||
Storeys generation helper
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import math
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from helpers import constants as cte
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
from city_model_structure.attributes.point import Point
|
|
||||||
from city_model_structure.building_demand.storey import Storey
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
|
|
||||||
|
|
||||||
class StoreysGeneration:
|
|
||||||
"""
|
|
||||||
StoreysGeneration
|
|
||||||
"""
|
|
||||||
def __init__(self, building, divide_in_storeys=False):
|
|
||||||
self._building = building
|
|
||||||
self._divide_in_storeys = divide_in_storeys
|
|
||||||
self._storeys = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def storeys(self) -> [Storey]:
|
|
||||||
"""
|
|
||||||
subsections of building trimesh by storey in case of no interiors defined
|
|
||||||
:return: [Storey]
|
|
||||||
"""
|
|
||||||
number_of_storeys, height = self._calculate_number_storeys_and_height(self._building.average_storey_height,
|
|
||||||
self._building.eave_height,
|
|
||||||
self._building.storeys_above_ground)
|
|
||||||
number_of_storeys = 1
|
|
||||||
if not self._divide_in_storeys or number_of_storeys == 1:
|
|
||||||
return [Storey('storey_0', self._building.surfaces, [None, None], self._building.volume)]
|
|
||||||
|
|
||||||
if number_of_storeys == 0:
|
|
||||||
raise Exception('Number of storeys cannot be 0')
|
|
||||||
|
|
||||||
storeys = []
|
|
||||||
surfaces_child_last_storey = []
|
|
||||||
rest_surfaces = []
|
|
||||||
|
|
||||||
total_volume = 0
|
|
||||||
for i in range(0, number_of_storeys - 1):
|
|
||||||
name = 'storey_' + str(i)
|
|
||||||
surfaces_child = []
|
|
||||||
if i == 0:
|
|
||||||
neighbours = [None, 'storey_1']
|
|
||||||
for surface in self._building.surfaces:
|
|
||||||
if surface.type == cte.GROUND:
|
|
||||||
surfaces_child.append(surface)
|
|
||||||
else:
|
|
||||||
rest_surfaces.append(surface)
|
|
||||||
else:
|
|
||||||
neighbours = ['storey_' + str(i - 1), 'storey_' + str(i + 1)]
|
|
||||||
height_division = self._building.lower_corner[2] + height * (i + 1)
|
|
||||||
intersections = []
|
|
||||||
for surface in rest_surfaces:
|
|
||||||
if surface.type == cte.ROOF:
|
|
||||||
if height_division >= surface.upper_corner[2] > height_division - height:
|
|
||||||
surfaces_child.append(surface)
|
|
||||||
else:
|
|
||||||
surfaces_child_last_storey.append(surface)
|
|
||||||
else:
|
|
||||||
surface_child, rest_surface, intersection = surface.divide(height_division)
|
|
||||||
surfaces_child.append(surface_child)
|
|
||||||
intersections.extend(intersection)
|
|
||||||
if i == number_of_storeys - 2:
|
|
||||||
surfaces_child_last_storey.append(rest_surface)
|
|
||||||
points = []
|
|
||||||
for intersection in intersections:
|
|
||||||
points.append(intersection[1])
|
|
||||||
coordinates = self._intersections_to_coordinates(intersections)
|
|
||||||
polygon = Polygon(coordinates)
|
|
||||||
ceiling = Surface(polygon, polygon, surface_type=cte.INTERIOR_SLAB)
|
|
||||||
surfaces_child.append(ceiling)
|
|
||||||
volume = ceiling.area_above_ground * height
|
|
||||||
total_volume += volume
|
|
||||||
storeys.append(Storey(name, surfaces_child, neighbours, volume))
|
|
||||||
name = 'storey_' + str(number_of_storeys - 1)
|
|
||||||
neighbours = ['storey_' + str(number_of_storeys - 2), None]
|
|
||||||
volume = self._building.volume - total_volume
|
|
||||||
if volume < 0:
|
|
||||||
raise Exception('Error in storeys creation, volume of last storey cannot be lower that 0')
|
|
||||||
storeys.append(Storey(name, surfaces_child_last_storey, neighbours, volume))
|
|
||||||
return storeys
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _calculate_number_storeys_and_height(average_storey_height, eave_height, storeys_above_ground):
|
|
||||||
if average_storey_height is None:
|
|
||||||
if storeys_above_ground is None or storeys_above_ground <= 0:
|
|
||||||
sys.stderr.write('Warning: not enough information to divide building into storeys, '
|
|
||||||
'either number of storeys or average storey height must be provided.\n')
|
|
||||||
return 0, 0
|
|
||||||
number_of_storeys = int(storeys_above_ground)
|
|
||||||
height = eave_height / number_of_storeys
|
|
||||||
else:
|
|
||||||
height = float(average_storey_height)
|
|
||||||
if storeys_above_ground is not None:
|
|
||||||
number_of_storeys = int(storeys_above_ground)
|
|
||||||
else:
|
|
||||||
number_of_storeys = math.floor(float(eave_height) / height) + 1
|
|
||||||
last_storey_height = float(eave_height) - height*(number_of_storeys-1)
|
|
||||||
if last_storey_height < 0.3*height:
|
|
||||||
number_of_storeys -= 1
|
|
||||||
return number_of_storeys, height
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _intersections_to_coordinates(edges_list):
|
|
||||||
# todo: this method is not robust, the while loop needs to be improved
|
|
||||||
points = [Point(edges_list[0][0]), Point(edges_list[0][1])]
|
|
||||||
found_edges = []
|
|
||||||
j = 0
|
|
||||||
while j < len(points)-1:
|
|
||||||
for i in range(1, len(edges_list)):
|
|
||||||
if i not in found_edges:
|
|
||||||
point_2 = points[len(points) - 1]
|
|
||||||
point_1 = Point(edges_list[i][0])
|
|
||||||
found = False
|
|
||||||
if point_1.distance_to_point(point_2) <= 1e-10:
|
|
||||||
points.append(Point(edges_list[i][1]))
|
|
||||||
found_edges.append(i)
|
|
||||||
found = True
|
|
||||||
if not found:
|
|
||||||
point_1 = Point(edges_list[i][1])
|
|
||||||
if point_1.distance_to_point(point_2) <= 1e-10:
|
|
||||||
points.append(Point(edges_list[i][0]))
|
|
||||||
found_edges.append(i)
|
|
||||||
j += 1
|
|
||||||
|
|
||||||
points.remove(points[len(points)-1])
|
|
||||||
array_points = []
|
|
||||||
for point in points:
|
|
||||||
array_points.append(point.coordinates)
|
|
||||||
return np.array(array_points)
|
|
||||||
|
|
||||||
def assign_thermal_zones_delimited_by_thermal_boundaries(self):
|
|
||||||
"""
|
|
||||||
During storeys creation, the thermal boundaries and zones are also created.
|
|
||||||
It is afterwards needed to define which zones are delimited by each thermal boundary
|
|
||||||
"""
|
|
||||||
for storey in self._building.storeys:
|
|
||||||
for thermal_boundary in storey.thermal_boundaries:
|
|
||||||
if thermal_boundary.surface.type != cte.INTERIOR_WALL or thermal_boundary.surface.type != cte.INTERIOR_SLAB:
|
|
||||||
# external thermal boundary -> only one thermal zone
|
|
||||||
thermal_zones = [storey.thermal_zone]
|
|
||||||
else:
|
|
||||||
# internal thermal boundary -> two thermal zones
|
|
||||||
grad = np.rad2deg(thermal_boundary.surface.inclination)
|
|
||||||
if grad >= 170:
|
|
||||||
thermal_zones = [storey.thermal_zone, storey.neighbours[0]]
|
|
||||||
else:
|
|
||||||
thermal_zones = [storey.neighbours[1], storey.thermal_zone]
|
|
||||||
thermal_boundary.thermal_zones = thermal_zones
|
|
|
@ -1,184 +0,0 @@
|
||||||
"""
|
|
||||||
Nrel-based interface, it reads format defined within the CERC team based on NREL structure
|
|
||||||
and enriches the city with archetypes and materials
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import xmltodict
|
|
||||||
|
|
||||||
from imports.construction.data_classes.nrel_building_achetype import NrelBuildingArchetype as nba
|
|
||||||
from imports.construction.data_classes.nrel_thermal_boundary_archetype import NrelThermalBoundaryArchetype as ntba
|
|
||||||
from imports.construction.data_classes.nrel_thermal_opening_archetype import NrelThermalOpeningArchetype as ntoa
|
|
||||||
from imports.construction.data_classes.nrel_layer_archetype import NrelLayerArchetype as nla
|
|
||||||
|
|
||||||
|
|
||||||
class NrelPhysicsInterface:
|
|
||||||
"""
|
|
||||||
NrelPhysicsInterface abstract class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, base_path, constructions_file='us_constructions.xml',
|
|
||||||
archetypes_file='us_archetypes.xml'):
|
|
||||||
self._building_archetypes = []
|
|
||||||
# load construction Library, CERC-NREL format
|
|
||||||
path = str(base_path / constructions_file)
|
|
||||||
with open(path) as xml:
|
|
||||||
self._library = xmltodict.parse(xml.read(), force_list='layer')
|
|
||||||
|
|
||||||
# load archetypes Library, CERC-NREL format
|
|
||||||
path = str(base_path / archetypes_file)
|
|
||||||
with open(path) as xml:
|
|
||||||
self._archetypes = xmltodict.parse(xml.read(), force_list='layer')
|
|
||||||
|
|
||||||
for archetype in self._archetypes['archetypes']['archetype']:
|
|
||||||
archetype_keys = {}
|
|
||||||
for key, value in archetype.items():
|
|
||||||
if key[0] == '@':
|
|
||||||
archetype_keys[key] = value
|
|
||||||
average_storey_height = archetype['average_storey_height']['#text']
|
|
||||||
units = archetype['average_storey_height']['@units']
|
|
||||||
if units != 'm':
|
|
||||||
raise Exception(f'average storey height units = {units}, expected meters')
|
|
||||||
storeys_above_ground = archetype['number_of_storeys']['#text']
|
|
||||||
effective_thermal_capacity = float(archetype['thermal_capacity']['#text']) * 1000
|
|
||||||
units = archetype['thermal_capacity']['@units']
|
|
||||||
if units != 'kJ/K m2':
|
|
||||||
raise Exception(f'thermal capacity units = {units}, expected kJ/K m2')
|
|
||||||
additional_thermal_bridge_u_value = archetype['extra_loses_due_to_thermal_bridges']['#text']
|
|
||||||
units = archetype['extra_loses_due_to_thermal_bridges']['@units']
|
|
||||||
if units != 'W/K m2':
|
|
||||||
raise Exception(f'extra loses due to thermal bridges units = {units}, expected W/K m2')
|
|
||||||
indirectly_heated_area_ratio = archetype['indirect_heated_ratio']['#text']
|
|
||||||
# todo: check how infiltration rate is used in the model
|
|
||||||
infiltration_rate_system_off = archetype['infiltration_rate_for_ventilation_system_off']['#text']
|
|
||||||
units = archetype['infiltration_rate_for_ventilation_system_off']['@units']
|
|
||||||
if units != 'ACH':
|
|
||||||
raise Exception(f'infiltration rate for ventilation when system off units = {units}, expected ACH')
|
|
||||||
infiltration_rate_system_on = archetype['infiltration_rate_for_ventilation_system_on']['#text']
|
|
||||||
units = archetype['infiltration_rate_for_ventilation_system_on']['@units']
|
|
||||||
if units != 'ACH':
|
|
||||||
raise Exception(f'infiltration rate for ventilation when system on units = {units}, expected ACH')
|
|
||||||
|
|
||||||
thermal_boundary_archetypes = []
|
|
||||||
for construction in archetype['constructions']['construction']:
|
|
||||||
construction_type = construction['@type']
|
|
||||||
construction_id = construction['@id']
|
|
||||||
|
|
||||||
c_lib = self._search_construction_type('construction', construction_id)
|
|
||||||
construction_name = c_lib['@name']
|
|
||||||
layers = []
|
|
||||||
if 'layers' in c_lib:
|
|
||||||
for current_layer in c_lib['layers']['layer']:
|
|
||||||
material_lib = self._search_construction_type('material', current_layer['material'])
|
|
||||||
name = material_lib['@name']
|
|
||||||
solar_absorptance = material_lib['solar_absorptance']['#text']
|
|
||||||
thermal_absorptance = material_lib['thermal_absorptance']['#text']
|
|
||||||
visible_absorptance = material_lib['visible_absorptance']['#text']
|
|
||||||
no_mass = 'no_mass' in material_lib
|
|
||||||
if no_mass:
|
|
||||||
thermal_resistance = material_lib['thermal_resistance']['#text']
|
|
||||||
units = material_lib['thermal_resistance']['@units']
|
|
||||||
if units != 'm2 K/W':
|
|
||||||
raise Exception(f'thermal resistance units = {units}, expected m2 K/W')
|
|
||||||
layer = nla(name, solar_absorptance, thermal_absorptance, visible_absorptance, no_mass=no_mass,
|
|
||||||
thermal_resistance=thermal_resistance)
|
|
||||||
else:
|
|
||||||
thickness = current_layer['thickness']['#text']
|
|
||||||
units = current_layer['thickness']['@units']
|
|
||||||
if units != 'm':
|
|
||||||
raise Exception(f'thickness units = {units}, expected m')
|
|
||||||
conductivity = material_lib['conductivity']['#text']
|
|
||||||
units = material_lib['conductivity']['@units']
|
|
||||||
if units != 'W/m K':
|
|
||||||
raise Exception(f'conductivity units = {units}, expected W/m K')
|
|
||||||
specific_heat = material_lib['specific_heat']['#text']
|
|
||||||
units = material_lib['specific_heat']['@units']
|
|
||||||
if units != 'J/kg K':
|
|
||||||
raise Exception(f'specific_heat units = {units}, expected J/kg K')
|
|
||||||
density = material_lib['density']['#text']
|
|
||||||
units = material_lib['density']['@units']
|
|
||||||
if units != 'kg/m3':
|
|
||||||
raise Exception(f'density units = {units}, expected kg/m3')
|
|
||||||
layer = nla(name, solar_absorptance, thermal_absorptance, visible_absorptance, thickness=thickness,
|
|
||||||
conductivity=conductivity, specific_heat=specific_heat, density=density)
|
|
||||||
layers.append(layer)
|
|
||||||
|
|
||||||
thermal_opening = None
|
|
||||||
window_ratio = 0
|
|
||||||
if 'window' in construction and construction['window'] is not None:
|
|
||||||
window_ratio = construction['window_ratio']['#text']
|
|
||||||
w_lib = self._search_construction_type('window', construction['window'])
|
|
||||||
frame_ratio = w_lib['frame_ratio']['#text']
|
|
||||||
if 'conductivity' in w_lib:
|
|
||||||
conductivity = w_lib['conductivity']['#text']
|
|
||||||
units = w_lib['conductivity']['@units']
|
|
||||||
if units != 'W/m K':
|
|
||||||
raise Exception(f'conductivity units = {units}, expected W/m K')
|
|
||||||
thickness = w_lib['thickness']['#text']
|
|
||||||
units = w_lib['thickness']['@units']
|
|
||||||
if units != 'm':
|
|
||||||
raise Exception(f'thickness units = {units}, expected m')
|
|
||||||
g_value = w_lib['solar_transmittance_at_normal_incidence']['#text']
|
|
||||||
back_side_solar_transmittance_at_normal_incidence = \
|
|
||||||
w_lib['back_side_solar_transmittance_at_normal_incidence']['#text']
|
|
||||||
front_side_solar_transmittance_at_normal_incidence = \
|
|
||||||
w_lib['front_side_solar_transmittance_at_normal_incidence']['#text']
|
|
||||||
thermal_opening = ntoa(conductivity=conductivity, frame_ratio=frame_ratio, g_value=g_value,
|
|
||||||
thickness=thickness, back_side_solar_transmittance_at_normal_incidence=
|
|
||||||
back_side_solar_transmittance_at_normal_incidence,
|
|
||||||
front_side_solar_transmittance_at_normal_incidence=
|
|
||||||
front_side_solar_transmittance_at_normal_incidence)
|
|
||||||
else:
|
|
||||||
overall_u_value = w_lib['overall_u_value']['#text']
|
|
||||||
units = w_lib['overall_u_value']['@units']
|
|
||||||
if units != 'W/m2 K':
|
|
||||||
raise Exception(f'overall U-value units = {units}, expected W/m2 K')
|
|
||||||
g_value = w_lib['g_value']
|
|
||||||
thermal_opening = ntoa(frame_ratio=frame_ratio, g_value=g_value, overall_u_value=overall_u_value)
|
|
||||||
|
|
||||||
if 'outside_thermal_absorptance' in c_lib:
|
|
||||||
outside_solar_absorptance = c_lib['outside_solar_absorptance']['#text']
|
|
||||||
outside_thermal_absorptance = c_lib['outside_thermal_absorptance']['#text']
|
|
||||||
outside_visible_absorptance = c_lib['outside_visible_absorptance']['#text']
|
|
||||||
thermal_boundary_archetype = ntba(construction_type, window_ratio, construction_name, layers, thermal_opening,
|
|
||||||
outside_solar_absorptance, outside_thermal_absorptance,
|
|
||||||
outside_visible_absorptance)
|
|
||||||
else:
|
|
||||||
if 'overall_u_value' in c_lib:
|
|
||||||
overall_u_value = c_lib['overall_u_value']['#text']
|
|
||||||
units = c_lib['overall_u_value']['@units']
|
|
||||||
if units != 'W/m2 K':
|
|
||||||
raise Exception(f'overall U-value units = {units}, expected W/m2 K')
|
|
||||||
outside_solar_absorptance = c_lib['outside_solar_absorptance']['#text']
|
|
||||||
thermal_boundary_archetype = ntba(construction_type, window_ratio, construction_name, layers,
|
|
||||||
thermal_opening, outside_solar_absorptance=outside_solar_absorptance,
|
|
||||||
overall_u_value=overall_u_value)
|
|
||||||
else:
|
|
||||||
thermal_boundary_archetype = ntba(construction_type, window_ratio, construction_name, layers,
|
|
||||||
thermal_opening)
|
|
||||||
|
|
||||||
thermal_boundary_archetypes.append(thermal_boundary_archetype)
|
|
||||||
building_archetype = nba(archetype_keys, average_storey_height, storeys_above_ground,
|
|
||||||
effective_thermal_capacity, additional_thermal_bridge_u_value,
|
|
||||||
indirectly_heated_area_ratio, infiltration_rate_system_off,
|
|
||||||
infiltration_rate_system_on, thermal_boundary_archetypes)
|
|
||||||
self._building_archetypes.append(building_archetype)
|
|
||||||
|
|
||||||
def _search_construction_type(self, construction_type, construction_id):
|
|
||||||
for c_lib in self._library['library'][construction_type + 's'][construction_type]:
|
|
||||||
if construction_id == c_lib['@id']:
|
|
||||||
return c_lib
|
|
||||||
raise Exception('Archetype definition contains elements that does not exist in the library')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _search_construction_in_archetype(building_archetype, construction_type):
|
|
||||||
for thermal_boundary in building_archetype.thermal_boundary_archetypes:
|
|
||||||
if thermal_boundary.boundary_type == construction_type:
|
|
||||||
return thermal_boundary
|
|
||||||
raise Exception('Construction type not found')
|
|
||||||
|
|
||||||
def enrich_buildings(self):
|
|
||||||
"""
|
|
||||||
Raise not implemented error
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
|
@ -1,107 +0,0 @@
|
||||||
"""
|
|
||||||
UsPhysicsParameters import the construction and material information for US
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from imports.construction.nrel_physics_interface import NrelPhysicsInterface
|
|
||||||
from imports.construction.helpers.construction_helper import ConstructionHelper
|
|
||||||
from city_model_structure.building_demand.layer import Layer
|
|
||||||
from city_model_structure.building_demand.material import Material
|
|
||||||
from imports.construction.helpers.storeys_generation import StoreysGeneration
|
|
||||||
|
|
||||||
|
|
||||||
class UsPhysicsParameters(NrelPhysicsInterface):
|
|
||||||
"""
|
|
||||||
UsPhysicsParameters class
|
|
||||||
"""
|
|
||||||
def __init__(self, city, base_path):
|
|
||||||
self._city = city
|
|
||||||
self._climate_zone = ConstructionHelper.city_to_nrel_climate_zone(city.name)
|
|
||||||
super().__init__(base_path, 'us_constructions.xml', 'us_archetypes.xml')
|
|
||||||
|
|
||||||
def enrich_buildings(self):
|
|
||||||
"""
|
|
||||||
Returns the city with the construction parameters assigned to the buildings
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
city = self._city
|
|
||||||
# it is assumed that all buildings have the same archetypes' keys
|
|
||||||
for building in city.buildings:
|
|
||||||
building_type = ConstructionHelper.nrel_from_function(building.function)
|
|
||||||
if building_type is None:
|
|
||||||
return
|
|
||||||
archetype = self._search_archetype(building_type,
|
|
||||||
ConstructionHelper.yoc_to_nrel_standard(building.year_of_construction),
|
|
||||||
self._climate_zone)
|
|
||||||
if archetype is None:
|
|
||||||
sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} '
|
|
||||||
f'and building year of construction: {building.year_of_construction}\n')
|
|
||||||
continue
|
|
||||||
|
|
||||||
self._create_storeys(building, archetype)
|
|
||||||
self._assign_values(building, archetype)
|
|
||||||
|
|
||||||
def _search_archetype(self, building_type, standard, climate_zone):
|
|
||||||
for building_archetype in self._building_archetypes:
|
|
||||||
a_yc = str(building_archetype.archetype_keys['@reference_standard'])
|
|
||||||
a_bt = str(building_archetype.archetype_keys['@building_type'])
|
|
||||||
a_cz = str(building_archetype.archetype_keys['@climate_zone'])
|
|
||||||
if (a_yc == str(standard)) and (a_bt == str(building_type)) and (a_cz == str(climate_zone)):
|
|
||||||
return building_archetype
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _assign_values(self, building, archetype):
|
|
||||||
for thermal_zone in building.thermal_zones:
|
|
||||||
thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value
|
|
||||||
thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity
|
|
||||||
thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio
|
|
||||||
thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on
|
|
||||||
thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off
|
|
||||||
for thermal_boundary in thermal_zone.thermal_boundaries:
|
|
||||||
construction_type = ConstructionHelper.nrel_construction_types[thermal_boundary.type]
|
|
||||||
thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type)
|
|
||||||
if thermal_boundary_archetype.outside_solar_absorptance is not None:
|
|
||||||
thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance
|
|
||||||
thermal_boundary.outside_thermal_absorptance = thermal_boundary_archetype.outside_thermal_absorptance
|
|
||||||
thermal_boundary.outside_visible_absorptance = thermal_boundary_archetype.outside_visible_absorptance
|
|
||||||
thermal_boundary.construction_name = thermal_boundary_archetype.construction_name
|
|
||||||
thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio
|
|
||||||
thermal_boundary.layers = []
|
|
||||||
for layer_archetype in thermal_boundary_archetype.layers:
|
|
||||||
layer = Layer()
|
|
||||||
layer.thickness = layer_archetype.thickness
|
|
||||||
material = Material()
|
|
||||||
material.name = layer_archetype.name
|
|
||||||
material.no_mass = layer_archetype.no_mass
|
|
||||||
material.density = layer_archetype.density
|
|
||||||
material.conductivity = layer_archetype.conductivity
|
|
||||||
material.specific_heat = layer_archetype.specific_heat
|
|
||||||
material.solar_absorptance = layer_archetype.solar_absorptance
|
|
||||||
material.thermal_absorptance = layer_archetype.thermal_absorptance
|
|
||||||
material.visible_absorptance = layer_archetype.visible_absorptance
|
|
||||||
material.thermal_resistance = layer_archetype.thermal_resistance
|
|
||||||
layer.material = material
|
|
||||||
thermal_boundary.layers.append(layer)
|
|
||||||
for thermal_opening in thermal_boundary.thermal_openings:
|
|
||||||
if thermal_boundary_archetype.thermal_opening is not None:
|
|
||||||
thermal_opening_archetype = thermal_boundary_archetype.thermal_opening
|
|
||||||
thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio
|
|
||||||
thermal_opening.g_value = thermal_opening_archetype.g_value
|
|
||||||
thermal_opening.conductivity = thermal_opening_archetype.conductivity
|
|
||||||
thermal_opening.thickness = thermal_opening_archetype.thickness
|
|
||||||
thermal_opening.back_side_solar_transmittance_at_normal_incidence = \
|
|
||||||
thermal_opening_archetype.back_side_solar_transmittance_at_normal_incidence
|
|
||||||
thermal_opening.front_side_solar_transmittance_at_normal_incidence = \
|
|
||||||
thermal_opening_archetype.front_side_solar_transmittance_at_normal_incidence
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_storeys(building, archetype):
|
|
||||||
building.average_storey_height = archetype.average_storey_height
|
|
||||||
building.storeys_above_ground = archetype.storeys_above_ground
|
|
||||||
storeys_generation = StoreysGeneration(building)
|
|
||||||
storeys = storeys_generation.storeys
|
|
||||||
building.storeys = storeys
|
|
||||||
storeys_generation.assign_thermal_zones_delimited_by_thermal_boundaries()
|
|
|
@ -1,38 +0,0 @@
|
||||||
"""
|
|
||||||
ConstructionFactory (before PhysicsFactory) retrieve the specific construction module for the given region
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
from imports.construction.us_physics_parameters import UsPhysicsParameters
|
|
||||||
from imports.construction.ca_physics_parameters import CaPhysicsParameters
|
|
||||||
|
|
||||||
|
|
||||||
class ConstructionFactory:
|
|
||||||
"""
|
|
||||||
PhysicsFactor class
|
|
||||||
"""
|
|
||||||
def __init__(self, handler, city, base_path=Path(Path(__file__).parent.parent / 'data/construction')):
|
|
||||||
self._handler = '_' + handler.lower().replace(' ', '_')
|
|
||||||
self._city = city
|
|
||||||
self._base_path = base_path
|
|
||||||
|
|
||||||
def _nrel(self):
|
|
||||||
UsPhysicsParameters(self._city, self._base_path).enrich_buildings()
|
|
||||||
|
|
||||||
def _nrcan(self):
|
|
||||||
CaPhysicsParameters(self._city, self._base_path).enrich_buildings()
|
|
||||||
|
|
||||||
def enrich(self):
|
|
||||||
"""
|
|
||||||
Enrich the city with the construction information
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
getattr(self, self._handler, lambda: None)()
|
|
||||||
|
|
||||||
def _enrich_debug(self):
|
|
||||||
"""
|
|
||||||
Enrich the city with the construction information
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._nrel()
|
|
|
@ -1,112 +0,0 @@
|
||||||
"""
|
|
||||||
CityGml module parses citygml_classes files and import the geometry into the city model structure
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
import numpy as np
|
|
||||||
import xmltodict
|
|
||||||
from city_model_structure.city import City
|
|
||||||
from city_model_structure.building import Building
|
|
||||||
from city_model_structure.parts_consisting_building import PartsConsistingBuilding
|
|
||||||
from helpers.geometry_helper import GeometryHelper
|
|
||||||
from imports.geometry.citygml_classes.citygml_lod2 import CityGmlLod2
|
|
||||||
from imports.geometry.citygml_classes.citygml_lod1 import CityGmlLod1
|
|
||||||
|
|
||||||
|
|
||||||
class CityGml:
|
|
||||||
"""
|
|
||||||
CityGml class
|
|
||||||
"""
|
|
||||||
def __init__(self, path):
|
|
||||||
self._city = None
|
|
||||||
self._lod1_tags = ['lod1Solid', 'lod1MultiSurface']
|
|
||||||
self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve']
|
|
||||||
with open(path) as gml:
|
|
||||||
# Clean the namespaces is an important task to prevent wrong ns:field due poor citygml_classes implementations
|
|
||||||
fl = ('cityObjectMember', 'curveMember', 'boundedBy', 'surfaceMember', 'consistsOfBuildingPart')
|
|
||||||
self._gml = xmltodict.parse(gml.read(), process_namespaces=True, xml_attribs=True, namespaces={
|
|
||||||
'http://www.opengis.net/gml': None,
|
|
||||||
'http://www.opengis.net/citygml/1.0': None,
|
|
||||||
'http://www.opengis.net/citygml/building/1.0': None,
|
|
||||||
'http://schemas.opengis.net/citygml/building/1.0/building.xsd': None,
|
|
||||||
'http://www.opengis.net/citygml/appearance/1.0': None,
|
|
||||||
'http://schemas.opengis.net/citygml/appearance/1.0/appearance.xsd': None,
|
|
||||||
'http://www.opengis.net/citygml/relief/1.0': None,
|
|
||||||
'http://schemas.opengis.net/citygml/relief/1.0/relief.xsd': None,
|
|
||||||
'http://www.opengis.net/citygml/generics/1.0': None,
|
|
||||||
'http://www.w3.org/2001/XMLSchema-instance': None,
|
|
||||||
'urn:oasis:names:tc:ciq:xsdschema:xAL:2.0': None,
|
|
||||||
'http://www.w3.org/1999/xlink': None,
|
|
||||||
'http://www.opengis.net/citygml/relief/2.0': None,
|
|
||||||
'http://www.opengis.net/citygml/building/2.0': None,
|
|
||||||
'http://www.opengis.net/citygml/building/2.0 http://schemas.opengis.net/citygml/building/2.0/building.xsd '
|
|
||||||
'http://www.opengis.net/citygml/relief/2.0 http://schemas.opengis.net/citygml/relief/2.0/relief.xsd" '
|
|
||||||
'xmlns="http://www.opengis.net/citygml/2.0': None,
|
|
||||||
'http://www.opengis.net/citygml/2.0': None
|
|
||||||
}, force_list=fl)
|
|
||||||
|
|
||||||
self._city_objects = None
|
|
||||||
self._geometry = GeometryHelper()
|
|
||||||
|
|
||||||
for bound in self._gml['CityModel']['boundedBy']:
|
|
||||||
envelope = bound['Envelope']
|
|
||||||
if '#text' in envelope['lowerCorner']:
|
|
||||||
self._lower_corner = np.fromstring(envelope['lowerCorner']['#text'], dtype=float, sep=' ')
|
|
||||||
self._upper_corner = np.fromstring(envelope['upperCorner']['#text'], dtype=float, sep=' ')
|
|
||||||
else:
|
|
||||||
self._lower_corner = np.fromstring(envelope['lowerCorner'], dtype=float, sep=' ')
|
|
||||||
self._upper_corner = np.fromstring(envelope['upperCorner'], dtype=float, sep=' ')
|
|
||||||
if '@srsName' in envelope:
|
|
||||||
self._srs_name = envelope['@srsName']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def content(self):
|
|
||||||
"""
|
|
||||||
CityGml raw content
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._gml
|
|
||||||
|
|
||||||
def _create_building(self, city_object):
|
|
||||||
name = city_object['@id']
|
|
||||||
function = None
|
|
||||||
year_of_construction = None
|
|
||||||
if 'yearOfConstruction' in city_object:
|
|
||||||
year_of_construction = city_object['yearOfConstruction']
|
|
||||||
if 'function' in city_object:
|
|
||||||
function = city_object['function']
|
|
||||||
if any(key in city_object for key in self._lod1_tags):
|
|
||||||
lod = 1
|
|
||||||
surfaces = CityGmlLod1(city_object).surfaces
|
|
||||||
elif any(key in city_object for key in self._lod2_tags):
|
|
||||||
lod = 2
|
|
||||||
surfaces = CityGmlLod2(city_object).surfaces
|
|
||||||
else:
|
|
||||||
raise NotImplementedError("Not supported level of detail")
|
|
||||||
return Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, [])
|
|
||||||
|
|
||||||
def _create_parts_consisting_building(self, city_object):
|
|
||||||
name = city_object['@id']
|
|
||||||
building_parts = []
|
|
||||||
for part in city_object['consistsOfBuildingPart']:
|
|
||||||
building = self._create_building(part['BuildingPart'])
|
|
||||||
self._city.add_city_object(building)
|
|
||||||
building_parts.append(building)
|
|
||||||
return PartsConsistingBuilding(name, building_parts)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city(self) -> City:
|
|
||||||
"""
|
|
||||||
City model structure enriched with the geometry information
|
|
||||||
:return: City
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self._city is None:
|
|
||||||
self._city = City(self._lower_corner, self._upper_corner, self._srs_name)
|
|
||||||
for o in self._gml['CityModel']['cityObjectMember']:
|
|
||||||
city_object = o['Building']
|
|
||||||
if 'consistsOfBuildingPart' in city_object:
|
|
||||||
self._city.add_city_objects_cluster(self._create_parts_consisting_building(city_object))
|
|
||||||
else:
|
|
||||||
self._city.add_city_object(self._create_building(city_object))
|
|
||||||
return self._city
|
|
|
@ -1,48 +0,0 @@
|
||||||
"""
|
|
||||||
CityGmlBase module abstract class to template the different level of details
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from abc import ABC
|
|
||||||
import numpy as np
|
|
||||||
from imports.geometry.helpers.geometry_helper import GeometryHelper
|
|
||||||
|
|
||||||
|
|
||||||
class CityGmlBase(ABC):
|
|
||||||
"""
|
|
||||||
CityGmlBase class inherited by the specific level of detail classes.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._surfaces = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def surfaces(self):
|
|
||||||
"""
|
|
||||||
parsed surfaces
|
|
||||||
"""
|
|
||||||
return self._surfaces
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _remove_last_point(points):
|
|
||||||
array = points.split(' ')
|
|
||||||
res = " "
|
|
||||||
return res.join(array[0:len(array) - 3])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _solid_points(coordinates) -> np.ndarray:
|
|
||||||
solid_points = np.fromstring(coordinates, dtype=float, sep=' ')
|
|
||||||
solid_points = GeometryHelper.to_points_matrix(solid_points)
|
|
||||||
return solid_points
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _solid(cls, o):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _multi_surface(cls, o):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _multi_curve(cls, o):
|
|
||||||
raise NotImplementedError
|
|
|
@ -1,55 +0,0 @@
|
||||||
"""
|
|
||||||
CityGmlLod1 module parses citygml_classes files with level of detail 1 and import the geometry into the city model
|
|
||||||
structure
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from imports.geometry.citygml_classes.citygml_base import CityGmlBase
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
|
|
||||||
|
|
||||||
class CityGmlLod1(CityGmlBase):
|
|
||||||
"""
|
|
||||||
CityGmlLod1 class to parse level of detail 1 city gml files
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _multi_curve(cls, o):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, o):
|
|
||||||
super().__init__()
|
|
||||||
self._o = o
|
|
||||||
self._surfaces = self._identify(self._o)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _identify(cls, o):
|
|
||||||
if 'lod1Solid' in o:
|
|
||||||
return cls._solid(o)
|
|
||||||
if 'lod1MultiSurface' in o:
|
|
||||||
return cls._multi_surface(o)
|
|
||||||
raise NotImplementedError(o)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _solid(cls, o):
|
|
||||||
try:
|
|
||||||
solid_points = [
|
|
||||||
CityGmlBase._solid_points(CityGmlBase._remove_last_point(s['Polygon']['exterior']['LinearRing']['posList']
|
|
||||||
['#text']))
|
|
||||||
for s in o['lod1Solid']['Solid']['exterior']['CompositeSurface']['surfaceMember']]
|
|
||||||
except TypeError:
|
|
||||||
solid_points = [
|
|
||||||
CityGmlBase._solid_points(CityGmlBase._remove_last_point(s['Polygon']['exterior']['LinearRing']['posList']))
|
|
||||||
for s in o['lod1Solid']['Solid']['exterior']['CompositeSurface']['surfaceMember']]
|
|
||||||
|
|
||||||
return [Surface(Polygon(sp), Polygon(sp)) for sp in solid_points]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _multi_surface(cls, o):
|
|
||||||
print("lod1_multi")
|
|
||||||
solid_points = [CityGmlBase._solid_points(CityGmlBase._remove_last_point(s['Polygon']['exterior']['LinearRing']
|
|
||||||
['posList']))
|
|
||||||
for s in o['Building']['lod1MultiSurface']['MultiSurface']['surfaceMember']]
|
|
||||||
return [Surface(Polygon(sp), Polygon(sp)) for sp in solid_points]
|
|
|
@ -1,69 +0,0 @@
|
||||||
"""
|
|
||||||
CityGmlLod1 module parses citygml_classes files with level of detail 1 and import the geometry into the city model
|
|
||||||
structure
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from imports.geometry.citygml_classes.citygml_base import CityGmlBase
|
|
||||||
from imports.geometry.helpers.geometry_helper import GeometryHelper
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
|
|
||||||
|
|
||||||
class CityGmlLod2(CityGmlBase):
|
|
||||||
"""
|
|
||||||
CityGmlLod1 class to parse level of detail 1 city gml files
|
|
||||||
"""
|
|
||||||
def __init__(self, o):
|
|
||||||
super().__init__()
|
|
||||||
self._o = o
|
|
||||||
self._surfaces = self._identify(self._o)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _identify(cls, o):
|
|
||||||
if 'lod2Solid' in o:
|
|
||||||
return cls._solid(o)
|
|
||||||
if 'lod2MultiSurface' in o:
|
|
||||||
return cls._multi_surface(o)
|
|
||||||
if 'lod2MultiCurve' in o:
|
|
||||||
return cls._multi_curve(o)
|
|
||||||
raise NotImplementedError(o)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _surface_encoding(surfaces):
|
|
||||||
if 'lod2MultiSurface' in surfaces:
|
|
||||||
return 'lod2MultiSurface', 'MultiSurface'
|
|
||||||
raise NotImplementedError('unknown surface type')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _solid(cls, o):
|
|
||||||
surfaces = []
|
|
||||||
for b in o["boundedBy"]:
|
|
||||||
try:
|
|
||||||
surface_type = next(iter(b))
|
|
||||||
except TypeError:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
surface_encoding, surface_subtype = cls._surface_encoding(b[surface_type])
|
|
||||||
except NotImplementedError:
|
|
||||||
|
|
||||||
continue
|
|
||||||
for member in b[surface_type][surface_encoding][surface_subtype]['surfaceMember']:
|
|
||||||
if '@srsDimension' in member['Polygon']['exterior']['LinearRing']['posList']:
|
|
||||||
gml_points = member['Polygon']['exterior']['LinearRing']['posList']["#text"]
|
|
||||||
else:
|
|
||||||
gml_points = member['Polygon']['exterior']['LinearRing']['posList']
|
|
||||||
sp = cls._solid_points(cls._remove_last_point(gml_points))
|
|
||||||
p = Polygon(sp)
|
|
||||||
surface = Surface(p, p, surface_type=GeometryHelper.gml_surface_to_libs(surface_type))
|
|
||||||
surfaces.append(surface)
|
|
||||||
return surfaces
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _multi_curve(cls, o):
|
|
||||||
raise NotImplementedError('multi curve')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _multi_surface(cls, o):
|
|
||||||
return cls._solid(o)
|
|
|
@ -1,322 +0,0 @@
|
||||||
"""
|
|
||||||
Geometry helper
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import helpers.constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class GeometryHelper:
|
|
||||||
"""
|
|
||||||
Geometry helper
|
|
||||||
"""
|
|
||||||
# function
|
|
||||||
pluto_to_function = {
|
|
||||||
'A0': 'single family house',
|
|
||||||
'A1': 'single family house',
|
|
||||||
'A2': 'single family house',
|
|
||||||
'A3': 'single family house',
|
|
||||||
'A4': 'single family house',
|
|
||||||
'A5': 'single family house',
|
|
||||||
'A6': 'single family house',
|
|
||||||
'A7': 'single family house',
|
|
||||||
'A8': 'single family house',
|
|
||||||
'A9': 'single family house',
|
|
||||||
'B1': 'multifamily house',
|
|
||||||
'B2': 'multifamily house',
|
|
||||||
'B3': 'multifamily house',
|
|
||||||
'B9': 'multifamily house',
|
|
||||||
'C0': 'residential',
|
|
||||||
'C1': 'residential',
|
|
||||||
'C2': 'residential',
|
|
||||||
'C3': 'residential',
|
|
||||||
'C4': 'residential',
|
|
||||||
'C5': 'residential',
|
|
||||||
'C6': 'residential',
|
|
||||||
'C7': 'residential',
|
|
||||||
'C8': 'residential',
|
|
||||||
'C9': 'residential',
|
|
||||||
'D0': 'residential',
|
|
||||||
'D1': 'residential',
|
|
||||||
'D2': 'residential',
|
|
||||||
'D3': 'residential',
|
|
||||||
'D4': 'residential',
|
|
||||||
'D5': 'residential',
|
|
||||||
'D6': 'residential',
|
|
||||||
'D7': 'residential',
|
|
||||||
'D8': 'residential',
|
|
||||||
'D9': 'residential',
|
|
||||||
'E1': 'warehouse',
|
|
||||||
'E3': 'warehouse',
|
|
||||||
'E4': 'warehouse',
|
|
||||||
'E5': 'warehouse',
|
|
||||||
'E7': 'warehouse',
|
|
||||||
'E9': 'warehouse',
|
|
||||||
'F1': 'warehouse',
|
|
||||||
'F2': 'warehouse',
|
|
||||||
'F4': 'warehouse',
|
|
||||||
'F5': 'warehouse',
|
|
||||||
'F8': 'warehouse',
|
|
||||||
'F9': 'warehouse',
|
|
||||||
'G0': 'office',
|
|
||||||
'G1': 'office',
|
|
||||||
'G2': 'office',
|
|
||||||
'G3': 'office',
|
|
||||||
'G4': 'office',
|
|
||||||
'G5': 'office',
|
|
||||||
'G6': 'office',
|
|
||||||
'G7': 'office',
|
|
||||||
'G8': 'office',
|
|
||||||
'G9': 'office',
|
|
||||||
'H1': 'hotel',
|
|
||||||
'H2': 'hotel',
|
|
||||||
'H3': 'hotel',
|
|
||||||
'H4': 'hotel',
|
|
||||||
'H5': 'hotel',
|
|
||||||
'H6': 'hotel',
|
|
||||||
'H7': 'hotel',
|
|
||||||
'H8': 'hotel',
|
|
||||||
'H9': 'hotel',
|
|
||||||
'HB': 'hotel',
|
|
||||||
'HH': 'hotel',
|
|
||||||
'HR': 'hotel',
|
|
||||||
'HS': 'hotel',
|
|
||||||
'I1': 'hospital',
|
|
||||||
'I2': 'outpatient',
|
|
||||||
'I3': 'outpatient',
|
|
||||||
'I4': 'residential',
|
|
||||||
'I5': 'outpatient',
|
|
||||||
'I6': 'outpatient',
|
|
||||||
'I7': 'outpatient',
|
|
||||||
'I9': 'outpatient',
|
|
||||||
'J1': 'large office',
|
|
||||||
'J2': 'large office',
|
|
||||||
'J3': 'large office',
|
|
||||||
'J4': 'large office',
|
|
||||||
'J5': 'large office',
|
|
||||||
'J6': 'large office',
|
|
||||||
'J7': 'large office',
|
|
||||||
'J8': 'large office',
|
|
||||||
'J9': 'large office',
|
|
||||||
'K1': 'strip mall',
|
|
||||||
'K2': 'strip mall',
|
|
||||||
'K3': 'strip mall',
|
|
||||||
'K4': 'residential',
|
|
||||||
'K5': 'restaurant',
|
|
||||||
'K6': 'commercial',
|
|
||||||
'K7': 'commercial',
|
|
||||||
'K8': 'commercial',
|
|
||||||
'K9': 'commercial',
|
|
||||||
'L1': 'residential',
|
|
||||||
'L2': 'residential',
|
|
||||||
'L3': 'residential',
|
|
||||||
'L8': 'residential',
|
|
||||||
'L9': 'residential',
|
|
||||||
'M1': 'large office',
|
|
||||||
'M2': 'large office',
|
|
||||||
'M3': 'large office',
|
|
||||||
'M4': 'large office',
|
|
||||||
'M9': 'large office',
|
|
||||||
'N1': 'residential',
|
|
||||||
'N2': 'residential',
|
|
||||||
'N3': 'residential',
|
|
||||||
'N4': 'residential',
|
|
||||||
'N9': 'residential',
|
|
||||||
'O1': 'office',
|
|
||||||
'O2': 'office',
|
|
||||||
'O3': 'office',
|
|
||||||
'O4': 'office',
|
|
||||||
'O5': 'office',
|
|
||||||
'O6': 'office',
|
|
||||||
'O7': 'office',
|
|
||||||
'O8': 'office',
|
|
||||||
'O9': 'office',
|
|
||||||
'P1': 'large office',
|
|
||||||
'P2': 'hotel',
|
|
||||||
'P3': 'office',
|
|
||||||
'P4': 'office',
|
|
||||||
'P5': 'office',
|
|
||||||
'P6': 'office',
|
|
||||||
'P7': 'large office',
|
|
||||||
'P8': 'large office',
|
|
||||||
'P9': 'office',
|
|
||||||
'Q0': 'office',
|
|
||||||
'Q1': 'office',
|
|
||||||
'Q2': 'office',
|
|
||||||
'Q3': 'office',
|
|
||||||
'Q4': 'office',
|
|
||||||
'Q5': 'office',
|
|
||||||
'Q6': 'office',
|
|
||||||
'Q7': 'office',
|
|
||||||
'Q8': 'office',
|
|
||||||
'Q9': 'office',
|
|
||||||
'R0': 'residential',
|
|
||||||
'R1': 'residential',
|
|
||||||
'R2': 'residential',
|
|
||||||
'R3': 'residential',
|
|
||||||
'R4': 'residential',
|
|
||||||
'R5': 'residential',
|
|
||||||
'R6': 'residential',
|
|
||||||
'R7': 'residential',
|
|
||||||
'R8': 'residential',
|
|
||||||
'R9': 'residential',
|
|
||||||
'RA': 'residential',
|
|
||||||
'RB': 'residential',
|
|
||||||
'RC': 'residential',
|
|
||||||
'RD': 'residential',
|
|
||||||
'RG': 'residential',
|
|
||||||
'RH': 'residential',
|
|
||||||
'RI': 'residential',
|
|
||||||
'RK': 'residential',
|
|
||||||
'RM': 'residential',
|
|
||||||
'RR': 'residential',
|
|
||||||
'RS': 'residential',
|
|
||||||
'RW': 'residential',
|
|
||||||
'RX': 'residential',
|
|
||||||
'RZ': 'residential',
|
|
||||||
'S0': 'residential',
|
|
||||||
'S1': 'residential',
|
|
||||||
'S2': 'residential',
|
|
||||||
'S3': 'residential',
|
|
||||||
'S4': 'residential',
|
|
||||||
'S5': 'residential',
|
|
||||||
'S9': 'residential',
|
|
||||||
'T1': 'na',
|
|
||||||
'T2': 'na',
|
|
||||||
'T9': 'na',
|
|
||||||
'U0': 'warehouse',
|
|
||||||
'U1': 'warehouse',
|
|
||||||
'U2': 'warehouse',
|
|
||||||
'U3': 'warehouse',
|
|
||||||
'U4': 'warehouse',
|
|
||||||
'U5': 'warehouse',
|
|
||||||
'U6': 'warehouse',
|
|
||||||
'U7': 'warehouse',
|
|
||||||
'U8': 'warehouse',
|
|
||||||
'U9': 'warehouse',
|
|
||||||
'V0': 'na',
|
|
||||||
'V1': 'na',
|
|
||||||
'V2': 'na',
|
|
||||||
'V3': 'na',
|
|
||||||
'V4': 'na',
|
|
||||||
'V5': 'na',
|
|
||||||
'V6': 'na',
|
|
||||||
'V7': 'na',
|
|
||||||
'V8': 'na',
|
|
||||||
'V9': 'na',
|
|
||||||
'W1': 'primary school',
|
|
||||||
'W2': 'primary school',
|
|
||||||
'W3': 'secondary school',
|
|
||||||
'W4': 'secondary school',
|
|
||||||
'W5': 'secondary school',
|
|
||||||
'W6': 'secondary school',
|
|
||||||
'W7': 'secondary school',
|
|
||||||
'W8': 'primary school',
|
|
||||||
'W9': 'secondary school',
|
|
||||||
'Y1': 'large office',
|
|
||||||
'Y2': 'large office',
|
|
||||||
'Y3': 'large office',
|
|
||||||
'Y4': 'large office',
|
|
||||||
'Y5': 'large office',
|
|
||||||
'Y6': 'large office',
|
|
||||||
'Y7': 'large office',
|
|
||||||
'Y8': 'large office',
|
|
||||||
'Y9': 'large office',
|
|
||||||
'Z0': 'na',
|
|
||||||
'Z1': 'large office',
|
|
||||||
'Z2': 'na',
|
|
||||||
'Z3': 'na',
|
|
||||||
'Z4': 'na',
|
|
||||||
'Z5': 'na',
|
|
||||||
'Z6': 'na',
|
|
||||||
'Z7': 'na',
|
|
||||||
'Z8': 'na',
|
|
||||||
'Z9': 'na'
|
|
||||||
}
|
|
||||||
hft_to_function = {
|
|
||||||
'residential': cte.RESIDENTIAL,
|
|
||||||
'single family house': cte.SFH,
|
|
||||||
'multifamily house': cte.MFH,
|
|
||||||
'hotel': cte.HOTEL,
|
|
||||||
'hospital': cte.HOSPITAL,
|
|
||||||
'outpatient': cte.OUTPATIENT,
|
|
||||||
'commercial': cte.COMMERCIAL,
|
|
||||||
'strip mall': cte.STRIP_MALL,
|
|
||||||
'warehouse': cte.WAREHOUSE,
|
|
||||||
'primary school': cte.PRIMARY_SCHOOL,
|
|
||||||
'secondary school': cte.SECONDARY_SCHOOL,
|
|
||||||
'office': cte.OFFICE,
|
|
||||||
'large office': cte.LARGE_OFFICE
|
|
||||||
}
|
|
||||||
|
|
||||||
# usage
|
|
||||||
function_to_usage = {
|
|
||||||
'full service restaurant': 'restaurant',
|
|
||||||
'highrise apartment': cte.RESIDENTIAL,
|
|
||||||
'hospital': 'health care',
|
|
||||||
'large hotel': 'hotel',
|
|
||||||
'large office': 'office and administration',
|
|
||||||
'medium office': 'office and administration',
|
|
||||||
'midrise apartment': cte.RESIDENTIAL,
|
|
||||||
'outpatient healthcare': 'health care',
|
|
||||||
'primary school': 'education',
|
|
||||||
'quick service restaurant': 'restaurant',
|
|
||||||
'secondary school': 'education',
|
|
||||||
'small hotel': 'hotel',
|
|
||||||
'small office': 'office and administration',
|
|
||||||
'stand alone retail': 'retail',
|
|
||||||
'strip mall': 'hall',
|
|
||||||
'supermarket': 'retail',
|
|
||||||
'warehouse': 'industry',
|
|
||||||
'residential': cte.RESIDENTIAL
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def function_from_hft(building_hft_function):
|
|
||||||
"""
|
|
||||||
Get internal function from the given HfT function
|
|
||||||
:param building_hft_function: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return GeometryHelper.hft_to_function[building_hft_function]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def function_from_pluto(building_pluto_function):
|
|
||||||
"""
|
|
||||||
Get internal function from the given pluto function
|
|
||||||
:param building_pluto_function: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return GeometryHelper.pluto_to_function[building_pluto_function]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def usage_from_function(building_function):
|
|
||||||
"""
|
|
||||||
Get the internal usage for the given internal building function
|
|
||||||
:param building_function: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return GeometryHelper.function_to_usage[building_function]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def to_points_matrix(points):
|
|
||||||
"""
|
|
||||||
Transform a point vector into a point matrix
|
|
||||||
:param points: [x, y, z, x, y, z ...]
|
|
||||||
:return: [[x,y,z],[x,y,z]...]
|
|
||||||
"""
|
|
||||||
rows = points.size // 3
|
|
||||||
points = points.reshape(rows, 3)
|
|
||||||
return points
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def gml_surface_to_libs(surface):
|
|
||||||
"""
|
|
||||||
Transform citygml surface names into libs names
|
|
||||||
"""
|
|
||||||
if surface == 'WallSurface':
|
|
||||||
return 'Wall'
|
|
||||||
if surface == 'GroundSurface':
|
|
||||||
return 'Ground'
|
|
||||||
return 'Roof'
|
|
|
@ -1,81 +0,0 @@
|
||||||
"""
|
|
||||||
Obj module parses obj files and import the geometry into the city model structure
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import trimesh.exchange.load
|
|
||||||
from trimesh import Scene
|
|
||||||
import trimesh.geometry
|
|
||||||
from city_model_structure.city import City
|
|
||||||
from city_model_structure.building import Building
|
|
||||||
from city_model_structure.building_demand.surface import Surface
|
|
||||||
from city_model_structure.attributes.polygon import Polygon
|
|
||||||
|
|
||||||
|
|
||||||
class Obj:
|
|
||||||
"""
|
|
||||||
Obj class
|
|
||||||
"""
|
|
||||||
def __init__(self, path):
|
|
||||||
self._city = None
|
|
||||||
with open(path, 'r') as file:
|
|
||||||
self._scene = trimesh.exchange.load.load(file, file_type='obj', force='scene')
|
|
||||||
self._corners = self._scene.bounds_corners
|
|
||||||
_bound_corner_min = []
|
|
||||||
_bound_corner_max = []
|
|
||||||
for corner in self._corners:
|
|
||||||
if _bound_corner_min is None:
|
|
||||||
_bound_corner_min = corner
|
|
||||||
elif _bound_corner_max is None:
|
|
||||||
_bound_corner_max = corner
|
|
||||||
else:
|
|
||||||
_bound_corner_min[0] = min(_bound_corner_min[0], corner[0])
|
|
||||||
_bound_corner_min[1] = min(_bound_corner_min[1], corner[1])
|
|
||||||
_bound_corner_min[2] = min(_bound_corner_min[2], corner[2])
|
|
||||||
_bound_corner_max[0] = max(_bound_corner_max[0], corner[0])
|
|
||||||
_bound_corner_max[1] = max(_bound_corner_max[1], corner[1])
|
|
||||||
_bound_corner_max[2] = max(_bound_corner_max[2], corner[2])
|
|
||||||
self._lower_corner = _bound_corner_min
|
|
||||||
self._upper_corner = _bound_corner_max
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scene(self) -> Scene:
|
|
||||||
"""
|
|
||||||
Obj scene
|
|
||||||
"""
|
|
||||||
return self._scene
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city(self) -> City:
|
|
||||||
"""
|
|
||||||
Create a city out of an obj file
|
|
||||||
"""
|
|
||||||
if self._city is None:
|
|
||||||
# todo: refactor this method to clearly choose the obj type
|
|
||||||
# todo: where do we get this information from?
|
|
||||||
srs_name = 'EPSG:26911'
|
|
||||||
|
|
||||||
self._city = City(self._lower_corner, self._upper_corner, srs_name)
|
|
||||||
scene = self.scene.geometry
|
|
||||||
keys = scene.keys()
|
|
||||||
for key in keys:
|
|
||||||
name = key
|
|
||||||
# todo: where do we get this information from?
|
|
||||||
lod = 1
|
|
||||||
year_of_construction = 0
|
|
||||||
function = ''
|
|
||||||
|
|
||||||
obj = scene[key]
|
|
||||||
surfaces = []
|
|
||||||
for face in obj.faces:
|
|
||||||
# todo: review for obj with windows
|
|
||||||
points = []
|
|
||||||
for vertex_index in face:
|
|
||||||
points.append(obj.vertices[vertex_index])
|
|
||||||
solid_polygon = Polygon(points)
|
|
||||||
perimeter_polygon = solid_polygon
|
|
||||||
surface = Surface(solid_polygon, perimeter_polygon)
|
|
||||||
surfaces.append(surface)
|
|
||||||
building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner)
|
|
||||||
self._city.add_city_object(building)
|
|
||||||
return self._city
|
|
|
@ -1,54 +0,0 @@
|
||||||
"""
|
|
||||||
OsmSubway module parses osm files and import the metro location into the city model structure
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import xmltodict
|
|
||||||
from pyproj import Transformer
|
|
||||||
from city_model_structure.city import City
|
|
||||||
from city_model_structure.subway_entrance import SubwayEntrance
|
|
||||||
|
|
||||||
|
|
||||||
class OsmSubway:
|
|
||||||
"""
|
|
||||||
Open street map subway
|
|
||||||
"""
|
|
||||||
def __init__(self, path):
|
|
||||||
self._city = None
|
|
||||||
self._subway_entrances = []
|
|
||||||
with open(path) as osm:
|
|
||||||
self._osm = xmltodict.parse(osm.read(), force_list='tag')
|
|
||||||
for node in self._osm['osm']['node']:
|
|
||||||
if 'tag' not in node:
|
|
||||||
continue
|
|
||||||
for tag in node['tag']:
|
|
||||||
if '@v' not in tag:
|
|
||||||
continue
|
|
||||||
if tag['@v'] == 'subway_entrance':
|
|
||||||
subway_entrance = SubwayEntrance(node['@id'], node['@lat'], node['@lon'])
|
|
||||||
self._subway_entrances.append(subway_entrance)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city(self) -> City:
|
|
||||||
"""
|
|
||||||
City subway entrances
|
|
||||||
"""
|
|
||||||
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857")
|
|
||||||
lower_corner = [sys.float_info.max, sys.float_info.max, 0]
|
|
||||||
upper_corner = [sys.float_info.min, sys.float_info.min, 0]
|
|
||||||
x = 0
|
|
||||||
y = 1
|
|
||||||
for subway_entrance in self._subway_entrances:
|
|
||||||
coordinate = transformer.transform(subway_entrance.longitude, subway_entrance.latitude)
|
|
||||||
if coordinate[x] >= upper_corner[x]:
|
|
||||||
upper_corner[x] = coordinate[x]
|
|
||||||
if coordinate[y] >= upper_corner[y]:
|
|
||||||
upper_corner[y] = coordinate[y]
|
|
||||||
if coordinate[x] < lower_corner[x]:
|
|
||||||
lower_corner[x] = coordinate[x]
|
|
||||||
if coordinate[y] < lower_corner[y]:
|
|
||||||
lower_corner[y] = coordinate[y]
|
|
||||||
|
|
||||||
return City(lower_corner, upper_corner, 'unknown')
|
|
|
@ -1,39 +0,0 @@
|
||||||
"""
|
|
||||||
GeometryFactory retrieve the specific geometric module to load the given format
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from city_model_structure.city import City
|
|
||||||
from imports.geometry.citygml import CityGml
|
|
||||||
from imports.geometry.obj import Obj
|
|
||||||
from imports.geometry.osm_subway import OsmSubway
|
|
||||||
|
|
||||||
|
|
||||||
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 _obj(self):
|
|
||||||
return Obj(self._path).city
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _osm_subway(self):
|
|
||||||
return OsmSubway(self._path).city
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city(self) -> City:
|
|
||||||
"""
|
|
||||||
Load the city model structure from a geometry source
|
|
||||||
:return: City
|
|
||||||
"""
|
|
||||||
return getattr(self, self._file_type, lambda: None)
|
|
|
@ -1,42 +0,0 @@
|
||||||
"""
|
|
||||||
Schedules retrieve the specific usage schedules module for the given standard
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import pandas as pd
|
|
||||||
from imports.schedules.helpers.schedules_helper import SchedulesHelper
|
|
||||||
|
|
||||||
|
|
||||||
class ComnetSchedules:
|
|
||||||
"""
|
|
||||||
Commet based schedules
|
|
||||||
"""
|
|
||||||
def __init__(self, city, base_path):
|
|
||||||
self._city = city
|
|
||||||
self._comnet_schedules_path = base_path / 'comnet_archetypes.xlsx'
|
|
||||||
xls = pd.ExcelFile(self._comnet_schedules_path)
|
|
||||||
for building in city.buildings:
|
|
||||||
schedules = dict()
|
|
||||||
for usage_zone in building.usage_zones:
|
|
||||||
usage_schedules = pd.read_excel(xls,
|
|
||||||
sheet_name=SchedulesHelper.comnet_from_usage(usage_zone.usage),
|
|
||||||
skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA")
|
|
||||||
# todo: should we save the data type? How?
|
|
||||||
number_of_schedule_types = 13
|
|
||||||
schedules_per_schedule_type = 3
|
|
||||||
day_types = dict({'week_day': 0, 'saturday': 1, 'sunday': 2})
|
|
||||||
for schedule_types in range(0, number_of_schedule_types):
|
|
||||||
data = pd.DataFrame()
|
|
||||||
columns_names = []
|
|
||||||
name = ''
|
|
||||||
for schedule_day in range(0, schedules_per_schedule_type):
|
|
||||||
row_cells = usage_schedules.iloc[schedules_per_schedule_type*schedule_types + schedule_day]
|
|
||||||
if schedule_day == day_types['week_day']:
|
|
||||||
name = row_cells[0]
|
|
||||||
columns_names.append(row_cells[2])
|
|
||||||
data1 = row_cells[schedules_per_schedule_type:]
|
|
||||||
data = pd.concat([data, data1], axis=1)
|
|
||||||
data.columns = columns_names
|
|
||||||
schedules[name] = data
|
|
||||||
usage_zone.schedules = schedules
|
|
|
@ -1,114 +0,0 @@
|
||||||
"""
|
|
||||||
MyClass module
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project
|
|
||||||
"""
|
|
||||||
import pandas as pd
|
|
||||||
import parseidf
|
|
||||||
|
|
||||||
|
|
||||||
class DoeIdf:
|
|
||||||
"""
|
|
||||||
This is a import factory to add Idf schedules into the data model
|
|
||||||
"""
|
|
||||||
idf_schedule_to_commet_schedule = {'BLDG_LIGHT_SCH': 'Lights',
|
|
||||||
'BLDG_OCC_SCH_wo_SB': 'Occupancy',
|
|
||||||
'BLDG_EQUIP_SCH': 'Equipment',
|
|
||||||
'ACTIVITY_SCH': 'Activity',
|
|
||||||
'INFIL_QUARTER_ON_SCH': 'Infiltration'}
|
|
||||||
|
|
||||||
_SCHEDULE_COMPACT_TYPE = 'SCHEDULE:COMPACT'
|
|
||||||
_SCHEDULE_TYPE_NAME = 1
|
|
||||||
|
|
||||||
def __init__(self, city, base_path):
|
|
||||||
self._hours = []
|
|
||||||
panda_hours = pd.timedelta_range(0, periods=24, freq='H')
|
|
||||||
for _, hour in enumerate(panda_hours):
|
|
||||||
self._hours.append(str(hour).replace('0 days ', '').replace(':00:00', ':00'))
|
|
||||||
self._city = city
|
|
||||||
self._idf_schedules_path = base_path / 'ASHRAE901_OfficeSmall_STD2019_Buffalo.idf'
|
|
||||||
with open(self._idf_schedules_path, 'r') as file:
|
|
||||||
idf = parseidf.parse(file.read())
|
|
||||||
self._load_schedule(idf, 'small_office')
|
|
||||||
self._load_schedule(idf, 'residential')
|
|
||||||
|
|
||||||
def _load_schedule(self, idf, building_usage):
|
|
||||||
schedules_day = {}
|
|
||||||
for compact_schedule in idf[self._SCHEDULE_COMPACT_TYPE]:
|
|
||||||
if compact_schedule[self._SCHEDULE_TYPE_NAME] in self.idf_schedule_to_commet_schedule:
|
|
||||||
schedule_type = self.idf_schedule_to_commet_schedule[compact_schedule[self._SCHEDULE_TYPE_NAME]]
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
days_index = []
|
|
||||||
days_schedules = []
|
|
||||||
for position, elements in enumerate(compact_schedule):
|
|
||||||
element_title = elements.title().replace('For: ', '')
|
|
||||||
if elements.title() != element_title:
|
|
||||||
days_index.append(position)
|
|
||||||
# store a cleaned version of the compact schedule
|
|
||||||
days_schedules.append(element_title)
|
|
||||||
days_index.append(len(days_schedules))
|
|
||||||
# create panda
|
|
||||||
data = {'WD': [], 'Sat': [], 'Sun': []}
|
|
||||||
rows = []
|
|
||||||
for j in range(1, 13):
|
|
||||||
rows.append(f'{j}am')
|
|
||||||
for j in range(1, 13):
|
|
||||||
rows.append(f'{j}pm')
|
|
||||||
for i, day_index in enumerate(days_index):
|
|
||||||
if day_index == len(days_schedules):
|
|
||||||
break
|
|
||||||
schedules_day[f'{days_schedules[day_index]}'] = []
|
|
||||||
hour_index = 0
|
|
||||||
for hours_values in range(day_index + 1, days_index[i + 1] - 1, 2):
|
|
||||||
# Create 24h sequence
|
|
||||||
for index, hour in enumerate(self._hours[hour_index:]):
|
|
||||||
hour_formatted = days_schedules[hours_values].replace("Until: ", "")
|
|
||||||
if len(hour_formatted) == 4:
|
|
||||||
hour_formatted = f'0{hour_formatted}'
|
|
||||||
if hour == hour_formatted:
|
|
||||||
hour_index += index
|
|
||||||
break
|
|
||||||
entry = days_schedules[hours_values + 1]
|
|
||||||
schedules_day[f'{days_schedules[day_index]}'].append(entry)
|
|
||||||
|
|
||||||
if 'Weekdays' in days_schedules[day_index]:
|
|
||||||
data['WD'] = []
|
|
||||||
for entry in schedules_day[f'{days_schedules[day_index]}']:
|
|
||||||
data['WD'].append(entry)
|
|
||||||
|
|
||||||
elif 'Alldays' in days_schedules[day_index]:
|
|
||||||
data['WD'] = []
|
|
||||||
data['Sat'] = []
|
|
||||||
data['Sun'] = []
|
|
||||||
for entry in schedules_day[f'{days_schedules[day_index]}']:
|
|
||||||
data['WD'].append(entry)
|
|
||||||
data['Sat'].append(entry)
|
|
||||||
data['Sun'].append(entry)
|
|
||||||
|
|
||||||
elif 'Weekends' in days_schedules[day_index]:
|
|
||||||
# Weekends schedule present so let's re set the values
|
|
||||||
data['Sat'] = []
|
|
||||||
data['Sun'] = []
|
|
||||||
for entry in schedules_day[f'{days_schedules[day_index]}']:
|
|
||||||
data['Sat'].append(entry)
|
|
||||||
data['Sun'].append(entry)
|
|
||||||
|
|
||||||
elif 'Saturday' in days_schedules[day_index]:
|
|
||||||
data['Sat'] = []
|
|
||||||
for entry in schedules_day[f'{days_schedules[day_index]}']:
|
|
||||||
data['Sat'].append(entry)
|
|
||||||
|
|
||||||
elif 'Sunday' in days_schedules[day_index]:
|
|
||||||
data['Sun'] = []
|
|
||||||
for entry in schedules_day[f'{days_schedules[day_index]}']:
|
|
||||||
data['Sun'].append(entry)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
df = pd.DataFrame(data, index=rows)
|
|
||||||
for building in self._city.buildings:
|
|
||||||
for usage_zone in building.usage_zones:
|
|
||||||
if usage_zone.usage == building_usage:
|
|
||||||
if usage_zone.schedules is None:
|
|
||||||
usage_zone.schedules = {}
|
|
||||||
usage_zone.schedules[schedule_type] = df
|
|
|
@ -1,39 +0,0 @@
|
||||||
"""
|
|
||||||
Schedules helper
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import helpers.constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class SchedulesHelper:
|
|
||||||
"""
|
|
||||||
Schedules helper
|
|
||||||
"""
|
|
||||||
usage_to_comnet = {
|
|
||||||
cte.RESIDENTIAL: 'C-12 Residential',
|
|
||||||
cte.INDUSTRY: 'C-10 Warehouse',
|
|
||||||
cte.OFFICE_ADMINISTRATION: 'C-5 Office',
|
|
||||||
cte.HOTEL: 'C-3 Hotel',
|
|
||||||
cte.HEALTH_CARE: 'C-2 Health',
|
|
||||||
cte.RETAIL: 'C-8 Retail',
|
|
||||||
cte.HALL: 'C-8 Retail',
|
|
||||||
cte.RESTAURANT: 'C-7 Restaurant',
|
|
||||||
cte.EDUCATION: 'C-9 School'
|
|
||||||
}
|
|
||||||
comnet_default_value = 'C-12 Residential'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def comnet_from_usage(usage):
|
|
||||||
"""
|
|
||||||
Get Comnet usage from the given internal usage key
|
|
||||||
:param usage: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return SchedulesHelper.usage_to_comnet[usage]
|
|
||||||
except KeyError:
|
|
||||||
sys.stderr.write('Error: keyword not found. Returned default Comnet schedules "residential"\n')
|
|
||||||
return SchedulesHelper.comnet_default_value
|
|
|
@ -1,37 +0,0 @@
|
||||||
"""
|
|
||||||
SchedulesFactory retrieve the specific schedules module for the given standard
|
|
||||||
This factory can only be called after calling the usage factory so the usage zones are created.
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from imports.schedules.comnet_schedules_parameters import ComnetSchedules
|
|
||||||
from imports.schedules.doe_idf import DoeIdf
|
|
||||||
|
|
||||||
|
|
||||||
class SchedulesFactory:
|
|
||||||
"""
|
|
||||||
SchedulesFactor class
|
|
||||||
"""
|
|
||||||
def __init__(self, handler, city, base_path=Path(Path(__file__).parent.parent / 'data/schedules')):
|
|
||||||
self._handler = '_' + handler.lower().replace(' ', '_')
|
|
||||||
self._city = city
|
|
||||||
self._base_path = base_path
|
|
||||||
for building in city.buildings:
|
|
||||||
if len(building.usage_zones) == 0:
|
|
||||||
raise Exception('It seems that the schedule factory is being called before the usage factory. '
|
|
||||||
'Please ensure that the usage factory is called first.')
|
|
||||||
|
|
||||||
def _comnet(self):
|
|
||||||
ComnetSchedules(self._city, self._base_path)
|
|
||||||
|
|
||||||
def _doe_idf(self):
|
|
||||||
DoeIdf(self._city, self._base_path)
|
|
||||||
|
|
||||||
def enrich(self):
|
|
||||||
"""
|
|
||||||
Enrich the city with the schedules information
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
getattr(self, self._handler, lambda: None)()
|
|
|
@ -1,35 +0,0 @@
|
||||||
"""
|
|
||||||
Concordia energy consumption
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
import pandas as pd
|
|
||||||
from imports.sensors.concordia_file_report import ConcordiaFileReport
|
|
||||||
from city_model_structure.iot.concordia_energy_sensor import ConcordiaEnergySensor
|
|
||||||
|
|
||||||
|
|
||||||
class ConcordiaEnergyConsumption(ConcordiaFileReport):
|
|
||||||
"""
|
|
||||||
Concordia energy consumption sensor class
|
|
||||||
"""
|
|
||||||
def __init__(self, city, end_point, base_path):
|
|
||||||
super().__init__(city, end_point, base_path, 'concordia_energy_db.json')
|
|
||||||
for city_object in city.city_objects:
|
|
||||||
self._assign_sensor_to_object(city_object)
|
|
||||||
|
|
||||||
def _assign_sensor_to_object(self, obj):
|
|
||||||
for i in range(len(self._city_object)):
|
|
||||||
if self._city_object[i] == obj.name and self._sensors[i] in self._sensor_point:
|
|
||||||
building_measures = [self._measures["Date time"], self._measures[self._sensor_point[self._sensors[i]]]]
|
|
||||||
building_headers = ["Date time", "Energy consumption"]
|
|
||||||
building_energy_consumption = pd.concat(building_measures, keys=building_headers, axis=1)
|
|
||||||
sensor = ConcordiaEnergySensor(self._sensors[i])
|
|
||||||
sensor_exist = False
|
|
||||||
for j in range(len(obj.sensors)):
|
|
||||||
if obj.sensors[j].name is sensor.name:
|
|
||||||
obj.sensors[j].add_period(building_energy_consumption)
|
|
||||||
sensor_exist = True
|
|
||||||
break
|
|
||||||
if not sensor_exist:
|
|
||||||
sensor.add_period(building_energy_consumption)
|
|
||||||
obj.sensors.append(sensor)
|
|
|
@ -1,79 +0,0 @@
|
||||||
"""
|
|
||||||
Concordia file report
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import io
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
|
|
||||||
class ConcordiaFileReport:
|
|
||||||
"""
|
|
||||||
Concordia file report for sensors base class
|
|
||||||
"""
|
|
||||||
def __init__(self, city, end_point, base_path, db_file):
|
|
||||||
self._city_object = []
|
|
||||||
self._city_objects_cluster = []
|
|
||||||
self._sensors = []
|
|
||||||
self._sensor_point = {}
|
|
||||||
self._city = city
|
|
||||||
self._end_point = end_point
|
|
||||||
self._sensor_database = base_path
|
|
||||||
metadata = True
|
|
||||||
content = False
|
|
||||||
with open(Path(base_path / db_file).resolve()) as concordia_db:
|
|
||||||
self._sensor_database = json.load(concordia_db)
|
|
||||||
|
|
||||||
for city_object in self._sensor_database['sensors']:
|
|
||||||
city_object_name = city_object['city_object']
|
|
||||||
for sensor in city_object['sensors']:
|
|
||||||
self._city_object.append(city_object_name)
|
|
||||||
self._sensors.append(sensor)
|
|
||||||
|
|
||||||
buffer = ""
|
|
||||||
with open(end_point.resolve()) as data:
|
|
||||||
for line in data:
|
|
||||||
line = ConcordiaFileReport._clean_line(line)
|
|
||||||
if metadata:
|
|
||||||
fields = line.split(',')
|
|
||||||
if len(fields) > 2:
|
|
||||||
point = fields[0].replace(":", "")
|
|
||||||
key = fields[1]
|
|
||||||
if fields[1] in self._sensors:
|
|
||||||
self._sensor_point[key] = point
|
|
||||||
if "End of Report" in line:
|
|
||||||
content = False
|
|
||||||
if content:
|
|
||||||
line = ConcordiaFileReport._merge_date_time(line)
|
|
||||||
buffer = buffer + line + '\n'
|
|
||||||
if line == '':
|
|
||||||
metadata = False
|
|
||||||
content = True
|
|
||||||
measures = pd.read_csv(io.StringIO(buffer), sep=',')
|
|
||||||
measures["Date time"] = pd.to_datetime(measures["Date time"])
|
|
||||||
self._measures = ConcordiaFileReport._force_format(measures)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _clean_line(line):
|
|
||||||
return line.replace('"', '').replace('\n', '')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _merge_date_time(line):
|
|
||||||
fields = line.split(',')
|
|
||||||
date = fields[0]
|
|
||||||
time = fields[1]
|
|
||||||
if '<>' in date:
|
|
||||||
return line.replace(f'{date},{time}', 'Date time')
|
|
||||||
date_fields = date.split('/')
|
|
||||||
format_date_time = f'"{int(date_fields[2])}-{int(date_fields[0]):02d}-{int(date_fields[1]):02d} {time}"'
|
|
||||||
return line.replace(f'{date},{time}', format_date_time)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _force_format(df):
|
|
||||||
for head in df.head():
|
|
||||||
if 'Date time' not in head:
|
|
||||||
df = df.astype({head: 'float64'})
|
|
||||||
return df
|
|
|
@ -1,36 +0,0 @@
|
||||||
"""
|
|
||||||
Concordia gas flow
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import pandas as pd
|
|
||||||
from imports.sensors.concordia_file_report import ConcordiaFileReport
|
|
||||||
from city_model_structure.iot.concordia_gas_flow_sensor import ConcordiaGasFlowSensor
|
|
||||||
|
|
||||||
|
|
||||||
class ConcordiaGasFlow(ConcordiaFileReport):
|
|
||||||
"""
|
|
||||||
Concordia gas flow sensor class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, city, end_point, base_path):
|
|
||||||
super().__init__(city, end_point, base_path, 'concordia_gas_flow_db.json')
|
|
||||||
for city_object in city.city_objects:
|
|
||||||
self._assign_sensor_to_object(city_object)
|
|
||||||
|
|
||||||
def _assign_sensor_to_object(self, obj):
|
|
||||||
for i in range(len(self._city_object)):
|
|
||||||
if self._city_object[i] == obj.name and self._sensors[i] in self._sensor_point:
|
|
||||||
building_measures = [self._measures["Date time"], self._measures[self._sensor_point[self._sensors[i]]]]
|
|
||||||
building_headers = ["Date time", "Gas Flow Cumulative Monthly"]
|
|
||||||
building_energy_consumption = pd.concat(building_measures, keys=building_headers, axis=1)
|
|
||||||
sensor = ConcordiaGasFlowSensor(self._sensors[i])
|
|
||||||
sensor_exist = False
|
|
||||||
for j in range(len(obj.sensors)):
|
|
||||||
if obj.sensors[j].name is sensor.name:
|
|
||||||
obj.sensors[j].add_period(building_energy_consumption)
|
|
||||||
sensor_exist = True
|
|
||||||
break
|
|
||||||
if not sensor_exist:
|
|
||||||
sensor.add_period(building_energy_consumption)
|
|
||||||
obj.sensors.append(sensor)
|
|
|
@ -1,35 +0,0 @@
|
||||||
"""
|
|
||||||
Concordia temperature
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import pandas as pd
|
|
||||||
from imports.sensors.concordia_file_report import ConcordiaFileReport
|
|
||||||
from city_model_structure.iot.concordia_temperature_sensor import ConcordiaTemperatureSensor
|
|
||||||
|
|
||||||
|
|
||||||
class ConcordiaTemperature(ConcordiaFileReport):
|
|
||||||
"""
|
|
||||||
Concordia temperature sensor class
|
|
||||||
"""
|
|
||||||
def __init__(self, city, end_point, base_path):
|
|
||||||
super().__init__(city, end_point, base_path, 'concordia_temperature_db.json')
|
|
||||||
for city_object in city.city_objects:
|
|
||||||
self._assign_sensor_to_object(city_object)
|
|
||||||
|
|
||||||
def _assign_sensor_to_object(self, obj):
|
|
||||||
for i in range(len(self._city_object)):
|
|
||||||
if self._city_object[i] == obj.name and self._sensors[i] in self._sensor_point:
|
|
||||||
building_measures = [self._measures["Date time"], self._measures[self._sensor_point[self._sensors[i]]]]
|
|
||||||
building_headers = ["Date time", "Temperature"]
|
|
||||||
building_energy_consumption = pd.concat(building_measures, keys=building_headers, axis=1)
|
|
||||||
sensor = ConcordiaTemperatureSensor(self._sensors[i])
|
|
||||||
sensor_exist = False
|
|
||||||
for j in range(len(obj.sensors)):
|
|
||||||
if obj.sensors[j].name is sensor.name:
|
|
||||||
obj.sensors[j].add_period(building_energy_consumption)
|
|
||||||
sensor_exist = True
|
|
||||||
break
|
|
||||||
if not sensor_exist:
|
|
||||||
sensor.add_period(building_energy_consumption)
|
|
||||||
obj.sensors.append(sensor)
|
|
|
@ -1,37 +0,0 @@
|
||||||
"""
|
|
||||||
SensorsFactory retrieve sensors information
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2021 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
from imports.sensors.concordia_energy_consumption import ConcordiaEnergyConsumption
|
|
||||||
from imports.sensors.concordia_gas_flow import ConcordiaGasFlow
|
|
||||||
from imports.sensors.concordia_temperature import ConcordiaTemperature
|
|
||||||
|
|
||||||
|
|
||||||
class SensorsFactory:
|
|
||||||
"""
|
|
||||||
UsageFactory class
|
|
||||||
"""
|
|
||||||
def __init__(self, handler, city, end_point, base_path=Path(Path(__file__).parent.parent / 'data/sensors')):
|
|
||||||
self._handler = '_' + handler.lower().replace(' ', '_')
|
|
||||||
self._city = city
|
|
||||||
self._end_point = end_point
|
|
||||||
self._base_path = base_path
|
|
||||||
|
|
||||||
def _cec(self):
|
|
||||||
ConcordiaEnergyConsumption(self._city, self._end_point, self._base_path)
|
|
||||||
|
|
||||||
def _cgf(self):
|
|
||||||
ConcordiaGasFlow(self._city, self._end_point, self._base_path)
|
|
||||||
|
|
||||||
def _ct(self):
|
|
||||||
ConcordiaTemperature(self._city, self._end_point, self._base_path)
|
|
||||||
|
|
||||||
def enrich(self):
|
|
||||||
"""
|
|
||||||
Enrich the city with the usage information
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
getattr(self, self._handler, lambda: None)()
|
|
|
@ -1,78 +0,0 @@
|
||||||
"""
|
|
||||||
CaUsageParameters model the usage properties
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from imports.geometry.helpers.geometry_helper import GeometryHelper as gh
|
|
||||||
from imports.usage.hft_usage_interface import HftUsageInterface
|
|
||||||
from city_model_structure.building_demand.usage_zone import UsageZone
|
|
||||||
from city_model_structure.building_demand.internal_gains import InternalGains
|
|
||||||
|
|
||||||
|
|
||||||
class CaUsageParameters(HftUsageInterface):
|
|
||||||
"""
|
|
||||||
CaUsageParameters class
|
|
||||||
"""
|
|
||||||
def __init__(self, city, base_path):
|
|
||||||
super().__init__(base_path, 'ca_archetypes_reduced.xml')
|
|
||||||
self._city = city
|
|
||||||
# todo: this is a wrong location for self._min_air_change -> re-think where to place this info
|
|
||||||
# and where it comes from
|
|
||||||
self._min_air_change = 0
|
|
||||||
|
|
||||||
def enrich_buildings(self):
|
|
||||||
"""
|
|
||||||
Returns the city with the usage parameters assigned to the buildings
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
city = self._city
|
|
||||||
for building in city.buildings:
|
|
||||||
archetype = self._search_archetype(building.function)
|
|
||||||
if archetype is None:
|
|
||||||
sys.stderr.write(f'Building {building.name} has unknown archetype for building function:'
|
|
||||||
f' {building.function}, that assigns building usage as '
|
|
||||||
f'{gh.usage_from_function(building.function)}\n')
|
|
||||||
continue
|
|
||||||
# todo: what to do with mix-usage usage from gml?
|
|
||||||
mix_usage = False
|
|
||||||
if not mix_usage:
|
|
||||||
# just one usage_zone
|
|
||||||
for thermal_zone in building.thermal_zones:
|
|
||||||
usage_zone = UsageZone()
|
|
||||||
usage_zone.volume = thermal_zone.volume
|
|
||||||
self._assign_values(usage_zone, archetype)
|
|
||||||
thermal_zone.usage_zones = [usage_zone]
|
|
||||||
|
|
||||||
def _search_archetype(self, building_usage):
|
|
||||||
for building_archetype in self._usage_archetypes:
|
|
||||||
if building_archetype.usage == building_usage:
|
|
||||||
return building_archetype
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _assign_values(usage_zone, archetype):
|
|
||||||
usage_zone.usage = archetype.usage
|
|
||||||
# Due to the fact that python is not a typed language, the wrong object type is assigned to
|
|
||||||
# usage_zone.internal_gains when writing usage_zone.internal_gains = archetype.internal_gains.
|
|
||||||
# Therefore, this walk around has been done.
|
|
||||||
internal_gains = []
|
|
||||||
for archetype_internal_gain in archetype.internal_gains:
|
|
||||||
internal_gain = InternalGains()
|
|
||||||
internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain
|
|
||||||
internal_gain.convective_fraction = archetype_internal_gain.convective_fraction
|
|
||||||
internal_gain.radiative_fraction = archetype_internal_gain.radiative_fraction
|
|
||||||
internal_gain.latent_fraction = archetype_internal_gain.latent_fraction
|
|
||||||
internal_gains.append(internal_gain)
|
|
||||||
usage_zone.internal_gains = internal_gains
|
|
||||||
usage_zone.heating_setpoint = archetype.heating_setpoint
|
|
||||||
usage_zone.heating_setback = archetype.heating_setback
|
|
||||||
usage_zone.cooling_setpoint = archetype.cooling_setpoint
|
|
||||||
usage_zone.occupancy_density = archetype.occupancy_density
|
|
||||||
usage_zone.hours_day = archetype.hours_day
|
|
||||||
usage_zone.days_year = archetype.days_year
|
|
||||||
usage_zone.dhw_average_volume_pers_day = archetype.dhw_average_volume_pers_day
|
|
||||||
usage_zone.dhw_preparation_temperature = archetype.dhw_preparation_temperature
|
|
||||||
usage_zone.electrical_app_average_consumption_sqm_year = archetype.electrical_app_average_consumption_sqm_year
|
|
||||||
usage_zone.mechanical_air_change = archetype.mechanical_air_change
|
|
|
@ -1,49 +0,0 @@
|
||||||
"""
|
|
||||||
HftInternalGainsArchetype stores internal gains information, complementing the HftUsageZoneArchetype class
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class HftInternalGainsArchetype:
|
|
||||||
"""
|
|
||||||
HftInternalGainsArchetype class
|
|
||||||
"""
|
|
||||||
def __init__(self, average_internal_gain=None, convective_fraction=None, radiative_fraction=None,
|
|
||||||
latent_fraction=None):
|
|
||||||
self._average_internal_gain = average_internal_gain
|
|
||||||
self._convective_fraction = convective_fraction
|
|
||||||
self._radiative_fraction = radiative_fraction
|
|
||||||
self._latent_fraction = latent_fraction
|
|
||||||
|
|
||||||
@property
|
|
||||||
def average_internal_gain(self):
|
|
||||||
"""
|
|
||||||
Get internal gains average internal gain in W/m2
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._average_internal_gain
|
|
||||||
|
|
||||||
@property
|
|
||||||
def convective_fraction(self):
|
|
||||||
"""
|
|
||||||
Get internal gains convective fraction
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._convective_fraction
|
|
||||||
|
|
||||||
@property
|
|
||||||
def radiative_fraction(self):
|
|
||||||
"""
|
|
||||||
Get internal gains radiative fraction
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._radiative_fraction
|
|
||||||
|
|
||||||
@property
|
|
||||||
def latent_fraction(self):
|
|
||||||
"""
|
|
||||||
Get internal gains latent fraction
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._latent_fraction
|
|
|
@ -1,144 +0,0 @@
|
||||||
"""
|
|
||||||
HftUsageZoneArchetype stores usage information by building archetypes
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
from typing import List
|
|
||||||
from imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype
|
|
||||||
|
|
||||||
|
|
||||||
class HftUsageZoneArchetype:
|
|
||||||
"""
|
|
||||||
HftUsageZoneArchetype class
|
|
||||||
"""
|
|
||||||
def __init__(self, usage=None, internal_gains=None, heating_set_point=None, heating_set_back=None,
|
|
||||||
cooling_set_point=None, occupancy_density=None, hours_day=None, days_year=None,
|
|
||||||
dhw_average_volume_pers_day=None, dhw_preparation_temperature=None,
|
|
||||||
electrical_app_average_consumption_sqm_year=None, mechanical_air_change=None,
|
|
||||||
occupancy=None, schedules=None):
|
|
||||||
self._usage = usage
|
|
||||||
self._internal_gains = internal_gains
|
|
||||||
self._heating_setpoint = heating_set_point
|
|
||||||
self._heating_setback = heating_set_back
|
|
||||||
self._cooling_setpoint = cooling_set_point
|
|
||||||
self._occupancy_density = occupancy_density
|
|
||||||
self._hours_day = hours_day
|
|
||||||
self._days_year = days_year
|
|
||||||
self._dhw_average_volume_pers_day = dhw_average_volume_pers_day
|
|
||||||
self._dhw_preparation_temperature = dhw_preparation_temperature
|
|
||||||
self._electrical_app_average_consumption_sqm_year = electrical_app_average_consumption_sqm_year
|
|
||||||
self._mechanical_air_change = mechanical_air_change
|
|
||||||
self._occupancy = occupancy
|
|
||||||
self._schedules = schedules
|
|
||||||
|
|
||||||
@property
|
|
||||||
def internal_gains(self) -> List[HftInternalGainsArchetype]:
|
|
||||||
"""
|
|
||||||
Get usage zone internal gains
|
|
||||||
:return: [InternalGains]
|
|
||||||
"""
|
|
||||||
return self._internal_gains
|
|
||||||
|
|
||||||
@property
|
|
||||||
def heating_setpoint(self):
|
|
||||||
"""
|
|
||||||
Get usage zone heating set point in celsius grads
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._heating_setpoint
|
|
||||||
|
|
||||||
@property
|
|
||||||
def heating_setback(self):
|
|
||||||
"""
|
|
||||||
Get usage zone heating setback in celsius grads
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._heating_setback
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cooling_setpoint(self):
|
|
||||||
"""
|
|
||||||
Get usage zone cooling setpoint in celsius grads
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._cooling_setpoint
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hours_day(self):
|
|
||||||
"""
|
|
||||||
Get usage zone usage hours per day
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._hours_day
|
|
||||||
|
|
||||||
@property
|
|
||||||
def days_year(self):
|
|
||||||
"""
|
|
||||||
Get usage zone usage days per year
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._days_year
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mechanical_air_change(self):
|
|
||||||
"""
|
|
||||||
Set usage zone mechanical air change in air change per hour (ACH)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._mechanical_air_change
|
|
||||||
|
|
||||||
@property
|
|
||||||
def usage(self):
|
|
||||||
"""
|
|
||||||
Get usage zone usage
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
return self._usage
|
|
||||||
|
|
||||||
@property
|
|
||||||
def occupancy(self):
|
|
||||||
"""
|
|
||||||
Get schedules data
|
|
||||||
:return: [Occupancy]
|
|
||||||
"""
|
|
||||||
return self._occupancy
|
|
||||||
|
|
||||||
@property
|
|
||||||
def schedules(self):
|
|
||||||
"""
|
|
||||||
Get schedules
|
|
||||||
:return: [Schedule_Values]
|
|
||||||
"""
|
|
||||||
return self._schedules
|
|
||||||
|
|
||||||
@property
|
|
||||||
def occupancy_density(self):
|
|
||||||
"""
|
|
||||||
Get schedules density in persons per m2
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._occupancy_density
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dhw_average_volume_pers_day(self):
|
|
||||||
"""
|
|
||||||
Get average DHW consumption in m3 per person per day
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._dhw_average_volume_pers_day
|
|
||||||
|
|
||||||
@property
|
|
||||||
def dhw_preparation_temperature(self):
|
|
||||||
"""
|
|
||||||
Get preparation temperature of the DHW in degree Celsius
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._dhw_preparation_temperature
|
|
||||||
|
|
||||||
@property
|
|
||||||
def electrical_app_average_consumption_sqm_year(self):
|
|
||||||
"""
|
|
||||||
Get average consumption of electrical appliances in Joules per m2 and year (J/m2yr)
|
|
||||||
:return: float
|
|
||||||
"""
|
|
||||||
return self._electrical_app_average_consumption_sqm_year
|
|
|
@ -1,38 +0,0 @@
|
||||||
"""
|
|
||||||
Usage helper
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import helpers.constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class UsageHelper:
|
|
||||||
"""
|
|
||||||
Usage helpre class
|
|
||||||
"""
|
|
||||||
usage_to_hft = {
|
|
||||||
cte.RESIDENTIAL: 'residential',
|
|
||||||
cte.INDUSTRY: 'industry',
|
|
||||||
cte.OFFICE_ADMINISTRATION: 'office and administration',
|
|
||||||
cte.HOTEL: 'hotel',
|
|
||||||
cte.HEALTH_CARE: 'health care',
|
|
||||||
cte.RETAIL: 'retail',
|
|
||||||
cte.HALL: 'hall',
|
|
||||||
cte.RESTAURANT: 'restaurant',
|
|
||||||
cte.EDUCATION: 'education'
|
|
||||||
}
|
|
||||||
hft_default_value = 'residential'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def hft_from_usage(usage):
|
|
||||||
"""
|
|
||||||
Get HfT usage from the given internal usage key
|
|
||||||
:param usage: str
|
|
||||||
:return: str
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return UsageHelper.usage_to_hft[usage]
|
|
||||||
except KeyError:
|
|
||||||
sys.stderr.write('Error: keyword not found. Returned default HfT usage "residential"\n')
|
|
||||||
return UsageHelper.hft_default_value
|
|
|
@ -1,141 +0,0 @@
|
||||||
"""
|
|
||||||
Hft-based interface, it reads format defined within the CERC team based on that one used in SimStadt and developed by
|
|
||||||
the IAF team at hft-Stuttgart and enriches the city with usage parameters
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import xmltodict
|
|
||||||
from imports.usage.data_classes.hft_usage_zone_archetype import HftUsageZoneArchetype as huza
|
|
||||||
from imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype as higa
|
|
||||||
|
|
||||||
|
|
||||||
class HftUsageInterface:
|
|
||||||
"""
|
|
||||||
HftUsageInterface abstract class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, base_path, usage_file='ca_library_reduced.xml'):
|
|
||||||
path = str(base_path / usage_file)
|
|
||||||
self._usage_archetypes = []
|
|
||||||
with open(path) as xml:
|
|
||||||
self._archetypes = xmltodict.parse(xml.read(), force_list=('zoneUsageVariant', 'zoneUsageType'))
|
|
||||||
for zone_usage_type in self._archetypes['buildingUsageLibrary']['zoneUsageType']:
|
|
||||||
usage = zone_usage_type['id']
|
|
||||||
usage_archetype = self._parse_zone_usage_type(usage, zone_usage_type)
|
|
||||||
self._usage_archetypes.append(usage_archetype)
|
|
||||||
if 'zoneUsageVariant' in zone_usage_type:
|
|
||||||
for usage_zone_variant in zone_usage_type['zoneUsageVariant']:
|
|
||||||
usage = usage_zone_variant['id']
|
|
||||||
usage_archetype_variant = self._parse_zone_usage_variant(usage, usage_archetype, usage_zone_variant)
|
|
||||||
self._usage_archetypes.append(usage_archetype_variant)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _parse_zone_usage_type(usage, zone_usage_type):
|
|
||||||
occupancy_density = zone_usage_type['occupancy']['occupancyDensity']
|
|
||||||
hours_day = zone_usage_type['occupancy']['usageHoursPerDay']
|
|
||||||
days_year = zone_usage_type['occupancy']['usageDaysPerYear']
|
|
||||||
cooling_setpoint = zone_usage_type['endUses']['space_cooling']['coolingSetPointTemperature']
|
|
||||||
heating_setpoint = zone_usage_type['endUses']['space_heating']['heatingSetPointTemperature']
|
|
||||||
heating_setback = zone_usage_type['endUses']['space_heating']['heatingSetBackTemperature']
|
|
||||||
mechanical_air_change = None
|
|
||||||
if 'ventilation' in zone_usage_type['endUses'] and zone_usage_type['endUses']['ventilation'] is not None:
|
|
||||||
mechanical_air_change = zone_usage_type['endUses']['ventilation']['mechanicalAirChangeRate']
|
|
||||||
dhw_average_volume_pers_day = None
|
|
||||||
dhw_preparation_temperature = None
|
|
||||||
if 'domestic_hot_water' in zone_usage_type['endUses']:
|
|
||||||
# liters to cubic meters
|
|
||||||
dhw_average_volume_pers_day = float(
|
|
||||||
zone_usage_type['endUses']['domestic_hot_water']['averageVolumePerPersAndDay']) / 1000
|
|
||||||
dhw_preparation_temperature = zone_usage_type['endUses']['domestic_hot_water']['preparationTemperature']
|
|
||||||
electrical_app_average_consumption_sqm_year = None
|
|
||||||
if 'all_electrical_appliances' in zone_usage_type['endUses']:
|
|
||||||
if 'averageConsumptionPerSqmAndYear' in zone_usage_type['endUses']['all_electrical_appliances']:
|
|
||||||
# kWh to J
|
|
||||||
electrical_app_average_consumption_sqm_year = \
|
|
||||||
float(zone_usage_type['endUses']['all_electrical_appliances']['averageConsumptionPerSqmAndYear']) / 3.6
|
|
||||||
|
|
||||||
# todo: for internal_gain in usage_zone_variant['schedules']['internGains']:????????????????
|
|
||||||
# There are no more internal gains? How is it saved when more than one???
|
|
||||||
internal_gains = []
|
|
||||||
if 'internGains' in zone_usage_type['occupancy']:
|
|
||||||
latent_fraction = zone_usage_type['occupancy']['internGains']['latentFraction']
|
|
||||||
convective_fraction = zone_usage_type['occupancy']['internGains']['convectiveFraction']
|
|
||||||
average_internal_gain = zone_usage_type['occupancy']['internGains']['averageInternGainPerSqm']
|
|
||||||
radiative_fraction = zone_usage_type['occupancy']['internGains']['radiantFraction']
|
|
||||||
else:
|
|
||||||
latent_fraction = 0
|
|
||||||
convective_fraction = 0
|
|
||||||
average_internal_gain = 0
|
|
||||||
radiative_fraction = 0
|
|
||||||
|
|
||||||
internal_gains.append(higa(average_internal_gain, convective_fraction, radiative_fraction, latent_fraction))
|
|
||||||
usage_zone_archetype = huza(usage=usage, internal_gains=internal_gains, heating_set_point=heating_setpoint,
|
|
||||||
heating_set_back=heating_setback, cooling_set_point=cooling_setpoint,
|
|
||||||
occupancy_density=occupancy_density, hours_day=hours_day, days_year=days_year,
|
|
||||||
dhw_average_volume_pers_day=dhw_average_volume_pers_day,
|
|
||||||
dhw_preparation_temperature=dhw_preparation_temperature,
|
|
||||||
electrical_app_average_consumption_sqm_year=electrical_app_average_consumption_sqm_year,
|
|
||||||
mechanical_air_change=mechanical_air_change)
|
|
||||||
return usage_zone_archetype
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _parse_zone_usage_variant(usage, usage_zone, usage_zone_variant):
|
|
||||||
# for the variants all is optional because it mimics the inheritance concept from OOP
|
|
||||||
occupancy_density = usage_zone.occupancy_density
|
|
||||||
hours_day = usage_zone.hours_day
|
|
||||||
days_year = usage_zone.days_year
|
|
||||||
cooling_setpoint = usage_zone.cooling_setpoint
|
|
||||||
heating_setpoint = usage_zone.heating_setpoint
|
|
||||||
heating_setback = usage_zone.heating_setback
|
|
||||||
mechanical_air_change = usage_zone.mechanical_air_change
|
|
||||||
dhw_average_volume_pers_day = usage_zone.dhw_average_volume_pers_day
|
|
||||||
dhw_preparation_temperature = usage_zone.dhw_preparation_temperature
|
|
||||||
electrical_app_average_consumption_sqm_year = usage_zone.electrical_app_average_consumption_sqm_year
|
|
||||||
|
|
||||||
# todo: for internal_gain in usage_zone_variant['schedules']['internGains']:????????????????
|
|
||||||
# There are no more internal gains? How is it saved when more than one???
|
|
||||||
# for internal_gain in usage_zone.internal_gains:
|
|
||||||
internal_gains = usage_zone.internal_gains[0]
|
|
||||||
latent_fraction = internal_gains.latent_fraction
|
|
||||||
convective_fraction = internal_gains.convective_fraction
|
|
||||||
average_internal_gain = internal_gains.average_internal_gain
|
|
||||||
radiative_fraction = internal_gains.radiative_fraction
|
|
||||||
|
|
||||||
if 'space_cooling' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['space_cooling'] is not None:
|
|
||||||
if 'coolingSetPointTemperature' in usage_zone_variant['endUses']['space_cooling']:
|
|
||||||
cooling_setpoint = usage_zone_variant['endUses']['space_cooling']['coolingSetPointTemperature']
|
|
||||||
if 'space_heating' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['space_heating'] is not None:
|
|
||||||
if 'heatingSetPointTemperature' in usage_zone_variant['endUses']['space_heating']:
|
|
||||||
heating_setpoint = usage_zone_variant['endUses']['space_heating']['heatingSetPointTemperature']
|
|
||||||
if 'heatingSetBackTemperature' in usage_zone_variant['endUses']['space_heating']:
|
|
||||||
heating_setback = usage_zone_variant['endUses']['space_heating']['heatingSetBackTemperature']
|
|
||||||
if 'ventilation' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['ventilation'] is not None:
|
|
||||||
if 'mechanicalAirChangeRate' in usage_zone_variant['endUses']['ventilation']:
|
|
||||||
mechanical_air_change = usage_zone_variant['endUses']['ventilation']['mechanicalAirChangeRate']
|
|
||||||
# todo: for internal_gain in usage_zone_variant['schedules']['internGains']:????????????????
|
|
||||||
# There are no more internal gains? How is it saved when more than one???
|
|
||||||
if 'schedules' in usage_zone_variant:
|
|
||||||
if 'usageHoursPerDay' in usage_zone_variant['schedules']:
|
|
||||||
hours_day = usage_zone_variant['schedules']['usageHoursPerDay']
|
|
||||||
if 'usageDaysPerYear' in usage_zone_variant['schedules']:
|
|
||||||
days_year = usage_zone_variant['schedules']['usageDaysPerYear']
|
|
||||||
if 'internalGains' in usage_zone_variant['schedules'] and usage_zone_variant['schedules'][
|
|
||||||
'internGains'] is not None:
|
|
||||||
internal_gains = []
|
|
||||||
if 'latentFraction' in usage_zone_variant['schedules']['internGains']:
|
|
||||||
latent_fraction = usage_zone_variant['schedules']['internGains']['latentFraction']
|
|
||||||
if 'convectiveFraction' in usage_zone_variant['schedules']['internGains']:
|
|
||||||
convective_fraction = usage_zone_variant['schedules']['internGains']['convectiveFraction']
|
|
||||||
if 'averageInternGainPerSqm' in usage_zone_variant['schedules']['internGains']:
|
|
||||||
average_internal_gain = usage_zone_variant['schedules']['internGains']['averageInternGainPerSqm']
|
|
||||||
if 'radiantFraction' in usage_zone_variant['schedules']['internGains']:
|
|
||||||
radiative_fraction = usage_zone_variant['schedules']['internGains']['radiantFraction']
|
|
||||||
internal_gains.append(higa(average_internal_gain, convective_fraction, radiative_fraction, latent_fraction))
|
|
||||||
usage_zone_archetype = huza(usage=usage, internal_gains=internal_gains, heating_set_point=heating_setpoint,
|
|
||||||
heating_set_back=heating_setback, cooling_set_point=cooling_setpoint,
|
|
||||||
occupancy_density=occupancy_density, hours_day=hours_day, days_year=days_year,
|
|
||||||
dhw_average_volume_pers_day=dhw_average_volume_pers_day,
|
|
||||||
dhw_preparation_temperature=dhw_preparation_temperature,
|
|
||||||
electrical_app_average_consumption_sqm_year=electrical_app_average_consumption_sqm_year,
|
|
||||||
mechanical_air_change=mechanical_air_change)
|
|
||||||
return usage_zone_archetype
|
|
|
@ -1,78 +0,0 @@
|
||||||
"""
|
|
||||||
HftUsageParameters model the usage properties
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from imports.geometry.helpers.geometry_helper import GeometryHelper as gh
|
|
||||||
from imports.usage.hft_usage_interface import HftUsageInterface
|
|
||||||
from city_model_structure.building_demand.usage_zone import UsageZone
|
|
||||||
from city_model_structure.building_demand.internal_gains import InternalGains
|
|
||||||
|
|
||||||
|
|
||||||
class HftUsageParameters(HftUsageInterface):
|
|
||||||
"""
|
|
||||||
HftUsageParameters class
|
|
||||||
"""
|
|
||||||
def __init__(self, city, base_path):
|
|
||||||
super().__init__(base_path, 'de_library.xml')
|
|
||||||
self._city = city
|
|
||||||
# todo: this is a wrong location for self._min_air_change -> re-think where to place this info
|
|
||||||
# and where it comes from
|
|
||||||
self._min_air_change = 0
|
|
||||||
|
|
||||||
def enrich_buildings(self):
|
|
||||||
"""
|
|
||||||
Returns the city with the usage parameters assigned to the buildings
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
city = self._city
|
|
||||||
for building in city.buildings:
|
|
||||||
archetype = self._search_archetype(gh.usage_from_function(building.function))
|
|
||||||
if archetype is None:
|
|
||||||
sys.stderr.write(f'Building {building.name} has unknown archetype for building function:'
|
|
||||||
f' {building.function}, that assigns building usage as '
|
|
||||||
f'{gh.usage_from_function(building.function)}\n')
|
|
||||||
continue
|
|
||||||
# todo: what to do with mix-usage usage from gml?
|
|
||||||
mix_usage = False
|
|
||||||
if not mix_usage:
|
|
||||||
# just one usage_zone
|
|
||||||
for thermal_zone in building.thermal_zones:
|
|
||||||
usage_zone = UsageZone()
|
|
||||||
self._assign_values(usage_zone, archetype)
|
|
||||||
usage_zone.volume = thermal_zone.volume
|
|
||||||
thermal_zone.usage_zones = [usage_zone]
|
|
||||||
|
|
||||||
def _search_archetype(self, building_usage):
|
|
||||||
for building_archetype in self._usage_archetypes:
|
|
||||||
if building_archetype.usage == building_usage:
|
|
||||||
return building_archetype
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _assign_values(usage_zone, archetype):
|
|
||||||
usage_zone.usage = archetype.usage
|
|
||||||
# Due to the fact that python is not a typed language, the wrong object type is assigned to
|
|
||||||
# usage_zone.internal_gains when writing usage_zone.internal_gains = archetype.internal_gains.
|
|
||||||
# Therefore, this walk around has been done.
|
|
||||||
internal_gains = []
|
|
||||||
for archetype_internal_gain in archetype.internal_gains:
|
|
||||||
internal_gain = InternalGains()
|
|
||||||
internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain
|
|
||||||
internal_gain.convective_fraction = archetype_internal_gain.convective_fraction
|
|
||||||
internal_gain.radiative_fraction = archetype_internal_gain.radiative_fraction
|
|
||||||
internal_gain.latent_fraction = archetype_internal_gain.latent_fraction
|
|
||||||
internal_gains.append(internal_gain)
|
|
||||||
usage_zone.internal_gains = internal_gains
|
|
||||||
usage_zone.heating_setpoint = archetype.heating_setpoint
|
|
||||||
usage_zone.heating_setback = archetype.heating_setback
|
|
||||||
usage_zone.cooling_setpoint = archetype.cooling_setpoint
|
|
||||||
usage_zone.occupancy_density = archetype.occupancy_density
|
|
||||||
usage_zone.hours_day = archetype.hours_day
|
|
||||||
usage_zone.days_year = archetype.days_year
|
|
||||||
usage_zone.dhw_average_volume_pers_day = archetype.dhw_average_volume_pers_day
|
|
||||||
usage_zone.dhw_preparation_temperature = archetype.dhw_preparation_temperature
|
|
||||||
usage_zone.electrical_app_average_consumption_sqm_year = archetype.electrical_app_average_consumption_sqm_year
|
|
||||||
usage_zone.mechanical_air_change = archetype.mechanical_air_change
|
|
|
@ -1,38 +0,0 @@
|
||||||
"""
|
|
||||||
UsageFactory retrieve the specific usage module for the given region
|
|
||||||
This factory can only be called after calling the construction factory so the thermal zones are created.
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
from imports.usage.hft_usage_parameters import HftUsageParameters
|
|
||||||
from imports.usage.ca_usage_parameters import CaUsageParameters
|
|
||||||
|
|
||||||
# todo: handle missing lambda and rise error.
|
|
||||||
|
|
||||||
|
|
||||||
class UsageFactory:
|
|
||||||
"""
|
|
||||||
UsageFactory class
|
|
||||||
"""
|
|
||||||
def __init__(self, handler, city, base_path=Path(Path(__file__).parent.parent / 'data/usage')):
|
|
||||||
self._handler = '_' + handler.lower().replace(' ', '_')
|
|
||||||
self._city = city
|
|
||||||
self._base_path = base_path
|
|
||||||
for building in city.buildings:
|
|
||||||
if len(building.thermal_zones) == 0:
|
|
||||||
raise Exception('It seems that the usage factory is being called before the construction factory. '
|
|
||||||
'Please ensure that the construction factory is called first.')
|
|
||||||
|
|
||||||
def _hft(self):
|
|
||||||
return HftUsageParameters(self._city, self._base_path).enrich_buildings()
|
|
||||||
|
|
||||||
def _ca(self):
|
|
||||||
return CaUsageParameters(self._city, self._base_path).enrich_buildings()
|
|
||||||
|
|
||||||
def enrich(self):
|
|
||||||
"""
|
|
||||||
Enrich the city with the usage information
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
getattr(self, self._handler, lambda: None)()
|
|
|
@ -1,56 +0,0 @@
|
||||||
"""
|
|
||||||
DatWeatherParameters class to extract weather parameters from a defined region in .dat format
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
import pandas as pd
|
|
||||||
import helpers.constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class DatWeatherParameters:
|
|
||||||
"""
|
|
||||||
DatWeatherParameters class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, city, path):
|
|
||||||
self._weather_values = None
|
|
||||||
self._city = city
|
|
||||||
self._city_name = city.climate_reference_city
|
|
||||||
file_name = 'inseldb_' + self._city_name + '.dat'
|
|
||||||
self._path = Path(path / file_name)
|
|
||||||
|
|
||||||
if self._weather_values is None:
|
|
||||||
try:
|
|
||||||
self._weather_values = pd.read_csv(self._path, sep=r'\s+', header=None,
|
|
||||||
names=['hour', 'global_horiz', 'temperature', 'diffuse', 'beam', 'empty'])
|
|
||||||
except SystemExit:
|
|
||||||
sys.stderr.write(f'Error: weather file {self._path} not found\n')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
for building in self._city.buildings:
|
|
||||||
new_value = pd.DataFrame(self._weather_values[['temperature']].to_numpy(), columns=['inseldb'])
|
|
||||||
if cte.HOUR not in building.external_temperature:
|
|
||||||
building.external_temperature[cte.HOUR] = new_value
|
|
||||||
else:
|
|
||||||
pd.concat([building.external_temperature[cte.HOUR], new_value], axis=1)
|
|
||||||
|
|
||||||
new_value = pd.DataFrame(self._weather_values[['global_horiz']].to_numpy(), columns=['inseldb'])
|
|
||||||
if cte.HOUR not in building.global_horizontal:
|
|
||||||
building.global_horizontal[cte.HOUR] = new_value
|
|
||||||
else:
|
|
||||||
pd.concat([building.global_horizontal[cte.HOUR], new_value], axis=1)
|
|
||||||
|
|
||||||
new_value = pd.DataFrame(self._weather_values[['diffuse']].to_numpy(), columns=['inseldb'])
|
|
||||||
if cte.HOUR not in building.diffuse:
|
|
||||||
building.diffuse[cte.HOUR] = new_value
|
|
||||||
else:
|
|
||||||
pd.concat([building.diffuse[cte.HOUR], new_value], axis=1)
|
|
||||||
|
|
||||||
new_value = pd.DataFrame(self._weather_values[['beam']].to_numpy(), columns=['inseldb'])
|
|
||||||
if cte.HOUR not in building.beam:
|
|
||||||
building.beam[cte.HOUR] = new_value
|
|
||||||
else:
|
|
||||||
pd.concat([building.beam[cte.HOUR], new_value], axis=1)
|
|
|
@ -1,111 +0,0 @@
|
||||||
"""
|
|
||||||
EpwWeatherParameters class to extract weather parameters from a defined region in .epw format (EnergyPlus Weather)
|
|
||||||
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
||||||
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
import pandas as pd
|
|
||||||
import helpers.constants as cte
|
|
||||||
|
|
||||||
|
|
||||||
class EpwWeatherParameters:
|
|
||||||
"""
|
|
||||||
EpwWeatherParameters class
|
|
||||||
"""
|
|
||||||
def __init__(self, city, path, file_name):
|
|
||||||
self._weather_values = None
|
|
||||||
self._city = city
|
|
||||||
self._path = Path(path / file_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
file = open(self._path, 'r')
|
|
||||||
line = file.readline().split(',')
|
|
||||||
city.climate_reference_city = line[1]
|
|
||||||
city.latitude = line[6]
|
|
||||||
city.longitude = line[7]
|
|
||||||
city.time_zone = line[8]
|
|
||||||
for i in range(0, 2):
|
|
||||||
line = file.readline().split(',')
|
|
||||||
line = file.readline().split(',')
|
|
||||||
number_records = int(line[1])
|
|
||||||
depth_measurement_ground_temperature = []
|
|
||||||
ground_temperature = []
|
|
||||||
for i in range(0, number_records):
|
|
||||||
depth_measurement_ground_temperature.append(line[i*16+2])
|
|
||||||
temperatures = []
|
|
||||||
for j in range(0, 12):
|
|
||||||
temperatures.append(line[i*16+j+6])
|
|
||||||
ground_temperature.append(temperatures)
|
|
||||||
file.close()
|
|
||||||
except SystemExit:
|
|
||||||
sys.stderr.write(f'Error: weather file {self._path} not found. Please download it from '
|
|
||||||
f'https://energyplus.net/weather and place it in folder data\\weather\\epw\n')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._weather_values = pd.read_csv(self._path, header=0, skiprows=7,
|
|
||||||
names=['year', 'month', 'day', 'hour', 'minute',
|
|
||||||
'data_source_and_uncertainty_flags',
|
|
||||||
'dry_bulb_temperature_c', 'dew_point_temperature_c',
|
|
||||||
'relative_humidity_perc',
|
|
||||||
'atmospheric_station_pressure_pa',
|
|
||||||
'extraterrestrial_horizontal_radiation_wh_m2',
|
|
||||||
'extraterrestrial_direct_normal_radiation_wh_m2',
|
|
||||||
'horizontal_infrared_radiation_intensity_wh_m2',
|
|
||||||
'global_horizontal_radiation_wh_m2', 'direct_normal_radiation_wh_m2',
|
|
||||||
'diffuse_horizontal_radiation_wh_m2',
|
|
||||||
'global_horizontal_illuminance_lux',
|
|
||||||
'direct_normal_illuminance_lux', 'diffuse_horizontal_illuminance_lux',
|
|
||||||
'zenith_luminance_cd_m2',
|
|
||||||
'wind_direction_deg', 'wind_speed_m_s', 'total_sky_cover',
|
|
||||||
'opaque_sky_cover', 'visibility_km',
|
|
||||||
'ceiling_heigh_m_s', 'present_weather_observation',
|
|
||||||
'present_weather_codes',
|
|
||||||
'precipitable_water_mm', 'aerosol_optical_depth_10_3_ths',
|
|
||||||
'snow_depth_cm',
|
|
||||||
'days_since_last_snowfall', 'albedo', 'liquid_precipitation_depth_mm',
|
|
||||||
'liquid_precipitation_quality_hr'])
|
|
||||||
except SystemExit:
|
|
||||||
sys.stderr.write(f'Error: wrong formatting of weather file {self._path}\n')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
for building in self._city.buildings:
|
|
||||||
if cte.HOUR in building.external_temperature:
|
|
||||||
del building.external_temperature[cte.HOUR]
|
|
||||||
new_value = pd.DataFrame(self._weather_values[['dry_bulb_temperature_c']].to_numpy(), columns=['epw'])
|
|
||||||
number_invalid_records = new_value[new_value.epw == 99.9].count().epw
|
|
||||||
if number_invalid_records > 0:
|
|
||||||
sys.stderr.write(f'Warning: {self._path} invalid records (value of 99.9) in dry bulb temperature\n')
|
|
||||||
if cte.HOUR not in building.external_temperature:
|
|
||||||
building.external_temperature[cte.HOUR] = new_value
|
|
||||||
else:
|
|
||||||
pd.concat([building.external_temperature[cte.HOUR], new_value], axis=1)
|
|
||||||
|
|
||||||
new_value = pd.DataFrame(self._weather_values[['global_horizontal_radiation_wh_m2']].to_numpy(), columns=['epw'])
|
|
||||||
number_invalid_records = new_value[new_value.epw == 9999].count().epw
|
|
||||||
if number_invalid_records > 0:
|
|
||||||
sys.stderr.write(f'Warning: {self._path} invalid records (value of 9999) in global horizontal radiation\n')
|
|
||||||
if cte.HOUR not in building.global_horizontal:
|
|
||||||
building.global_horizontal[cte.HOUR] = new_value
|
|
||||||
else:
|
|
||||||
pd.concat([building.global_horizontal[cte.HOUR], new_value], axis=1)
|
|
||||||
|
|
||||||
new_value = pd.DataFrame(self._weather_values[['diffuse_horizontal_radiation_wh_m2']].to_numpy(), columns=['epw'])
|
|
||||||
number_invalid_records = new_value[new_value.epw == 9999].count().epw
|
|
||||||
if number_invalid_records > 0:
|
|
||||||
sys.stderr.write(f'Warning: {self._path} invalid records (value of 9999) in diffuse horizontal radiation\n')
|
|
||||||
if cte.HOUR not in building.diffuse:
|
|
||||||
building.diffuse[cte.HOUR] = new_value
|
|
||||||
else:
|
|
||||||
pd.concat([building.diffuse[cte.HOUR], new_value], axis=1)
|
|
||||||
|
|
||||||
new_value = pd.DataFrame(self._weather_values[['direct_normal_radiation_wh_m2']].to_numpy(), columns=['epw'])
|
|
||||||
number_invalid_records = new_value[new_value.epw == 9999].count().epw
|
|
||||||
if number_invalid_records > 0:
|
|
||||||
sys.stderr.write(f'Warning: {self._path} invalid records (value of 9999) in direct horizontal radiation\n')
|
|
||||||
if cte.HOUR not in building.beam:
|
|
||||||
building.beam[cte.HOUR] = new_value
|
|
||||||
else:
|
|
||||||
pd.concat([building.beam[cte.HOUR], new_value], axis=1)
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user