Restore repo

This commit is contained in:
Pilar 2021-08-27 17:29:32 -04:00
parent a78cb879a0
commit c60aca4c07
103 changed files with 114266 additions and 0 deletions

View File

@ -0,0 +1,27 @@
"""
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)

View File

@ -0,0 +1,26 @@
# 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)

View File

@ -0,0 +1,45 @@
"""
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

View File

@ -0,0 +1,46 @@
"""
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

View File

@ -0,0 +1,57 @@
"""
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

View File

@ -0,0 +1,35 @@
"""
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

View File

@ -0,0 +1,646 @@
"""
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

View File

@ -0,0 +1,252 @@
"""
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()

View File

@ -0,0 +1,31 @@
"""
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

View File

@ -0,0 +1,33 @@
"""
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

View File

@ -0,0 +1,353 @@
"""
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

View File

@ -0,0 +1,27 @@
"""
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)

View File

@ -0,0 +1,26 @@
# 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)

View File

@ -0,0 +1,81 @@
"""
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

View File

@ -0,0 +1,59 @@
"""
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

View File

@ -0,0 +1,165 @@
"""
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

View File

@ -0,0 +1,217 @@
"""
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

View File

@ -0,0 +1,93 @@
"""
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

View File

@ -0,0 +1,295 @@
"""
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

View File

@ -0,0 +1,394 @@
"""
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

View File

@ -0,0 +1,279 @@
"""
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

View File

@ -0,0 +1,200 @@
"""
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

View File

@ -0,0 +1,379 @@
"""
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

View File

@ -0,0 +1,38 @@
"""
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

View File

@ -0,0 +1,366 @@
"""
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

View File

@ -0,0 +1,229 @@
"""
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

View File

@ -0,0 +1,72 @@
"""
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

View File

@ -0,0 +1,33 @@
"""
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

View File

@ -0,0 +1,46 @@
"""
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

View File

@ -0,0 +1,114 @@
"""
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

View File

@ -0,0 +1,42 @@
"""
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')

View File

@ -0,0 +1,42 @@
"""
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')

View File

@ -0,0 +1,42 @@
"""
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')

View File

@ -0,0 +1,73 @@
"""
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

View File

@ -0,0 +1,51 @@
"""
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

View File

@ -0,0 +1,36 @@
"""
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

View File

@ -0,0 +1,44 @@
"""
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

View File

@ -0,0 +1,119 @@
"""
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

View File

@ -0,0 +1,70 @@
"""
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

View File

@ -0,0 +1,27 @@
"""
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')

View File

@ -0,0 +1,136 @@
"""
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

View File

@ -0,0 +1,125 @@
"""
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

View File

@ -0,0 +1,135 @@
"""
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

View File

@ -0,0 +1,70 @@
"""
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

View File

@ -0,0 +1,25 @@
"""
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

View File

@ -0,0 +1,84 @@
"""
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

View File

@ -0,0 +1,36 @@
"""
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

View File

@ -0,0 +1,33 @@
"""
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

View File

@ -0,0 +1,84 @@
"""
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)

View File

@ -0,0 +1,378 @@
"""
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

View File

@ -0,0 +1,278 @@
"""
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

View File

@ -0,0 +1,151 @@
! 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

View File

@ -0,0 +1,32 @@
"""
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)

View File

@ -0,0 +1,88 @@
"""
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))

View File

@ -0,0 +1,15 @@
"""
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')

View File

@ -0,0 +1,31 @@
"""
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))

View File

@ -0,0 +1,41 @@
"""
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')

View File

@ -0,0 +1,57 @@
"""
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'

View File

@ -0,0 +1,99 @@
"""
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

View File

@ -0,0 +1,196 @@
"""
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

View File

@ -0,0 +1,29 @@
"""
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

View File

@ -0,0 +1,137 @@
"""
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

View File

@ -0,0 +1,157 @@
"""
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

View File

@ -0,0 +1,79 @@
"""
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()

View File

@ -0,0 +1,97 @@
"""
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

View File

@ -0,0 +1,63 @@
"""
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

View File

@ -0,0 +1,95 @@
"""
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

View File

@ -0,0 +1,54 @@
"""
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

View File

@ -0,0 +1,152 @@
"""
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

View File

@ -0,0 +1,157 @@
"""
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

View File

@ -0,0 +1,184 @@
"""
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

View File

@ -0,0 +1,107 @@
"""
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()

View File

@ -0,0 +1,38 @@
"""
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()

View File

@ -0,0 +1,112 @@
"""
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

View File

@ -0,0 +1,48 @@
"""
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

View File

@ -0,0 +1,55 @@
"""
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]

View File

@ -0,0 +1,69 @@
"""
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)

View File

@ -0,0 +1,322 @@
"""
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'

View File

@ -0,0 +1,81 @@
"""
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

View File

@ -0,0 +1,54 @@
"""
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')

View File

@ -0,0 +1,39 @@
"""
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)

View File

@ -0,0 +1,42 @@
"""
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

View File

@ -0,0 +1,114 @@
"""
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

View File

@ -0,0 +1,39 @@
"""
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

View File

@ -0,0 +1,37 @@
"""
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)()

View File

@ -0,0 +1,35 @@
"""
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)

View File

@ -0,0 +1,79 @@
"""
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

View File

@ -0,0 +1,36 @@
"""
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)

View File

@ -0,0 +1,35 @@
"""
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)

View File

@ -0,0 +1,37 @@
"""
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)()

View File

@ -0,0 +1,78 @@
"""
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

View File

@ -0,0 +1,49 @@
"""
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

View File

@ -0,0 +1,144 @@
"""
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

View File

@ -0,0 +1,38 @@
"""
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

View File

@ -0,0 +1,141 @@
"""
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

View File

@ -0,0 +1,78 @@
"""
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

View File

@ -0,0 +1,38 @@
"""
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)()

View File

@ -0,0 +1,56 @@
"""
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)

View File

@ -0,0 +1,111 @@
"""
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