diff --git a/city_model_structure/building_demand/building_from_trimesh.py b/city_model_structure/building_demand/building_from_trimesh.py new file mode 100644 index 00000000..2c0b9e31 --- /dev/null +++ b/city_model_structure/building_demand/building_from_trimesh.py @@ -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) diff --git a/city_model_structure/building_demand/help.py b/city_model_structure/building_demand/help.py new file mode 100644 index 00000000..7670b030 --- /dev/null +++ b/city_model_structure/building_demand/help.py @@ -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) diff --git a/city_model_structure/city_model_structure/attributes/edge.py b/city_model_structure/city_model_structure/attributes/edge.py new file mode 100644 index 00000000..c1b36165 --- /dev/null +++ b/city_model_structure/city_model_structure/attributes/edge.py @@ -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 diff --git a/city_model_structure/city_model_structure/attributes/node.py b/city_model_structure/city_model_structure/attributes/node.py new file mode 100644 index 00000000..12dba288 --- /dev/null +++ b/city_model_structure/city_model_structure/attributes/node.py @@ -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 diff --git a/city_model_structure/city_model_structure/attributes/plane.py b/city_model_structure/city_model_structure/attributes/plane.py new file mode 100644 index 00000000..f653d6c7 --- /dev/null +++ b/city_model_structure/city_model_structure/attributes/plane.py @@ -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 diff --git a/city_model_structure/city_model_structure/attributes/point.py b/city_model_structure/city_model_structure/attributes/point.py new file mode 100644 index 00000000..71c3d987 --- /dev/null +++ b/city_model_structure/city_model_structure/attributes/point.py @@ -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 diff --git a/city_model_structure/city_model_structure/attributes/polygon.py b/city_model_structure/city_model_structure/attributes/polygon.py new file mode 100644 index 00000000..e82359b3 --- /dev/null +++ b/city_model_structure/city_model_structure/attributes/polygon.py @@ -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 diff --git a/city_model_structure/city_model_structure/attributes/polyhedron.py b/city_model_structure/city_model_structure/attributes/polyhedron.py new file mode 100644 index 00000000..f0f225c0 --- /dev/null +++ b/city_model_structure/city_model_structure/attributes/polyhedron.py @@ -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() diff --git a/city_model_structure/city_model_structure/attributes/schedule_value.py b/city_model_structure/city_model_structure/attributes/schedule_value.py new file mode 100644 index 00000000..87d422b2 --- /dev/null +++ b/city_model_structure/city_model_structure/attributes/schedule_value.py @@ -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 diff --git a/city_model_structure/city_model_structure/bixi_feature.py b/city_model_structure/city_model_structure/bixi_feature.py new file mode 100644 index 00000000..ae175a0c --- /dev/null +++ b/city_model_structure/city_model_structure/bixi_feature.py @@ -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 diff --git a/city_model_structure/city_model_structure/building.py b/city_model_structure/city_model_structure/building.py new file mode 100644 index 00000000..c04acb42 --- /dev/null +++ b/city_model_structure/city_model_structure/building.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/building_from_trimesh.py b/city_model_structure/city_model_structure/building_demand/building_from_trimesh.py new file mode 100644 index 00000000..2c0b9e31 --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/building_from_trimesh.py @@ -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) diff --git a/city_model_structure/city_model_structure/building_demand/help.py b/city_model_structure/city_model_structure/building_demand/help.py new file mode 100644 index 00000000..7670b030 --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/help.py @@ -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) diff --git a/city_model_structure/city_model_structure/building_demand/internal_gains.py b/city_model_structure/city_model_structure/building_demand/internal_gains.py new file mode 100644 index 00000000..7debf06b --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/internal_gains.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/layer.py b/city_model_structure/city_model_structure/building_demand/layer.py new file mode 100644 index 00000000..69860270 --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/layer.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/material.py b/city_model_structure/city_model_structure/building_demand/material.py new file mode 100644 index 00000000..3aee0735 --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/material.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/occupants.py b/city_model_structure/city_model_structure/building_demand/occupants.py new file mode 100644 index 00000000..f11cc770 --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/occupants.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/storey.py b/city_model_structure/city_model_structure/building_demand/storey.py new file mode 100644 index 00000000..ad3e841e --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/storey.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/surface.py b/city_model_structure/city_model_structure/building_demand/surface.py new file mode 100644 index 00000000..3c18f2fc --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/surface.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/thermal_boundary.py b/city_model_structure/city_model_structure/building_demand/thermal_boundary.py new file mode 100644 index 00000000..878a9124 --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/thermal_boundary.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/thermal_opening.py b/city_model_structure/city_model_structure/building_demand/thermal_opening.py new file mode 100644 index 00000000..9897820c --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/thermal_opening.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/thermal_zone.py b/city_model_structure/city_model_structure/building_demand/thermal_zone.py new file mode 100644 index 00000000..ddf23f4d --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/thermal_zone.py @@ -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 diff --git a/city_model_structure/city_model_structure/building_demand/usage_zone.py b/city_model_structure/city_model_structure/building_demand/usage_zone.py new file mode 100644 index 00000000..5ad104ca --- /dev/null +++ b/city_model_structure/city_model_structure/building_demand/usage_zone.py @@ -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 diff --git a/city_model_structure/city_model_structure/buildings_cluster.py b/city_model_structure/city_model_structure/buildings_cluster.py new file mode 100644 index 00000000..5ba7f74e --- /dev/null +++ b/city_model_structure/city_model_structure/buildings_cluster.py @@ -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 diff --git a/city_model_structure/city_model_structure/city.py b/city_model_structure/city_model_structure/city.py new file mode 100644 index 00000000..326a0c89 --- /dev/null +++ b/city_model_structure/city_model_structure/city.py @@ -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 diff --git a/city_model_structure/city_model_structure/city_object.py b/city_model_structure/city_model_structure/city_object.py new file mode 100644 index 00000000..f42a1c77 --- /dev/null +++ b/city_model_structure/city_model_structure/city_object.py @@ -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 diff --git a/city_model_structure/city_model_structure/city_objects_cluster.py b/city_model_structure/city_model_structure/city_objects_cluster.py new file mode 100644 index 00000000..780574a1 --- /dev/null +++ b/city_model_structure/city_model_structure/city_objects_cluster.py @@ -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 diff --git a/city_model_structure/city_model_structure/composting_plant.py b/city_model_structure/city_model_structure/composting_plant.py new file mode 100644 index 00000000..0030b4d0 --- /dev/null +++ b/city_model_structure/city_model_structure/composting_plant.py @@ -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 diff --git a/city_model_structure/city_model_structure/energy_systems/heat_pump.py b/city_model_structure/city_model_structure/energy_systems/heat_pump.py new file mode 100644 index 00000000..c6b059bc --- /dev/null +++ b/city_model_structure/city_model_structure/energy_systems/heat_pump.py @@ -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 diff --git a/city_model_structure/city_model_structure/energy_systems/pv_system.py b/city_model_structure/city_model_structure/energy_systems/pv_system.py new file mode 100644 index 00000000..e0facb47 --- /dev/null +++ b/city_model_structure/city_model_structure/energy_systems/pv_system.py @@ -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 diff --git a/city_model_structure/city_model_structure/iot/concordia_energy_sensor.py b/city_model_structure/city_model_structure/iot/concordia_energy_sensor.py new file mode 100644 index 00000000..88a88e51 --- /dev/null +++ b/city_model_structure/city_model_structure/iot/concordia_energy_sensor.py @@ -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') diff --git a/city_model_structure/city_model_structure/iot/concordia_gas_flow_sensor.py b/city_model_structure/city_model_structure/iot/concordia_gas_flow_sensor.py new file mode 100644 index 00000000..3dd7a7c0 --- /dev/null +++ b/city_model_structure/city_model_structure/iot/concordia_gas_flow_sensor.py @@ -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') diff --git a/city_model_structure/city_model_structure/iot/concordia_temperature_sensor.py b/city_model_structure/city_model_structure/iot/concordia_temperature_sensor.py new file mode 100644 index 00000000..ae1b1129 --- /dev/null +++ b/city_model_structure/city_model_structure/iot/concordia_temperature_sensor.py @@ -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') diff --git a/city_model_structure/city_model_structure/iot/sensor.py b/city_model_structure/city_model_structure/iot/sensor.py new file mode 100644 index 00000000..6dfad778 --- /dev/null +++ b/city_model_structure/city_model_structure/iot/sensor.py @@ -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 diff --git a/city_model_structure/city_model_structure/network.py b/city_model_structure/city_model_structure/network.py new file mode 100644 index 00000000..67759f54 --- /dev/null +++ b/city_model_structure/city_model_structure/network.py @@ -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 diff --git a/city_model_structure/city_model_structure/parts_consisting_building.py b/city_model_structure/city_model_structure/parts_consisting_building.py new file mode 100644 index 00000000..bb342321 --- /dev/null +++ b/city_model_structure/city_model_structure/parts_consisting_building.py @@ -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 diff --git a/city_model_structure/city_model_structure/subway_entrance.py b/city_model_structure/city_model_structure/subway_entrance.py new file mode 100644 index 00000000..b42dd880 --- /dev/null +++ b/city_model_structure/city_model_structure/subway_entrance.py @@ -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 diff --git a/city_model_structure/city_model_structure/transport/connection.py b/city_model_structure/city_model_structure/transport/connection.py new file mode 100644 index 00000000..9265a5df --- /dev/null +++ b/city_model_structure/city_model_structure/transport/connection.py @@ -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 diff --git a/city_model_structure/city_model_structure/transport/crossing.py b/city_model_structure/city_model_structure/transport/crossing.py new file mode 100644 index 00000000..5a2db9e2 --- /dev/null +++ b/city_model_structure/city_model_structure/transport/crossing.py @@ -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 diff --git a/city_model_structure/city_model_structure/transport/join.py b/city_model_structure/city_model_structure/transport/join.py new file mode 100644 index 00000000..a7f609b8 --- /dev/null +++ b/city_model_structure/city_model_structure/transport/join.py @@ -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') diff --git a/city_model_structure/city_model_structure/transport/lane.py b/city_model_structure/city_model_structure/transport/lane.py new file mode 100644 index 00000000..d89481d8 --- /dev/null +++ b/city_model_structure/city_model_structure/transport/lane.py @@ -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, -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 diff --git a/city_model_structure/city_model_structure/transport/phase.py b/city_model_structure/city_model_structure/transport/phase.py new file mode 100644 index 00000000..cc3f7a83 --- /dev/null +++ b/city_model_structure/city_model_structure/transport/phase.py @@ -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 diff --git a/city_model_structure/city_model_structure/transport/traffic_edge.py b/city_model_structure/city_model_structure/transport/traffic_edge.py new file mode 100644 index 00000000..6bfa3d39 --- /dev/null +++ b/city_model_structure/city_model_structure/transport/traffic_edge.py @@ -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 diff --git a/city_model_structure/city_model_structure/transport/traffic_light.py b/city_model_structure/city_model_structure/transport/traffic_light.py new file mode 100644 index 00000000..a5244eaa --- /dev/null +++ b/city_model_structure/city_model_structure/transport/traffic_light.py @@ -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 diff --git a/city_model_structure/city_model_structure/transport/traffic_network.py b/city_model_structure/city_model_structure/transport/traffic_network.py new file mode 100644 index 00000000..6bd46a52 --- /dev/null +++ b/city_model_structure/city_model_structure/transport/traffic_network.py @@ -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 diff --git a/city_model_structure/city_model_structure/transport/traffic_node.py b/city_model_structure/city_model_structure/transport/traffic_node.py new file mode 100644 index 00000000..1ff63de7 --- /dev/null +++ b/city_model_structure/city_model_structure/transport/traffic_node.py @@ -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 diff --git a/city_model_structure/city_model_structure/transport/walkway_node.py b/city_model_structure/city_model_structure/transport/walkway_node.py new file mode 100644 index 00000000..cde5f821 --- /dev/null +++ b/city_model_structure/city_model_structure/transport/walkway_node.py @@ -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 diff --git a/city_model_structure/city_model_structure/tree.py b/city_model_structure/city_model_structure/tree.py new file mode 100644 index 00000000..f1e0caff --- /dev/null +++ b/city_model_structure/city_model_structure/tree.py @@ -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 diff --git a/exports/exports/exports_factory.py b/exports/exports/exports_factory.py new file mode 100644 index 00000000..06d90099 --- /dev/null +++ b/exports/exports/exports_factory.py @@ -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) diff --git a/exports/exports/formats/energy_ade.py b/exports/exports/formats/energy_ade.py new file mode 100644 index 00000000..60c10af2 --- /dev/null +++ b/exports/exports/formats/energy_ade.py @@ -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 diff --git a/exports/exports/formats/idf.py b/exports/exports/formats/idf.py new file mode 100644 index 00000000..b956367e --- /dev/null +++ b/exports/exports/formats/idf.py @@ -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) diff --git a/exports/exports/formats/idf_files/Energy+.idd b/exports/exports/formats/idf_files/Energy+.idd new file mode 100644 index 00000000..12d2d762 --- /dev/null +++ b/exports/exports/formats/idf_files/Energy+.idd @@ -0,0 +1,103532 @@ +!IDD_Version 9.5.0 +!IDD_BUILD de239b2e5f +! ************************************************************************** +! This file is the Input Data Dictionary (IDD) for EnergyPlus. +! The IDD defines the syntax and data model for each type of input "Object." +! Lines in EnergyPlus input files (and IDD) are limited to 500 characters. +! +! Object Description +! ------------------ +! To define an object (a record with data), develop a key word that is unique +! Each data item to the object can be A (Alphanumeric string) or N (numeric) +! Number each A and N. This will show how the data items will be put into the +! arrays that are passed to the Input Processor "Get" (GetObjectItem) routines. +! All alpha fields are limited to 100 characters. Numeric fields should be +! valid numerics (can include such as 1.0E+05) and are placed into double +! precision variables. +! +! NOTE: Even though a field may be optional, a comma representing that field +! must be included (unless it is the last field in the object). Since the +! entire input is "field-oriented" and not "keyword-oriented", the EnergyPlus +! Input Processor must have some representation (even if blank) for each +! field. +! +! Object Documentation +! -------------------- +! In addition, the following special comments appear one per line and +! most are followed by a value. Comments may apply to a field or the object +! or a group of objects. +! +! Field-level comments: +! +! \field Name of field +! (should be succinct and readable, blanks are encouraged) +! +! \note Note describing the field and its valid values. If multiple lines, +! start each line with \note. Limit line length to 100 characters. +! +! \required-field To flag fields which must have a value. If the idf input is blank and +! there is a \default, then the default will be used. However, as of v8.6.0 +! the use of \required-field and \default on the same field is discouraged +! and instances with both have been changed. +! (this comment has no "value") +! +! \begin-extensible Marks the first field at which the object accepts an extensible +! field set. A fixed number of fields from this marker define the +! extensible field set, see the object code \extensible for +! more information. +! +! \units Units (must be from EnergyPlus standard units list) +! EnergyPlus units are standard SI units +! +! \ip-units IP-Units (for use by input processors with IP units) +! This is only used if the default conversion is not +! appropriate. +! +! \unitsBasedOnField For fields that may have multiple possible units, indicates +! the field in the object that can be used to determine +! the units. The field reference is in the A2 form. +! +! \minimum Minimum that includes the following value +! +! \minimum> Minimum that must be > than the following value +! +! \maximum Maximum that includes the following value +! +! \maximum< Maximum that must be < than the following value +! +! \default Default for the field (if N/A then omit entire line). If a default is +! added to an existing field, then \required-field should be removed if present. +! Defaults are filled in only if the field is within \min-fields, or the actual +! object is longer than this field. +! +! \deprecated This field is not really used and will be deleted from the object. +! The required information is gotten internally or +! not needed by the program. +! +! \autosizable Flag to indicate that this field can be used with the Auto +! Sizing routines to produce calculated results for the +! field. If a value follows this, then that will be used +! when the "Autosize" feature is flagged. To trigger +! autosizing for a field, enter Autosize as the field's +! value. Only applicable to numeric fields. +! +! \autocalculatable Flag to indicate that this field can be automatically +! calculated. To trigger auto calculation for a field, enter +! Autocalculate as the field's value. Only applicable to +! numeric fields. +! +! \type Type of data for the field - +! integer +! real +! alpha (arbitrary string), +! choice (alpha with specific list of choices, see +! \key) +! object-list (link to a list of objects defined elsewhere, +! see \object-list and \reference) +! external-list (uses a special list from an external source, +! see \external-list) +! node (name used in connecting HVAC components) +! +! \retaincase Retains the alphabetic case for alpha type fields +! +! \key Possible value for "\type choice" (blanks are significant) +! use multiple \key lines to indicate all valid choices +! +! \object-list Name of a list of user-provided object names that are valid +! entries for this field (used with "\reference") +! see Zone and BuildingSurface:Detailed objects below for +! examples. +! ** Note that a field may have multiple \object-list commands. +! +! \external-list The values for this field should be selected from a special +! list generated outside of the IDD file. The choices for the +! special lists are: +! autoRDDvariable +! autoRDDmeter +! autoRDDvariableMeter +! When one of these are selected the options for the field +! are taken from the RDD or MDD file or both. +! +! \reference Name of a list of names to which this object belongs +! used with "\type object-list" and with "\object-list" +! see Zone and BuildingSurface:Detailed objects below for +! examples: +! +! Zone, +! A1 , \field Name +! \type alpha +! \reference ZoneNames +! +! BuildingSurface:Detailed, +! A4 , \field Zone Name +! \note Zone the surface is a part of +! \type object-list +! \object-list ZoneNames +! +! For each zone, the field "Name" may be referenced +! by other objects, such as BuildingSurface:Detailed, so it is +! commented with "\reference ZoneNames" +! Fields that reference a zone name, such as BuildingSurface:Detailed's +! "Zone Name", are commented as +! "\type object-list" and "\object-list ZoneNames" +! ** Note that a field may have multiple \reference commands. +! ** This is useful if the object belongs to a small specific +! object-list as well as a larger more general object-list. +! +! Object-level comments: +! +! \memo Memo describing the object. If multiple lines, start each line +! with \memo. +! Limit line length to 100 characters. +! +! \unique-object To flag objects which should appear only once in an idf +! (this comment has no "value") +! +! \required-object To flag objects which are required in every idf +! (this comment has no "value") +! +! \min-fields Minimum number of fields that should be included in the +! object. If appropriate, the Input Processor will fill +! any missing fields with defaults (for numeric fields). +! It will also supply that number of fields to the "get" +! routines using blanks for alpha fields (note -- blanks +! may not be allowable for some alpha fields). +! +! \obsolete This object has been replaced though is kept (and is read) +! in the current version. Please refer to documentation as +! to the dispersal of the object. If this object is +! encountered in an IDF, the InputProcessor will post an +! appropriate message to the error file. +! usage: \obsolete New=>[New object name] +! +! \extensible:<#> This object is dynamically extensible -- meaning, if you +! change the IDD appropriately (if the object has a simple list +! structure -- just add items to the list arguments (i.e. BRANCH +! LIST). These will be automatically redimensioned and used during +! the simulation. <#> should be entered by the developer to signify +! how many of the last fields are needed to be extended (and EnergyPlus +! will attempt to auto-extend the object). The first field of the first +! instance of the extensible field set is marked with \begin-extensible. +! +! \begin-extensible See previous item, marks beginning of extensible fields in +! an object. +! +! \format The object should have a special format when saved in +! the IDF Editor with the special format option enabled. +! The options include SingleLine, Vertices, CompactSchedule, +! FluidProperties, ViewFactors, and Spectral. +! The SingleLine option puts all the fields for the object +! on a single line. The Vertices option is used in objects +! that use X, Y and Z fields to format those three fields +! on a single line. +! The CompactSchedule formats that specific object. +! The FluidProperty option formats long lists of fluid +! properties to ten values per line. +! The ViewFactor option formats three fields related to +! view factors per line. +! The Spectral option formats the four fields related to +! window glass spectral data per line. +! +! \reference-class-name Adds the name of the class to the reference list +! similar to \reference. +! +! Group-level comments: +! +! \group Name for a group of related objects +! +! +! Notes on comments +! ----------------- +! +! 1. If a particular comment is not applicable (such as units, or default) +! then simply omit the comment rather than indicating N/A. +! +! 2. Memos and notes should be brief (recommend 5 lines or less per block). +! More extensive explanations are expected to be in the user documentation +! +! Default IP conversions (no \ip-units necessary) +! $/(m3/s) => $/(ft3/min) 0.000472000059660808 +! $/(W/K) => $/(Btu/h-F) 0.52667614683731 +! $/kW => $/(kBtuh/h) 0.293083235638921 +! $/m2 => $/ft2 0.0928939733269818 +! $/m3 => $/ft3 0.0283127014102352 +! (kg/s)/W => (lbm/sec)/(Btu/hr) 0.646078115385742 +! 1/K => 1/F 0.555555555555556 +! 1/m => 1/ft 0.3048 +! A/K => A/F 0.555555555555556 +! C => F 1.8 (plus 32) +! cm => in 0.3937 +! cm2 => inch2 0.15500031000062 +! deltaC => deltaF 1.8 +! deltaC/hr => deltaF/hr 1.8 +! deltaJ/kg => deltaBtu/lb 0.0004299 +! g/GJ => lb/MWh 0.00793664091373665 +! g/kg => grains/lb 7 +! g/MJ => lb/MWh 7.93664091373665 +! g/mol => lb/mol 0.0022046 +! g/m-s => lb/ft-s 0.000671968949659 +! g/m-s-K => lb/ft-s-F 0.000373574867724868 +! GJ => ton-hrs 78.9889415481832 +! J => Wh 0.000277777777777778 +! J/K => Btu/F 526.565 +! J/kg => Btu/lb 0.00042986 (plus 7.686) +! J/kg-K => Btu/lb-F 0.000239005736137667 +! J/kg-K2 => Btu/lb-F2 0.000132889924714692 +! J/kg-K3 => Btu/lb-F3 7.38277359526066E-05 +! J/m2-K => Btu/ft2-F 4.89224766847393E-05 +! J/m3 => Btu/ft3 2.68096514745308E-05 +! J/m3-K => Btu/ft3-F 1.49237004739337E-05 +! K => R 1.8 +! K/m => F/ft 0.54861322767449 +! kg => lb 2.2046 +! kg/J => lb/Btu 2325.83774250441 +! kg/kg-K => lb/lb-F 0.555555555555556 +! kg/m => lb/ft 0.67196893069637 +! kg/m2 => lb/ft2 0.204794053596664 +! kg/m3 => lb/ft3 0.062428 +! kg/m-s => lb/ft-s 0.67196893069637 +! kg/m-s-K => lb/ft-s-F 0.373316072609094 +! kg/m-s-K2 => lb/ft-s-F2 0.207397818116164 +! kg/Pa-s-m2 => lb/psi-s-ft2 1412.00523459398 +! kg/s => lb/s 2.20462247603796 +! kg/s2 => lb/s2 2.2046 +! kg/s-m => lb/s-ft 0.67196893069637 +! kJ/kg => Btu/lb 0.429925 +! kPa => psi 0.145038 +! L/day => pint/day 2.11337629827348 +! L/GJ => gal/kWh 0.000951022349025202 +! L/kWh => pint/kWh 2.11337629827348 +! L/MJ => gal/kWh 0.951022349025202 +! lux => foot-candles 0.092902267 +! m => ft 3.28083989501312 +! m/hr => ft/hr 3.28083989501312 +! m/s => ft/min 196.850393700787 +! m/s => miles/hr 2.2369362920544 +! m/yr => inch/yr 39.3700787401575 +! m2 => ft2 10.7639104167097 +! m2/m => ft2/ft 3.28083989501312 +! m2/person => ft2/person 10.764961 +! m2/s => ft2/s 10.7639104167097 +! m2-K/W => ft2-F-hr/Btu 5.678263 +! m3 => ft3 35.3146667214886 +! m3 => gal 264.172037284185 +! m3/GJ => ft3/MWh 127.13292 +! m3/hr => ft3/hr 35.3146667214886 +! m3/hr-m2 => ft3/hr-ft2 3.28083989501312 +! m3/hr-person => ft3/hr-person 35.3146667214886 +! m3/kg => ft3/lb 16.018 +! m3/m2 => ft3/ft2 3.28083989501312 +! m3/MJ => ft3/kWh 127.13292 +! m3/person => ft3/person 35.3146667214886 +! m3/s => ft3/min 2118.88000328931 +! m3/s-m => ft3/min-ft 645.89 +! m3/s-m2 => ft3/min-ft2 196.85 +! m3/s-person => ft3/min-person 2118.6438 +! m3/s-W => (ft3/min)/(Btu/h) 621.099127332943 +! N-m => lbf-in 8.85074900525547 +! N-s/m2 => lbf-s/ft2 0.0208857913669065 +! Pa => psi 0.000145037743897283 +! percent/K => percent/F 0.555555555555556 +! person/m2 => person/ft2 0.0928939733269818 +! s/m => s/ft 0.3048 +! V/K => V/F 0.555555555555556 +! W => Btu/h 3.4121412858518 +! W/((m3/s)-Pa) => W/((gal/min)-ftH20) 0.188582274697355 +! W/((m3/s)-Pa) => W/((ft3/min)-inH2O) 0.117556910599482 +! W/(m3/s) => W/(ft3/min) 0.0004719475 +! W/K => Btu/h-F 1.89563404769544 +! W/m => Btu/h-ft 1.04072 +! W/m2 => Btu/h-ft2 0.316957210776545 +! W/m2 => W/ft2 0.09290304 +! W/m2-K => Btu/h-ft2-F 0.176110194261872 +! W/m2-K2 => Btu/h-ft2-F2 0.097826 +! W/m-K => Btu-in/h-ft2-F 6.93481276005548 +! W/m-K2 => Btu/h-F2-ft 0.321418310071648 +! W/m-K3 => Btu/h-F3-ft 0.178565727817582 +! W/person => Btu/h-person 3.4121412858518 +! +! Other conversions supported (needs the \ip-units code) +! +! kPa => inHg 0.29523 +! m => in 39.3700787401575 +! m3/hr => gal/hr 264.172037284185 +! m3/hr-m2 => gal/hr-ft2 24.5423853466941 +! m3/hr-person => gal/hr-person 264.172037284185 +! m3/m2 => gal/ft2 24.5423853466941 +! m3/person => gal/person 264.172037284185 +! m3/s => gal/min 15850.3222370511 +! m3/s-m => gal/min-ft 4831.17821785317 +! m3/s-W => (gal/min)/(Btu/h) 4645.27137336702 +! Pa => ftH2O 0.00033455 +! Pa => inH2O 0.00401463 +! Pa => inHg 0.00029613 +! Pa => Pa 1 +! W => W 1 +! W/(m3/s) => W/(gal/min) 0.0000630902 +! W/m2 => W/m2 1 +! W/m-K => Btu/h-ft-F 0.577796066000163 +! W/person => W/person 1 +! +! Units fields that are not translated +! $ +! 1/hr +! A +! A/V +! Ah +! Availability +! Control +! cycles/hr +! days +! deg +! dimensionless +! eV +! hh:mm +! hr +! J/J +! kg/kg +! kgWater/kgDryAir +! kmol +! kmol/s +! m3/m3 +! micron +! minutes +! Mode +! ms +! ohms +! percent +! ppm +! rev/min +! s +! V +! VA +! W/m2 or deg C +! W/m2, W or deg C +! W/s +! W/W +! years +! ************************************************************************** + +\group Simulation Parameters + +Version, + \memo Specifies the EnergyPlus version of the IDF file. + \unique-object + \format singleLine + A1 ; \field Version Identifier + \default 9.5 + +SimulationControl, + \unique-object + \memo Note that the following 3 fields are related to the Sizing:Zone, Sizing:System, + \memo and Sizing:Plant objects. Having these fields set to Yes but no corresponding + \memo Sizing object will not cause the sizing to be done. However, having any of these + \memo fields set to No, the corresponding Sizing object is ignored. + \memo Note also, if you want to do system sizing, you must also do zone sizing in the same + \memo run or an error will result. + \min-fields 7 + A1, \field Do Zone Sizing Calculation + \note If Yes, Zone sizing is accomplished from corresponding Sizing:Zone objects + \note and autosize fields. + \type choice + \key Yes + \key No + \default No + A2, \field Do System Sizing Calculation + \note If Yes, System sizing is accomplished from corresponding Sizing:System objects + \note and autosize fields. + \note If Yes, Zone sizing (previous field) must also be Yes. + \type choice + \key Yes + \key No + \default No + A3, \field Do Plant Sizing Calculation + \note If Yes, Plant sizing is accomplished from corresponding Sizing:Plant objects + \note and autosize fields. + \type choice + \key Yes + \key No + \default No + A4, \field Run Simulation for Sizing Periods + \note If Yes, SizingPeriod:* objects are executed and results from those may be displayed.. + \type choice + \key Yes + \key No + \default Yes + A5, \field Run Simulation for Weather File Run Periods + \note If Yes, RunPeriod:* objects are executed and results from those may be displayed.. + \type choice + \key Yes + \key No + \default Yes + A6, \field Do HVAC Sizing Simulation for Sizing Periods + \note If Yes, SizingPeriod:* objects are exectuted additional times for advanced sizing. + \note Currently limited to use with coincident plant sizing, see Sizing:Plant object + \type choice + \key Yes + \key No + \default No + N1; \field Maximum Number of HVAC Sizing Simulation Passes + \note the entire set of SizingPeriod:* objects may be repeated to fine tune size results + \note this input sets a limit on the number of passes that the sizing algorithms can repeate the set + \type integer + \minimum 1 + \default 1 + +PerformancePrecisionTradeoffs, + \unique-object + \memo This object enables users to choose certain options that speed up EnergyPlus simulation, + \memo but may lead to small decreases in accuracy of results. + A1, \field Use Coil Direct Solutions + \note If Yes, an analytical or empirical solution will be used to replace iterations in + \note the coil performance calculations. + \type choice + \key Yes + \key No + \default No + A2, \field Zone Radiant Exchange Algorithm + \note Determines which algorithm will be used to solve long wave radiant exchange among surfaces within a zone. + \type choice + \key ScriptF + \key CarrollMRT + \default ScriptF + A3, \field Override Mode + \note The increasing mode number roughly correspond with increased speed. A description of each mode + \note are shown in the documentation. When Advanced is selected the N1 field value is used. + \type choice + \key Normal + \key Mode01 + \key Mode02 + \key Mode03 + \key Mode04 + \key Mode05 + \key Mode06 + \key Mode07 + \key Advanced + \default Normal + N1, \field MaxZoneTempDiff + \note Maximum zone temperature change before HVAC timestep is shortened. + \note Only used when Override Mode is set to Advanced + \type real + \minimum 0.1 + \maximum 3.0 + \default 0.3 + N2; \field MaxAllowedDelTemp + \note Maximum surface temperature change before HVAC timestep is shortened. + \note Only used when Override Mode is set to Advanced + \type real + \minimum 0.002 + \maximum 0.1 + \default 0.002 + +Building, + \memo Describes parameters that are used during the simulation + \memo of the building. There are necessary correlations between the entries for + \memo this object and some entries in the Site:WeatherStation and + \memo Site:HeightVariation objects, specifically the Terrain field. + \unique-object + \required-object + \min-fields 8 + A1 , \field Name + \retaincase + \default NONE + N1 , \field North Axis + \note degrees from true North + \units deg + \type real + \default 0.0 + A2 , \field Terrain + \note Country=FlatOpenCountry | Suburbs=CountryTownsSuburbs | City=CityCenter | Ocean=body of water (5km) | Urban=Urban-Industrial-Forest + \type choice + \key Country + \key Suburbs + \key City + \key Ocean + \key Urban + \default Suburbs + N2 , \field Loads Convergence Tolerance Value + \note Loads Convergence Tolerance Value is a change in load from one warmup day to the next + \type real + \minimum> 0.0 + \maximum .5 + \default .04 + \units W + N3 , \field Temperature Convergence Tolerance Value + \units deltaC + \type real + \minimum> 0.0 + \maximum .5 + \default .4 + A3 , \field Solar Distribution + \note MinimalShadowing | FullExterior | FullInteriorAndExterior | FullExteriorWithReflections | FullInteriorAndExteriorWithReflections + \type choice + \key MinimalShadowing + \key FullExterior + \key FullInteriorAndExterior + \key FullExteriorWithReflections + \key FullInteriorAndExteriorWithReflections + \default FullExterior + N4 , \field Maximum Number of Warmup Days + \note EnergyPlus will only use as many warmup days as needed to reach convergence tolerance. + \note This field's value should NOT be set less than 25. + \type integer + \minimum> 0 + \default 25 + N5 ; \field Minimum Number of Warmup Days + \note The minimum number of warmup days that produce enough temperature and flux history + \note to start EnergyPlus simulation for all reference buildings was suggested to be 6. + \note However this can lead to excessive run times as warmup days can be repeated needlessly. + \note For faster execution rely on the convergence criteria to detect when warmup is complete. + \note When this field is greater than the maximum warmup days defined previous field + \note the maximum number of warmup days will be reset to the minimum value entered here. + \note Warmup days will be set to be the value you entered. The default is 1. + \type integer + \minimum> 0 + \default 1 + +ShadowCalculation, + \unique-object + \memo This object is used to control details of the solar, shading, and daylighting models + \extensible:1 + A1 , \field Shading Calculation Method + \note Select between CPU-based polygon clipping method, the GPU-based pixel counting method, + \note or importing from external shading data. + \note If PixelCounting is selected and GPU hardware (or GPU emulation) is not available, a warning will be + \note displayed and EnergyPlus will revert to PolygonClipping. + \note If Scheduled is chosen, the External Shading Fraction Schedule Name is required + \note in SurfaceProperty:LocalEnvironment. + \note If Imported is chosen, the Schedule:File:Shading object is required. + \type choice + \key PolygonClipping + \key PixelCounting + \key Scheduled + \key Imported + \default PolygonClipping + A2 , \field Shading Calculation Update Frequency Method + \note choose calculation frequency method. note that Timestep is only needed for certain cases + \note and can increase execution time significantly. + \type choice + \key Periodic + \key Timestep + \default Periodic + N1 , \field Shading Calculation Update Frequency + \type integer + \minimum 1 + \default 20 + \note enter number of days + \note this field is only used if the previous field is set to Periodic + \note warning issued if >31 + N2 , \field Maximum Figures in Shadow Overlap Calculations + \note Number of allowable figures in shadow overlap in PolygonClipping calculations + \type integer + \minimum 200 + \default 15000 + A3 , \field Polygon Clipping Algorithm + \note Advanced Feature. Internal default is SutherlandHodgman + \note Refer to InputOutput Reference and Engineering Reference for more information + \type choice + \key ConvexWeilerAtherton + \key SutherlandHodgman + \key SlaterBarskyandSutherlandHodgman + \default SutherlandHodgman + N3 , \field Pixel Counting Resolution + \note Number of pixels in both dimensions of the surface rendering + \type integer + \default 512 + A4 , \field Sky Diffuse Modeling Algorithm + \note Advanced Feature. Internal default is SimpleSkyDiffuseModeling + \note If you have shading elements that change transmittance over the + \note year, you may wish to choose the detailed method. + \note Refer to InputOutput Reference and Engineering Reference for more information + \type choice + \key SimpleSkyDiffuseModeling + \key DetailedSkyDiffuseModeling + \default SimpleSkyDiffuseModeling + A5 , \field Output External Shading Calculation Results + \type choice + \key Yes + \key No + \default No + \note If Yes is chosen, the calculated external shading fraction results will be saved to an external CSV file with surface names as the column headers. + A6 , \field Disable Self-Shading Within Shading Zone Groups + \note If Yes, self-shading will be disabled from all exterior surfaces in a given Shading Zone Group to surfaces within + \note the same Shading Zone Group. + \note If both Disable Self-Shading Within Shading Zone Groups and Disable Self-Shading From Shading Zone Groups to Other Zones = Yes, + \note then all self-shading from exterior surfaces will be disabled. + \note If only one of these fields = Yes, then at least one Shading Zone Group must be specified, or this field will be ignored. + \note Shading from Shading:* surfaces, overhangs, fins, and reveals will not be disabled. + \type choice + \key Yes + \key No + \default No + A7 , \field Disable Self-Shading From Shading Zone Groups to Other Zones + \note If Yes, self-shading will be disabled from all exterior surfaces in a given Shading Zone Group to all other zones in the model. + \note If both Disable Self-Shading Within Shading Zone Groups and Disable Self-Shading From Shading Zone Groups to Other Zones = Yes, + \note then all self-shading from exterior surfaces will be disabled. + \note If only one of these fields = Yes, then at least one Shading Zone Group must be specified, or this field will be ignored. + \note Shading from Shading:* surfaces, overhangs, fins, and reveals will not be disabled. + \type choice + \key Yes + \key No + \default No + A8 , \field Shading Zone Group 1 ZoneList Name + \note Specifies a group of zones which are controlled by the Disable Self-Shading fields. + \type object-list + \object-list ZoneListNames + \begin-extensible + A9 , \field Shading Zone Group 2 ZoneList Name + \type object-list + \object-list ZoneListNames + A10, \field Shading Zone Group 3 ZoneList Name + \type object-list + \object-list ZoneListNames + A11, \field Shading Zone Group 4 ZoneList Name + \type object-list + \object-list ZoneListNames + A12, \field Shading Zone Group 5 ZoneList Name + \type object-list + \object-list ZoneListNames + A13; \field Shading Zone Group 6 ZoneList Name + \type object-list + \object-list ZoneListNames + +SurfaceConvectionAlgorithm:Inside, + \memo Default indoor surface heat transfer convection algorithm to be used for all zones + \unique-object + \format singleLine + A1 ; \field Algorithm + \type choice + \key Simple + \key TARP + \key CeilingDiffuser + \key AdaptiveConvectionAlgorithm + \key ASTMC1340 + \default TARP + \note Simple = constant value natural convection (ASHRAE) + \note TARP = variable natural convection based on temperature difference (ASHRAE, Walton) + \note CeilingDiffuser = ACH-based forced and mixed convection correlations + \note for ceiling diffuser configuration with simple natural convection limit + \note AdaptiveConvectionAlgorithm = dynamic selection of convection models based on conditions + \note ASTMC1340 = mixed convection correlations based on heat flow direction, + \note surface tilt angle, surface characteristic length, and air speed past the surface. + +SurfaceConvectionAlgorithm:Outside, + \memo Default outside surface heat transfer convection algorithm to be used for all zones + \unique-object + \format singleLine + A1 ; \field Algorithm + \type choice + \key SimpleCombined + \key TARP + \key MoWiTT + \key DOE-2 + \key AdaptiveConvectionAlgorithm + \default DOE-2 + \note SimpleCombined = Combined radiation and convection coefficient using simple ASHRAE model + \note TARP = correlation from models developed by ASHRAE, Walton, and Sparrow et. al. + \note MoWiTT = correlation from measurements by Klems and Yazdanian for smooth surfaces + \note DOE-2 = correlation from measurements by Klems and Yazdanian for rough surfaces + \note AdaptiveConvectionAlgorithm = dynamic selection of correlations based on conditions + +HeatBalanceAlgorithm, + \memo Determines which Heat Balance Algorithm will be used ie. + \memo CTF (Conduction Transfer Functions), + \memo EMPD (Effective Moisture Penetration Depth with Conduction Transfer Functions). + \memo Advanced/Research Usage: CondFD (Conduction Finite Difference) + \memo Advanced/Research Usage: ConductionFiniteDifferenceSimplified + \memo Advanced/Research Usage: HAMT (Combined Heat And Moisture Finite Element) + \unique-object + \format singleLine + A1 , \field Algorithm + \type choice + \key ConductionTransferFunction + \key MoisturePenetrationDepthConductionTransferFunction + \key ConductionFiniteDifference + \key CombinedHeatAndMoistureFiniteElement + \default ConductionTransferFunction + N1 , \field Surface Temperature Upper Limit + \type real + \minimum 200 + \default 200 + \units C + N2 , \field Minimum Surface Convection Heat Transfer Coefficient Value + \units W/m2-K + \default 0.1 + \minimum> 0.0 + N3 ; \field Maximum Surface Convection Heat Transfer Coefficient Value + \units W/m2-K + \default 1000 + \minimum 1.0 + +HeatBalanceSettings:ConductionFiniteDifference, + \memo Determines settings for the Conduction Finite Difference + \memo algorithm for surface heat transfer modeling. + \unique-object + A1 , \field Difference Scheme + \type choice + \key CrankNicholsonSecondOrder + \key FullyImplicitFirstOrder + \default FullyImplicitFirstOrder + N1 , \field Space Discretization Constant + \note increase or decrease number of nodes + \type real + \default 3 + N2 , \field Relaxation Factor + \type real + \default 1.0 + \minimum 0.01 + \maximum 1.0 + N3 ; \field Inside Face Surface Temperature Convergence Criteria + \type real + \default 0.002 + \minimum 1.0E-7 + \maximum 0.01 + +ZoneAirHeatBalanceAlgorithm, + \memo Determines which algorithm will be used to solve the zone air heat balance. + \unique-object + \format singleLine + \min-fields 1 + A1 ; \field Algorithm + \type choice + \key ThirdOrderBackwardDifference + \key AnalyticalSolution + \key EulerMethod + \default ThirdOrderBackwardDifference + +ZoneAirContaminantBalance, + \memo Determines which contaminant concentration will be simulates. + \unique-object + \format singleLine + A1 , \field Carbon Dioxide Concentration + \note If Yes, CO2 simulation will be performed. + \type choice + \key Yes + \key No + \default No + A2 , \field Outdoor Carbon Dioxide Schedule Name + \note Schedule values should be in parts per million (ppm) + \type object-list + \object-list ScheduleNames + A3 , \field Generic Contaminant Concentration + \note If Yes, generic contaminant simulation will be performed. + \type choice + \key Yes + \key No + \default No + A4 ; \field Outdoor Generic Contaminant Schedule Name + \note Schedule values should be generic contaminant concentration in parts per + \note million (ppm) + \type object-list + \object-list ScheduleNames + +ZoneAirMassFlowConservation, + \memo Enforces the zone air mass flow balance by either adjusting zone mixing object flow only, + \memo adjusting zone total return flow only, zone mixing and the zone total return flows, + \memo or adjusting the zone total return and zone mixing object flows. Zone infiltration flow air + \memo flow is increased or decreased depending user selection in the infiltration treatment method. + \memo If either of zone mixing or zone return flow adjusting methods or infiltration is active, + \memo then the zone air mass flow balance calculation will attempt to enforce conservation of + \memo mass for each zone. If flow balancing method is "None" and infiltration is "None", then the + \memo zone air mass flow calculation defaults to assume self-balanced simple flow mixing and + \memo infiltration objects. + \unique-object + \min-fields 3 + A1, \field Adjust Zone Mixing and Return For Air Mass Flow Balance + \note If "AdjustMixingOnly", zone mixing object flow rates are adjusted to balance the zone air mass + \note flow and zone infiltration air flow may be increased or decreased if required in order to balance + \note the zone air mass flow. If "AdjustReturnOnly", zone total return flow rate is adjusted to balance + \note the zone air mass flow and zone infiltration air flow may be increased or decreased if required + \note in order to balance the zone air mass flow. If "AdjustMixingThenReturn", first the zone mixing + \note objects flow rates are adjusted to balance the zone air flow, second zone total return flow rate + \note is adjusted and zone infiltration air flow may be increased or decreased if required in order to + \note balance the zone air mass flow. If "AdjustReturnThenMixing", first zone total return flow rate is + \note adjusted to balance the zone air flow, second the zone mixing object flow rates are adjusted and + \note infiltration air flow may be increased or decreased if required in order to balance the zone + \note air mass flow. + \type choice + \key AdjustMixingOnly + \key AdjustReturnOnly + \key AdjustMixingThenReturn + \key AdjustReturnThenMixing + \key None + \default None + A2, \field Infiltration Balancing Method + \note This input field allows user to choose how zone infiltration flow is treated during + \note the zone air mass flow balance calculation. + \type choice + \key AddInfiltrationFlow + \key AdjustInfiltrationFlow + \key None + \default AddInfiltrationFlow + \note AddInfiltrationFlow may add infiltration to the base flow specified in the + \note infiltration object to balance the zone air mass flow. The additional infiltration + \note air mass flow is not self-balanced. The base flow is assumed to be self-balanced. + \note AdjustInfiltrationFlow may adjust the base flow calculated using + \note the base flow specified in the infiltration object to balance the zone air mass flow. If it + \note If no adjustment is required, then the base infiltration is assumed to be self-balanced. + \note None will make no changes to the base infiltration flow. + A3; \field Infiltration Balancing Zones + \note This input field allows user to choose which zones are included in infiltration balancing. + \note MixingSourceZonesOnly allows infiltration balancing only in zones which as source zones for mixing + \note which also have an infiltration object defined. + \note AllZones allows infiltration balancing in any zone which has an infiltration object defined. + \type choice + \key MixingSourceZonesOnly + \key AllZones + \default MixingSourceZonesOnly + +ZoneCapacitanceMultiplier:ResearchSpecial, + \format singleLine + \memo Multiplier altering the relative capacitance of the air compared to an empty zone + \min-fields 6 + A1 , \field Name + \required-field + \type alpha + A2 , \field Zone or ZoneList Name + \type object-list + \object-list ZoneAndZoneListNames + \note If this field is left blank, the multipliers are applied to all the zones not specified + N1 , \field Temperature Capacity Multiplier + \type real + \default 1.0 + \minimum> 0.0 + \note Used to alter the capacitance of zone air with respect to heat or temperature + N2 , \field Humidity Capacity Multiplier + \type real + \default 1.0 + \minimum> 0.0 + \note Used to alter the capacitance of zone air with respect to moisture or humidity ratio + N3 , \field Carbon Dioxide Capacity Multiplier + \type real + \default 1.0 + \minimum> 0.0 + \note Used to alter the capacitance of zone air with respect to zone air carbon dioxide concentration + N4 ; \field Generic Contaminant Capacity Multiplier + \type real + \default 1.0 + \minimum> 0.0 + \note Used to alter the capacitance of zone air with respect to zone air generic contaminant concentration + +Timestep, + \memo Specifies the "basic" timestep for the simulation. The + \memo value entered here is also known as the Zone Timestep. This is used in + \memo the Zone Heat Balance Model calculation as the driving timestep for heat + \memo transfer and load calculations. + \unique-object + \format singleLine + N1 ; \field Number of Timesteps per Hour + \note Number in hour: normal validity 4 to 60: 6 suggested + \note Must be evenly divisible into 60 + \note Allowable values include 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, and 60 + \note Normal 6 is minimum as lower values may cause inaccuracies + \note A minimum value of 20 is suggested for both ConductionFiniteDifference + \note and CombinedHeatAndMoistureFiniteElement surface heat balance algorithms + \note A minimum of 12 is suggested for simulations involving a Vegetated Roof (Material:RoofVegetation). + \default 6 + \type integer + \minimum 1 + \maximum 60 + +ConvergenceLimits, + \memo Specifies limits on HVAC system simulation timesteps and iterations. + \memo This item is an advanced feature that should be used only with caution. + \unique-object + N1 , \field Minimum System Timestep + \units minutes + \type integer + \note 0 sets the minimum to the zone timestep (ref: Timestep) + \note 1 is normal (ratchet down to 1 minute) + \note setting greater than zone timestep (in minutes) will effectively set to zone timestep + \minimum 0 + \maximum 60 + N2 , \field Maximum HVAC Iterations + \type integer + \default 20 + \minimum 1 + N3 , \field Minimum Plant Iterations + \note Controls the minimum number of plant system solver iterations within a single HVAC iteration + \note Larger values will increase runtime but might improve solution accuracy for complicated plant systems + \note Complex plants include: several interconnected loops, heat recovery, thermal load following generators, etc. + \type integer + \default 2 + \minimum 1 + N4 ; \field Maximum Plant Iterations + \note Controls the maximum number of plant system solver iterations within a single HVAC iteration + \note Smaller values might decrease runtime but could decrease solution accuracy for complicated plant systems + \type integer + \default 8 + \minimum 2 + +HVACSystemRootFindingAlgorithm, + \memo Specifies a HVAC system solver algorithm to find a root + \unique-object + A1 , \field Algorithm + \type choice + \key RegulaFalsi + \key Bisection + \key BisectionThenRegulaFalsi + \key RegulaFalsiThenBisection + \key Alternation + \default RegulaFalsi + N1 ; \field Number of Iterations Before Algorithm Switch + \note This field is used when RegulaFalsiThenBisection or BisectionThenRegulaFalsi is + \note entered. When iteration number is greater than the value, algorithm switches. + \type integer + \default 5 + +\group Compliance Objects + +Compliance:Building, + \memo Building level inputs related to compliance to building standards, building codes, and beyond energy code programs. + \unique-object + \min-fields 1 + N1; \field Building Rotation for Appendix G + \note Additional degrees of rotation to be used with the requirement in ASHRAE Standard 90.1 Appendix G + \note that states that the baseline building should be rotated in four directions. + \units deg + \type real + \default 0.0 + +\group Location and Climate + +Site:Location, + \memo Specifies the building's location. Only one location is allowed. + \memo Weather data file location, if it exists, will override this object. + \unique-object + \min-fields 5 + A1 , \field Name + \required-field + \type alpha + N1 , \field Latitude + \units deg + \minimum -90.0 + \maximum +90.0 + \default 0.0 + \note + is North, - is South, degree minutes represented in decimal (i.e. 30 minutes is .5) + \type real + N2 , \field Longitude + \units deg + \minimum -180.0 + \maximum +180.0 + \default 0.0 + \note - is West, + is East, degree minutes represented in decimal (i.e. 30 minutes is .5) + \type real + N3 , \field Time Zone + \note basic these limits on the WorldTimeZone Map (2003) + \units hr + \minimum -12.0 + \maximum +14.0 + \default 0.0 + \note Time relative to GMT. Decimal hours. + \type real + N4 ; \field Elevation + \units m + \minimum -300.0 + \maximum< 8900.0 + \default 0.0 + \type real + +Site:VariableLocation, + \memo Captures the scheduling of a moving/reorienting building, or more likely a vessel + \unique-object + \min-fields 1 + A1 , \field Name + \required-field + \type alpha + A2 , \field Building Location Latitude Schedule + \note The name of a schedule that defines the latitude of the building at any time. + \note If not entered, the latitude defined in the Site:Location, or the default + \note latitude, will be used for the entirety of the simulation + \type object-list + \object-list ScheduleNames + A3 , \field Building Location Longitude Schedule + \note The name of a schedule that defines the longitude of the building at any time. + \note If not entered, the longitude defined in the Site:Location, or the default + \note longitude, will be used for the entirety of the simulation + \type object-list + \object-list ScheduleNames + A4 ; \field Building Location Orientation Schedule + \note The name of a schedule that defines the orientation of the building at any time. + \note This orientation is based on a change from the original orientation. -- NEED TO REFINE THIS + \note If not entered, the original orientation will be used for the entirety of the simulation + \type object-list + \object-list ScheduleNames + +SizingPeriod:DesignDay, + \memo The design day object creates the parameters for the program to create + \memo the 24 hour weather profile that can be used for sizing as well as + \memo running to test the other simulation parameters. Parameters in this + \memo include a date (month and day), a day type (which uses the appropriate + \memo schedules for either sizing or simple tests), min/max temperatures, + \memo wind speeds, and solar radiation values. + A1, \field Name + \type alpha + \required-field + \reference RunPeriodsAndDesignDays + N1, \field Month + \required-field + \minimum 1 + \maximum 12 + \type integer + N2, \field Day of Month + \required-field + \minimum 1 + \maximum 31 + \type integer + \note must be valid for Month field + A2, \field Day Type + \required-field + \note Day Type selects the schedules appropriate for this design day + \type choice + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + \key Holiday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + N3, \field Maximum Dry-Bulb Temperature + \note This field is required when field "Dry-Bulb Temperature Range Modifier Type" + \note is not "TemperatureProfileSchedule". + \units C + \minimum -90 + \maximum 70 + \type real + N4, \field Daily Dry-Bulb Temperature Range + \note Must still produce appropriate maximum dry-bulb (within range) + \note This field is not needed if Dry-Bulb Temperature Range Modifier Type + \note is "delta". + \units deltaC + \minimum 0 + \default 0 + \type real + A3, \field Dry-Bulb Temperature Range Modifier Type + \note Type of modifier to the dry-bulb temperature calculated for the timestep + \type choice + \key MultiplierSchedule + \key DifferenceSchedule + \key TemperatureProfileSchedule + \key DefaultMultipliers + \default DefaultMultipliers + A4, \field Dry-Bulb Temperature Range Modifier Day Schedule Name + \type object-list + \object-list DayScheduleNames + \note Only used when previous field is "MultiplierSchedule", "DifferenceSchedule" or + \note "TemperatureProfileSchedule". + \note For type "MultiplierSchedule" the hour/time interval values should specify + \note the fraction (0-1) of the dry-bulb temperature range to be subtracted + \note from the maximum dry-bulb temperature for each timestep in the day + \note For type "DifferenceSchedule" the values should specify a number to be subtracted + \note from the maximum dry-bulb temperature for each timestep in the day. + \note Note that numbers in the difference schedule cannot be negative as that + \note would result in a higher maximum than the maximum previously specified. + \note For type "TemperatureProfileSchedule" the values should specify the actual dry-bulb + \note temperature for each timestep in the day. + A5, \field Humidity Condition Type + \note values/schedules indicated here and in subsequent fields create the humidity + \note values in the 24 hour design day conditions profile. + \type choice + \key WetBulb + \key DewPoint + \key HumidityRatio + \key Enthalpy + \key RelativeHumiditySchedule + \key WetBulbProfileMultiplierSchedule + \key WetBulbProfileDifferenceSchedule + \key WetBulbProfileDefaultMultipliers + \default WetBulb + N5, \field Wetbulb or DewPoint at Maximum Dry-Bulb + \note Wetbulb or dewpoint temperature coincident with the maximum temperature. + \note Required only if field Humidity Condition Type is "Wetbulb", "Dewpoint", + \note "WetBulbProfileMultiplierSchedule", "WetBulbProfileDifferenceSchedule", + \note or "WetBulbProfileDefaultMultipliers" + \type real + \units C + A6, \field Humidity Condition Day Schedule Name + \type object-list + \object-list DayScheduleNames + \note Only used when Humidity Condition Type is "RelativeHumiditySchedule", + \note "WetBulbProfileMultiplierSchedule", or "WetBulbProfileDifferenceSchedule" + \note For type "RelativeHumiditySchedule", the hour/time interval values should specify + \note relative humidity (percent) from 0.0 to 100.0. + \note For type "WetBulbProfileMultiplierSchedule" the hour/time interval values should specify + \note the fraction (0-1) of the wet-bulb temperature range to be subtracted from the + \note maximum wet-bulb temperature for each timestep in the day (units = Fraction) + \note For type "WetBulbProfileDifferenceSchedule" the values should specify a number to be subtracted + \note from the maximum wet-bulb temperature for each timestep in the day. (units = deltaC) + N6, \field Humidity Ratio at Maximum Dry-Bulb + \note Humidity ratio coincident with the maximum temperature (constant humidity ratio throughout day). + \note Required only if field Humidity Condition Type is "HumidityRatio". + \type real + \units kgWater/kgDryAir + N7, \field Enthalpy at Maximum Dry-Bulb + \note Enthalpy coincident with the maximum temperature. + \note Required only if field Humidity Condition Type is "Enthalpy". + \type real + \units J/kg + N8, \field Daily Wet-Bulb Temperature Range + \units deltaC + \note Required only if Humidity Condition Type = "WetbulbProfileMultiplierSchedule" or + \note "WetBulbProfileDefaultMultipliers" + N9, \field Barometric Pressure + \note This field's value is also checked against the calculated "standard barometric pressure" + \note for the location. If out of range (>10%) or blank, then is replaced by standard value. + \units Pa + \minimum 31000 + \maximum 120000 + \type real + \ip-units inHg + N10, \field Wind Speed + \required-field + \units m/s + \minimum 0 + \maximum 40 + \ip-units miles/hr + \type real + N11, \field Wind Direction + \required-field + \units deg + \minimum 0 + \maximum 360 + \note North=0.0 East=90.0 + \note 0 and 360 are the same direction. + \type real + A7, \field Rain Indicator + \note Yes is raining (all day), No is not raining + \type choice + \key Yes + \key No + \default No + A8, \field Snow Indicator + \type choice + \key Yes + \key No + \default No + \note Yes is Snow on Ground, No is no Snow on Ground + A9, \field Daylight Saving Time Indicator + \note Yes -- use schedules modified for Daylight Saving Time Schedules. + \note No - do not use schedules modified for Daylight Saving Time Schedules + \type choice + \key Yes + \key No + \default No + A10, \field Solar Model Indicator + \type choice + \key ASHRAEClearSky + \key ZhangHuang + \key Schedule + \key ASHRAETau + \key ASHRAETau2017 + \default ASHRAEClearSky + A11, \field Beam Solar Day Schedule Name + \note if Solar Model Indicator = Schedule, then beam schedule name (for day) + \type object-list + \object-list DayScheduleNames + A12, \field Diffuse Solar Day Schedule Name + \note if Solar Model Indicator = Schedule, then diffuse schedule name (for day) + \type object-list + \object-list DayScheduleNames + N12, \field ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) + \units dimensionless + \note Required if Solar Model Indicator = ASHRAETau or ASHRAETau2017 + \note ASHRAETau2017 solar model can be used with 2013 and 2017 HOF matching taub + \minimum 0 + \maximum 1.2 + \default 0 + N13, \field ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) + \units dimensionless + \note Required if Solar Model Indicator = ASHRAETau or ASHRAETau2017 + \note ASHRAETau2017 solar model can be used with 2013 and 2017 HOF matching taud + \minimum 0 + \maximum 3 + \default 0 + N14, \field Sky Clearness + \note Used if Sky Model Indicator = ASHRAEClearSky or ZhangHuang + \minimum 0.0 + \maximum 1.2 + \default 0.0 + \note 0.0 is totally unclear, 1.0 is totally clear + \type real + N15, \field Maximum Number Warmup Days + \note If used this design day will be run with a custom limit on the maximum number of days that are repeated for warmup. + \note Limiting the number of warmup days can improve run time. + \type integer + A13; \field Begin Environment Reset Mode + \note If used this can control if you want the thermal history to be reset at the beginning of the design day. + \note When using a series of similiar design days, this field can be used to retain warmup state from the previous design day. + \type choice + \key FullResetAtBeginEnvironment + \key SuppressAllBeginEnvironmentResets + \default FullResetAtBeginEnvironment + +SizingPeriod:WeatherFileDays, + \memo Use a weather file period for design sizing calculations. + A1 , \field Name + \reference RunPeriodsAndDesignDays + \required-field + \note user supplied name for reporting + N1 , \field Begin Month + \required-field + \minimum 1 + \maximum 12 + \type integer + N2 , \field Begin Day of Month + \required-field + \minimum 1 + \maximum 31 + \type integer + N3 , \field End Month + \required-field + \minimum 1 + \maximum 12 + \type integer + N4 , \field End Day of Month + \required-field + \minimum 1 + \maximum 31 + \type integer + A2 , \field Day of Week for Start Day + \note =[|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|SummerDesignDay|WinterDesignDay| + \note |CustomDay1|CustomDay2]; + \note if you use SummerDesignDay or WinterDesignDay or the CustomDays then this will apply + \note to the whole period; other days (i.e., Monday) will signify a start day and + \note normal sequence of subsequent days + \default Monday + \type choice + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + A3, \field Use Weather File Daylight Saving Period + \note If yes or blank, use daylight saving period as specified on Weatherfile. + \note If no, do not use the daylight saving period as specified on the Weatherfile. + \type choice + \default Yes + \key Yes + \key No + A4; \field Use Weather File Rain and Snow Indicators + \type choice + \key Yes + \key No + \default Yes + +SizingPeriod:WeatherFileConditionType, + \memo Use a weather file period for design sizing calculations. + \memo EPW weather files are created with typical and extreme periods + \memo created heuristically from the weather file data. For more + \memo details on these periods, see AuxiliaryPrograms document. + A1 , \field Name + \required-field + \reference RunPeriodsAndDesignDays + \note user supplied name for reporting + A2 , \field Period Selection + \required-field + \retaincase + \note Following is a list of all possible types of Extreme and Typical periods that + \note might be identified in the Weather File. Not all possible types are available + \note for all weather files. + \type choice + \key SummerExtreme + \key SummerTypical + \key WinterExtreme + \key WinterTypical + \key AutumnTypical + \key SpringTypical + \key WetSeason + \key DrySeason + \key NoDrySeason + \key NoWetSeason + \key TropicalHot + \key TropicalCold + \key NoDrySeasonMax + \key NoDrySeasonMin + \key NoWetSeasonMax + \key NoWetSeasonMin + A3 , \field Day of Week for Start Day + \note =[|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|SummerDesignDay|WinterDesignDay| + \note |CustomDay1|CustomDay2]; + \note if you use SummerDesignDay or WinterDesignDay or the CustomDays then this will apply + \note to the whole period; other days (i.e., Monday) will signify a start day and + \note normal sequence of subsequent days + \default Monday + \type choice + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + A4, \field Use Weather File Daylight Saving Period + \note If yes or blank, use daylight saving period as specified on Weatherfile. + \note If no, do not use the daylight saving period as specified on the Weatherfile. + \type choice + \default Yes + \key Yes + \key No + A5; \field Use Weather File Rain and Snow Indicators + \type choice + \key Yes + \key No + \default Yes + +RunPeriod, + \memo Specify a range of dates and other parameters for a simulation. + \memo Multiple run periods may be input, but they may not overlap. + \min-fields 7 + A1 , \field Name + \required-field + \reference RunPeriodsAndDesignDays + \note descriptive name (used in reporting mainly) + \note Cannot be not blank and must be unique + N1 , \field Begin Month + \required-field + \minimum 1 + \maximum 12 + \type integer + N2 , \field Begin Day of Month + \required-field + \minimum 1 + \maximum 31 + \type integer + N3, \field Begin Year + \note Start year of the simulation, if this field is specified it must agree with the Day of Week for Start Day + \note If this field is blank, the year will be selected to match the weekday, which is Sunday if not specified + N4 , \field End Month + \required-field + \minimum 1 + \maximum 12 + \type integer + N5 , \field End Day of Month + \required-field + \minimum 1 + \maximum 31 + \type integer + N6, \field End Year + \note end year of simulation, if specified + A2 , \field Day of Week for Start Day + \note =[Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday]; + \note If no year is input, this field will default to Sunday + \note If a year is input and this field is blank, the correct weekday is determined + \type choice + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + A3, \field Use Weather File Holidays and Special Days + \note If yes or blank, use holidays as specified on Weatherfile. + \note If no, do not use the holidays specified on the Weatherfile. + \note Note: You can still specify holidays/special days using the RunPeriodControl:SpecialDays object(s). + \type choice + \default Yes + \key Yes + \key No + A4, \field Use Weather File Daylight Saving Period + \note If yes or blank, use daylight saving period as specified on Weatherfile. + \note If no, do not use the daylight saving period as specified on the Weatherfile. + \type choice + \default Yes + \key Yes + \key No + A5, \field Apply Weekend Holiday Rule + \note if yes and single day holiday falls on weekend, "holiday" occurs on following Monday + \type choice + \key Yes + \key No + \default No + A6, \field Use Weather File Rain Indicators + \type choice + \key Yes + \key No + \default Yes + A7, \field Use Weather File Snow Indicators + \type choice + \key Yes + \key No + \default Yes + A8; \field Treat Weather as Actual + \type choice + \key Yes + \key No + \default No + +RunPeriodControl:SpecialDays, + \min-fields 4 + \memo This object sets up holidays/special days to be used during weather file + \memo run periods. (These are not used with SizingPeriod:* objects.) + \memo Depending on the value in the run period, days on the weather file may also + \memo be used. However, the weather file specification will take precedence over + \memo any specification shown here. (No error message on duplicate days or overlapping + \memo days). + A1, \field Name + \required-field + A2, \field Start Date + \required-field + \note Dates can be several formats: + \note / (month/day) + \note + \note + \note in in + \note can be January, February, March, April, May, June, July, August, September, October, November, December + \note Months can be the first 3 letters of the month + \note can be Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday + \note can be 1 or 1st, 2 or 2nd, etc. up to 5(?) + N1, \field Duration + \units days + \minimum 1 + \maximum 366 + \default 1 + A3; \field Special Day Type + \note Special Day Type selects the schedules appropriate for each day so labeled + \type choice + \key Holiday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + \default Holiday + +RunPeriodControl:DaylightSavingTime, + \unique-object + \min-fields 2 + \memo This object sets up the daylight saving time period for any RunPeriod. + \memo Ignores any daylight saving time period on the weather file and uses this definition. + \memo These are not used with SizingPeriod:DesignDay objects. + \memo Use with SizingPeriod:WeatherFileDays object can be controlled in that object. + A1, \field Start Date + \required-field + A2; \field End Date + \required-field + \note Dates can be several formats: + \note / (month/day) + \note + \note + \note in in + \note can be January, February, March, April, May, June, July, August, September, October, November, December + \note Months can be the first 3 letters of the month + \note can be Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday + \note can be 1 or 1st, 2 or 2nd, etc. up to 5(?) + +WeatherProperty:SkyTemperature, + \memo This object is used to override internal sky temperature calculations. + A1, \field Name + \note blank in this field will apply to all run periods (that is, all objects= + \note SizingPeriod:WeatherFileDays, SizingPeriod:WeatherFileConditionType or RunPeriod + \note otherwise, this name must match one of the environment object names. + \type object-list + \object-list RunPeriodsAndDesignDays + A2, \field Calculation Type + \required-field + \note The field indicates that the sky temperature will be imported from external schedules or calculated by alternative methods other than default. + \type choice + \key ClarkAllen + \key Brunt + \key Idso + \key BerdahlMartin + \key ScheduleValue + \key DifferenceScheduleDryBulbValue + \key DifferenceScheduleDewPointValue + \default ClarkAllen + A3, \field Schedule Name + \note if name matches a SizingPeriod:DesignDay, put in a day schedule of this name + \note if name is for a SizingPeriod:WeatherFileDays, SizingPeriod:WeatherFileConditionType or + \note RunPeriod, put in a full year schedule that covers the appropriate days. + \note Required if Calculation Type is ScheduleValue, DifferenceScheduleDryBulbValue or DifferenceScheduleDewPointValue. + \type object-list + \object-list DayScheduleNames + \object-list ScheduleNames + A4; \field Use Weather File Horizontal IR + \note If yes or blank, use Horizontal IR values from weather file when present, otherwise use the specified sky model. + \note If no, always use the specified sky model and ignore the horizontal IR values from the weather file. + \note For Calculation Type = ScheduleValue, DifferenceScheduleDryBulbValue or DifferenceScheduleDewPointValue, this field is ignored and the scheduled values are used. + \type choice + \default Yes + \key Yes + \key No + +Site:WeatherStation, + \unique-object + \memo This object should only be used for non-standard weather data. Standard weather data + \memo such as TMY2, IWEC, and ASHRAE design day data are all measured at the + \memo default conditions and do not require this object. + N1 , \field Wind Sensor Height Above Ground + \type real + \units m + \default 10.0 + \minimum> 0.0 + N2 , \field Wind Speed Profile Exponent + \type real + \default 0.14 + \minimum 0.0 + N3 , \field Wind Speed Profile Boundary Layer Thickness + \type real + \units m + \default 270.0 + \minimum 0.0 + N4 ; \field Air Temperature Sensor Height Above Ground + \type real + \units m + \default 1.5 + \minimum 0.0 + +Site:HeightVariation, + \unique-object + \memo This object is used if the user requires advanced control over height-dependent + \memo variations in wind speed and temperature. When this object is not present, the default model + \memo for temperature dependence on height is used, and the wind speed is modeled according + \memo to the Terrain field of the BUILDING object. + N1 , \field Wind Speed Profile Exponent + \note Set to zero for no wind speed dependence on height. + \type real + \default 0.22 + \minimum 0.0 + N2 , \field Wind Speed Profile Boundary Layer Thickness + \type real + \units m + \default 370.0 + \minimum> 0.0 + N3 ; \field Air Temperature Gradient Coefficient + \note Set to zero for no air temperature dependence on height. + \type real + \units K/m + \default 0.0065 + \minimum 0.0 + +Site:GroundTemperature:BuildingSurface, + \memo These temperatures are specifically for those surfaces that have the outside environment + \memo of "Ground". Documentation about what values these should be is located in the + \memo Auxiliary programs document (Ground Heat Transfer) as well as the InputOutput Reference. + \memo CAUTION - Do not use the "undisturbed" ground temperatures from the weather data. + \memo These values are too extreme for the soil under a conditioned building. + \memo For best results, use the Slab or Basement program to calculate custom monthly + \memo average ground temperatures (see Auxiliary Programs). For typical commercial + \memo buildings in the USA, a reasonable default value is 2C less than the average indoor space temperature. + \unique-object + \min-fields 12 + \format singleLine + N1 , \field January Ground Temperature + \units C + \type real + \default 18 + N2 , \field February Ground Temperature + \units C + \type real + \default 18 + N3 , \field March Ground Temperature + \units C + \type real + \default 18 + N4 , \field April Ground Temperature + \units C + \type real + \default 18 + N5 , \field May Ground Temperature + \units C + \type real + \default 18 + N6 , \field June Ground Temperature + \units C + \type real + \default 18 + N7 , \field July Ground Temperature + \units C + \type real + \default 18 + N8 , \field August Ground Temperature + \units C + \type real + \default 18 + N9 , \field September Ground Temperature + \units C + \type real + \default 18 + N10, \field October Ground Temperature + \units C + \type real + \default 18 + N11, \field November Ground Temperature + \units C + \type real + \default 18 + N12; \field December Ground Temperature + \units C + \type real + \default 18 + +Site:GroundTemperature:FCfactorMethod, + \memo These temperatures are specifically for underground walls and ground floors + \memo defined with the C-factor and F-factor methods, and should be close to the + \memo monthly average outdoor air temperature delayed by 3 months for the location. + \unique-object + \min-fields 12 + \format singleLine + N1 , \field January Ground Temperature + \units C + \type real + \default 13 + N2 , \field February Ground Temperature + \units C + \type real + \default 13 + N3 , \field March Ground Temperature + \units C + \type real + \default 13 + N4 , \field April Ground Temperature + \units C + \type real + \default 13 + N5 , \field May Ground Temperature + \units C + \type real + \default 13 + N6 , \field June Ground Temperature + \units C + \type real + \default 13 + N7 , \field July Ground Temperature + \units C + \type real + \default 13 + N8 , \field August Ground Temperature + \units C + \type real + \default 13 + N9 , \field September Ground Temperature + \units C + \type real + \default 13 + N10, \field October Ground Temperature + \units C + \type real + \default 13 + N11, \field November Ground Temperature + \units C + \type real + \default 13 + N12; \field December Ground Temperature + \units C + \type real + \default 13 + +Site:GroundTemperature:Shallow, + \memo These temperatures are specifically for the Surface Ground Heat Exchanger and + \memo should probably be close to the average outdoor air temperature for the location. + \memo They are not used in other models. + \unique-object + \min-fields 12 + \format singleLine + N1 , \field January Surface Ground Temperature + \units C + \type real + \default 13 + N2 , \field February Surface Ground Temperature + \units C + \type real + \default 13 + N3 , \field March Surface Ground Temperature + \units C + \type real + \default 13 + N4 , \field April Surface Ground Temperature + \units C + \type real + \default 13 + N5 , \field May Surface Ground Temperature + \units C + \type real + \default 13 + N6 , \field June Surface Ground Temperature + \units C + \type real + \default 13 + N7 , \field July Surface Ground Temperature + \units C + \type real + \default 13 + N8 , \field August Surface Ground Temperature + \units C + \type real + \default 13 + N9 , \field September Surface Ground Temperature + \units C + \type real + \default 13 + N10, \field October Surface Ground Temperature + \units C + \type real + \default 13 + N11, \field November Surface Ground Temperature + \units C + \type real + \default 13 + N12; \field December Surface Ground Temperature + \units C + \type real + \default 13 + +Site:GroundTemperature:Deep, + \memo These temperatures are specifically for the ground heat exchangers that would use + \memo "deep" (3-4 m depth) ground temperatures for their heat source. + \memo They are not used in other models. + \unique-object + \min-fields 12 + \format singleLine + N1 , \field January Deep Ground Temperature + \units C + \type real + \default 16 + N2 , \field February Deep Ground Temperature + \units C + \type real + \default 16 + N3 , \field March Deep Ground Temperature + \units C + \type real + \default 16 + N4 , \field April Deep Ground Temperature + \units C + \type real + \default 16 + N5 , \field May Deep Ground Temperature + \units C + \type real + \default 16 + N6 , \field June Deep Ground Temperature + \units C + \type real + \default 16 + N7 , \field July Deep Ground Temperature + \units C + \type real + \default 16 + N8 , \field August Deep Ground Temperature + \units C + \type real + \default 16 + N9 , \field September Deep Ground Temperature + \units C + \type real + \default 16 + N10, \field October Deep Ground Temperature + \units C + \type real + \default 16 + N11, \field November Deep Ground Temperature + \units C + \type real + \default 16 + N12; \field December Deep Ground Temperature + \units C + \type real + \default 16 + +Site:GroundTemperature:Undisturbed:FiniteDifference, + \memo Undisturbed ground temperature object using a + \memo detailed finite difference 1-D model + \min-fields 7 + A1, \field Name + \required-field + \reference UndisturbedGroundTempModels + N1, \field Soil Thermal Conductivity + \required-field + \type real + \units W/m-K + \minimum> 0.0 + N2, \field Soil Density + \required-field + \type real + \units kg/m3 + \minimum> 0.0 + N3, \field Soil Specific Heat + \required-field + \type real + \units J/kg-K + \minimum> 0.0 + N4, \field Soil Moisture Content Volume Fraction + \type real + \units percent + \minimum 0 + \maximum 100 + \default 30 + N5, \field Soil Moisture Content Volume Fraction at Saturation + \type real + \units percent + \minimum 0 + \maximum 100 + \default 50 + N6; \field Evapotranspiration Ground Cover Parameter + \type real + \units dimensionless + \minimum 0 + \maximum 1.5 + \default 0.4 + \note This specifies the ground cover effects during evapotranspiration + \note calculations. The value roughly represents the following cases: + \note = 0 : concrete or other solid, non-permeable ground surface material + \note = 0.5 : short grass, much like a manicured lawn + \note = 1 : standard reference state (12 cm grass) + \note = 1.5 : wild growth + +Site:GroundTemperature:Undisturbed:KusudaAchenbach, + \memo Undisturbed ground temperature object using the + \memo Kusuda-Achenbach 1965 correlation. + \min-fields 7 + A1, \field Name + \required-field + \reference UndisturbedGroundTempModels + N1, \field Soil Thermal Conductivity + \required-field + \type real + \units W/m-K + \minimum> 0.0 + N2, \field Soil Density + \required-field + \type real + \units kg/m3 + \minimum> 0.0 + N3, \field Soil Specific Heat + \required-field + \type real + \units J/kg-K + \minimum> 0.0 + N4, \field Average Soil Surface Temperature + \type real + \units C + \note Annual average surface temperature + \note If left blank the Site:GroundTemperature:Shallow object must be included in the input + \note The soil temperature, amplitude, and phase shift must all be included or omitted together + N5, \field Average Amplitude of Surface Temperature + \type real + \units deltaC + \minimum 0 + \note Annual average surface temperature variation from average. + \note If left blank the Site:GroundTemperature:Shallow object must be included in the input + \note The soil temperature, amplitude, and phase shift must all be included or omitted together + N6; \field Phase Shift of Minimum Surface Temperature + \type real + \units days + \minimum 0 + \maximum< 365 + \note The phase shift of minimum surface temperature, or the day + \note of the year when the minimum surface temperature occurs. + \note If left blank the Site:GroundTemperature:Shallow object must be included in the input + \note The soil temperature, amplitude, and phase shift must all be included or omitted together + +Site:GroundTemperature:Undisturbed:Xing, + \memo Undisturbed ground temperature object using the + \memo Xing 2014 2 harmonic parameter model. + \min-fields 9 + A1, \field Name + \required-field + \reference UndisturbedGroundTempModels + N1, \field Soil Thermal Conductivity + \required-field + \type real + \units W/m-K + \minimum> 0.0 + N2, \field Soil Density + \required-field + \type real + \units kg/m3 + \minimum> 0.0 + N3, \field Soil Specific Heat + \required-field + \type real + \units J/kg-K + \minimum> 0.0 + N4, \field Average Soil Surface Tempeature + \required-field + \type real + \units C + N5, \field Soil Surface Temperature Amplitude 1 + \required-field + \type real + \units deltaC + N6, \field Soil Surface Temperature Amplitude 2 + \required-field + \type real + \units deltaC + N7, \field Phase Shift of Temperature Amplitude 1 + \required-field + \type real + \units days + \maximum< 365 + N8; \field Phase Shift of Temperature Amplitude 2 + \required-field + \type real + \units days + \maximum< 365 + +Site:GroundDomain:Slab, + \memo Ground-coupled slab model for on-grade and + \memo in-grade cases with or without insulation. + A1, \field Name + \required-field + N1, \field Ground Domain Depth + \type real + \default 10 + \units m + \minimum> 0.0 + N2, \field Aspect Ratio + \type real + \default 1 + N3, \field Perimeter Offset + \type real + \default 5 + \units m + \minimum> 0.0 + N4, \field Soil Thermal Conductivity + \type real + \default 1.5 + \units W/m-K + \minimum> 0.0 + N5, \field Soil Density + \type real + \default 2800 + \units kg/m3 + \minimum> 0.0 + N6, \field Soil Specific Heat + \type real + \default 850 + \units J/kg-K + \minimum> 0.0 + N7, \field Soil Moisture Content Volume Fraction + \type real + \units percent + \minimum 0 + \maximum 100 + \default 30 + N8, \field Soil Moisture Content Volume Fraction at Saturation + \type real + \units percent + \minimum 0 + \maximum 100 + \default 50 + A2, \field Undisturbed Ground Temperature Model Type + \required-field + \type choice + \key Site:GroundTemperature:Undisturbed:FiniteDifference + \key Site:GroundTemperature:Undisturbed:KusudaAchenbach + \key Site:GroundTemperature:Undisturbed:Xing + A3, \field Undisturbed Ground Temperature Model Name + \required-field + \type object-list + \object-list UndisturbedGroundTempModels + N9, \field Evapotranspiration Ground Cover Parameter + \type real + \minimum 0 + \maximum 1.5 + \default 0.4 + \note This specifies the ground cover effects during evapotranspiration + \note calculations. The value roughly represents the following cases: + \note = 0 : concrete or other solid, non-permeable ground surface material + \note = 0.5 : short grass, much like a manicured lawn + \note = 1 : standard reference state (12 cm grass) + \note = 1.5 : wild growth + A4, \field Slab Boundary Condition Model Name + \required-field + \type object-list + \object-list OSCMNames + A5, \field Slab Location + \required-field + \type choice + \key InGrade + \key OnGrade + \note This field specifies whether the slab is located "in-grade" or "on-grade" + A6, \field Slab Material Name + \type object-list + \object-list MaterialName + \note Only applicable for the in-grade case + A7, \field Horizontal Insulation + \type choice + \key Yes + \key No + \default No + \note This field specifies the presence of insulation beneath the slab. + \note Only required for in-grade case. + A8, \field Horizontal Insulation Material Name + \type object-list + \object-list MaterialName + \note This field specifies the horizontal insulation material. + A9, \field Horizontal Insulation Extents + \type choice + \key Full + \key Perimeter + \default Full + \note This field specifies whether the horizontal insulation fully insulates + \note the surface or is perimeter only insulation + N10, \field Perimeter Insulation Width + \type real + \units m + \minimum> 0.0 + \note This field specifies the width of the underfloor perimeter insulation + A10, \field Vertical Insulation + \type choice + \key Yes + \key No + \default No + \note This field specifies the presence of vertical insulation at the slab edge. + A11, \field Vertical Insulation Material Name + \type object-list + \object-list MaterialName + \note This field specifies the vertical insulation material. + N11, \field Vertical Insulation Depth + \type real + \units m + \minimum> 0.0 + \note Only used when including vertical insulation + \note This field specifies the depth of the vertical insulation + A12, \field Simulation Timestep + \type choice + \key Timestep + \key Hourly + \default Hourly + \note This field specifies the ground domain simulation timestep. + N12, \field Geometric Mesh Coefficient + \type real + \minimum 1.0 + \maximum 2.0 + \default 1.6 + N13; \field Mesh Density Parameter + \type integer + \minimum 4 + \default 6 + +Site:GroundDomain:Basement, + \memo Ground-coupled basement model for simulating basements + \memo or other underground zones. + A1, \field Name + \required-field + N1, \field Ground Domain Depth + \type real + \default 10 + \units m + \minimum> 0.0 + \note The depth from ground surface to the deep ground boundary of the domain. + N2, \field Aspect Ratio + \type real + \default 1 + \note This defines the height to width ratio of the basement zone. + N3, \field Perimeter Offset + \type real + \default 5 + \units m + \minimum> 0.0 + \note The distance from the basement wall edge to the edge of the ground domain + N4, \field Soil Thermal Conductivity + \type real + \default 1.5 + \units W/m-K + \minimum> 0.0 + N5, \field Soil Density + \type real + \default 2800 + \units kg/m3 + \minimum> 0.0 + N6, \field Soil Specific Heat + \type real + \default 850 + \units J/kg-K + \minimum> 0.0 + N7, \field Soil Moisture Content Volume Fraction + \type real + \units percent + \minimum 0 + \maximum 100 + \default 30 + N8, \field Soil Moisture Content Volume Fraction at Saturation + \type real + \units percent + \minimum 0 + \maximum 100 + \default 50 + A2, \field Undisturbed Ground Temperature Model Type + \required-field + \type choice + \key Site:GroundTemperature:Undisturbed:FiniteDifference + \key Site:GroundTemperature:Undisturbed:KusudaAchenbach + \key Site:GroundTemperature:Undisturbed:Xing + A3, \field Undisturbed Ground Temperature Model Name + \required-field + \type object-list + \object-list UndisturbedGroundTempModels + N9, \field Evapotranspiration Ground Cover Parameter + \type real + \minimum 0 + \maximum 1.5 + \default 0.4 + \note This specifies the ground cover effects during evapotranspiration + \note calculations. The value roughly represents the following cases: + \note = 0 : concrete or other solid, non-permeable ground surface material + \note = 0.5 : short grass, much like a manicured lawn + \note = 1 : standard reference state (12 cm grass) + \note = 1.5 : wild growth + A4, \field Basement Floor Boundary Condition Model Name + \required-field + \type object-list + \object-list OSCMNames + A5, \field Horizontal Insulation + \type choice + \key Yes + \key No + \default No + \note This field specifies the presence of insulation beneath the basement floor. + A6, \field Horizontal Insulation Material Name + \type object-list + \object-list MaterialName + A7, \field Horizontal Insulation Extents + \type choice + \key Perimeter + \key Full + \default Full + \note This field specifies whether the horizontal insulation fully insulates + \note the surface or is perimeter only insulation + N10, \field Perimeter Horizontal Insulation Width + \type real + \units m + \minimum> 0.0 + \note Width of horizontal perimeter insulation measured from + \note foundation wall inside surface. + N11, \field Basement Wall Depth + \type real + \units m + \minimum> 0.0 + \note Depth measured from ground surface. + A8, \field Basement Wall Boundary Condition Model Name + \required-field + \type object-list + \object-list OSCMNames + A9, \field Vertical Insulation + \type choice + \key Yes + \key No + \default No + A10, \field Basement Wall Vertical Insulation Material Name + \type object-list + \object-list MaterialName + N12, \field Vertical Insulation Depth + \type real + \units m + \minimum> 0.0 + \note Depth measured from the ground surface. + A11, \field Simulation Timestep + \type choice + \key Timestep + \key Hourly + \default Hourly + \note This field specifies the basement domain simulation interval. + N13; \field Mesh Density Parameter + \type integer + \default 4 + \minimum 2 + +Site:GroundReflectance, + \memo Specifies the ground reflectance values used to calculate ground reflected solar. + \memo The ground reflectance can be further modified when snow is on the ground + \memo by Site:GroundReflectance:SnowModifier. + \unique-object + \min-fields 12 + \format singleLine + N1 , \field January Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N2 , \field February Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N3 , \field March Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N4 , \field April Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N5 , \field May Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N6 , \field June Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N7 , \field July Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N8 , \field August Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N9 , \field September Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N10 , \field October Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N11 , \field November Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + N12 ; \field December Ground Reflectance + \default 0.2 + \type real + \minimum 0.0 + \maximum 1.0 + \units dimensionless + +Site:GroundReflectance:SnowModifier, + \memo Specifies ground reflectance multipliers when snow resident on the ground. + \memo These multipliers are applied to the "normal" ground reflectances specified + \memo in Site:GroundReflectance. + N1, \field Ground Reflected Solar Modifier + \minimum 0.0 + \default 1.0 + \note Value for modifying the "normal" ground reflectance when Snow is on ground + \note when calculating the "Ground Reflected Solar Radiation Value" + \note a value of 1.0 here uses the "normal" ground reflectance + \note Ground Reflected Solar = (BeamSolar*CosSunZenith + DiffuseSolar)*GroundReflectance + \note This would be further modified by the Snow Ground Reflectance Modifier when Snow was on the ground + \note When Snow on ground, effective GroundReflectance is normal GroundReflectance*"Ground Reflectance Snow Modifier" + \note Ground Reflectance achieved in this manner will be restricted to [0.0,1.0] + N2; \field Daylighting Ground Reflected Solar Modifier + \minimum 0.0 + \default 1.0 + \note Value for modifying the "normal" daylighting ground reflectance when Snow is on ground + \note when calculating the "Ground Reflected Solar Radiation Value" + \note a value of 1.0 here uses the "normal" ground reflectance + \note Ground Reflected Solar = (BeamSolar*CosSunZenith + DiffuseSolar)*GroundReflectance + \note This would be further modified by the Snow Ground Reflectance Modifier when Snow was on the ground + \note When Snow on ground, effective GroundReflectance is normal GroundReflectance*"Daylighting Ground Reflectance Snow Modifier" + \note Ground Reflectance achieved in this manner will be restricted to [0.0,1.0] + +Site:WaterMainsTemperature, + \memo Used to calculate water mains temperatures delivered by underground water main pipes. + \memo Water mains temperatures are a function of outdoor climate conditions + \memo and vary with time of year. + A1 , \field Calculation Method + \required-field + \type choice + \key Schedule + \key Correlation + \key CorrelationFromWeatherFile + \default CorrelationFromWeatherFile + \note If calculation method is CorrelationFromWeatherFile, the two numeric input + \note fields are ignored. Instead, EnergyPlus calculates them from weather file. + A2 , \field Temperature Schedule Name + \type object-list + \object-list ScheduleNames + N1 , \field Annual Average Outdoor Air Temperature + \note If calculation method is CorrelationFromWeatherFile or Schedule, this input + \note field is ignored. + \type real + \units C + N2 ; \field Maximum Difference In Monthly Average Outdoor Air Temperatures + \note If calculation method is CorrelationFromWeatherFile or Schedule, this input + \note field is ignored. + \type real + \units deltaC + \minimum 0 + +Site:Precipitation, + \memo Used to describe the amount of water precipitation at the building site. + \memo Precipitation includes both rain and the equivalent water content of snow. + A1, \field Precipitation Model Type + \type choice + \key ScheduleAndDesignLevel + N1, \field Design Level for Total Annual Precipitation + \note meters of water per year used for design level + \units m/yr + A2, \field Precipitation Rates Schedule Name + \type object-list + \object-list ScheduleNames + \note Schedule values in meters of water per hour + \note values should be non-negative + N2; \field Average Total Annual Precipitation + \note meters of water per year from average weather statistics + \minimum 0 + \units m/yr + +RoofIrrigation, + \memo Used to describe the amount of irrigation on the ecoroof surface over the course + \memo of the simulation runperiod. + A1, \field Irrigation Model Type + \type choice + \key Schedule + \key SmartSchedule + \note SmartSchedule will not allow irrigation when soil is already moist. + \note Current threshold set at 30% of saturation. + A2, \field Irrigation Rate Schedule Name + \type object-list + \object-list ScheduleNames + \note Schedule values in meters of water per hour + \note values should be non-negative + N1; \field Irrigation Maximum Saturation Threshold + \note Used with SmartSchedule to set the saturation level at which no + \note irrigation is allowed. + \units percent + \minimum 0.0 + \maximum 100.0 + \default 40.0 + +Site:SolarAndVisibleSpectrum, + \memo If this object is omitted, the default solar and visible spectrum data will be used. + \unique-object + A1, \field Name + \required-field + \type alpha + A2, \field Spectrum Data Method + \note The method specifies which of the solar and visible spectrum data to use in the calculations. + \note Choices: Default - existing hard-wired spectrum data in EnergyPlus. + \note UserDefined - user specified spectrum data referenced by the next two fields + \type choice + \key Default + \key UserDefined + \default Default + A3, \field Solar Spectrum Data Object Name + \type object-list + \object-list SpectrumDataNames + A4; \field Visible Spectrum Data Object Name + \type object-list + \object-list SpectrumDataNames + +Site:SpectrumData, + \min-fields 8 + \memo Spectrum Data Type is followed by up to 107 sets of normal-incidence measured values of + \memo [wavelength, spectrum] for wavelengths covering the solar (0.25 to 2.5 microns) or visible + \memo spectrum (0.38 to 0.78 microns) + \extensible:2 + A1, \field Name + \required-field + \type alpha + \reference SpectrumDataNames + A2, \field Spectrum Data Type + \required-field + \type choice + \key Solar + \key Visible + N1, \field Wavelength + \type real + \units micron + N2, \field Spectrum + \type real + N3, \field Wavelength + \type real + \units micron + N4, \field Spectrum + \type real + N5, \field Wavelength + \begin-extensible + \type real + \units micron + N6, \field Spectrum + \type real + N7, N8, \note fields as indicated + N9, N10, \note fields as indicated + N11, N12, \note fields as indicated + N13, N14, \note fields as indicated + N15, N16, \note fields as indicated + N17, N18, \note fields as indicated + N19, N20, \note fields as indicated + N21, N22, \note fields as indicated + N23, N24, \note fields as indicated + N25, N26, \note fields as indicated + N27, N28, \note fields as indicated + N29, N30, \note fields as indicated + N31, N32, \note fields as indicated + N33, N34, \note fields as indicated + N35, N36, \note fields as indicated + N37, N38, \note fields as indicated + N39, N40, \note fields as indicated + N41, N42, \note fields as indicated + N43, N44, \note fields as indicated + N45, N46, \note fields as indicated + N47, N48, \note fields as indicated + N49, N50, \note fields as indicated + N51, N52, \note fields as indicated + N53, N54, \note fields as indicated + N55, N56, \note fields as indicated + N57, N58, \note fields as indicated + N59, N60, \note fields as indicated + N61, N62, \note fields as indicated + N63, N64, \note fields as indicated + N65, N66, \note fields as indicated + N67, N68, \note fields as indicated + N69, N70, \note fields as indicated + N71, N72, \note fields as indicated + N73, N74, \note fields as indicated + N75, N76, \note fields as indicated + N77, N78, \note fields as indicated + N79, N80, \note fields as indicated + N81, N82, \note fields as indicated + N83, N84, \note fields as indicated + N85, N86, \note fields as indicated + N87, N88, \note fields as indicated + N89, N90, \note fields as indicated + N91, N92, \note fields as indicated + N93, N94, \note fields as indicated + N95, N96, \note fields as indicated + N97, N98, \note fields as indicated + N99, N100, \note fields as indicated + N101, N102, \note fields as indicated + N103, N104, \note fields as indicated + N105, N106, \note fields as indicated + N107, N108, \note fields as indicated + N109, N110, \note fields as indicated + N111, N112, \note fields as indicated + N113, N114, \note fields as indicated + N115, N116, \note fields as indicated + N117, N118, \note fields as indicated + N119, N120, \note fields as indicated + N121, N122, \note fields as indicated + N123, N124, \note fields as indicated + N125, N126, \note fields as indicated + N127, N128, \note fields as indicated + N129, N130, \note fields as indicated + N131, N132, \note fields as indicated + N133, N134, \note fields as indicated + N135, N136, \note fields as indicated + N137, N138, \note fields as indicated + N139, N140, \note fields as indicated + N141, N142, \note fields as indicated + N143, N144, \note fields as indicated + N145, N146, \note fields as indicated + N147, N148, \note fields as indicated + N149, N150, \note fields as indicated + N151, N152, \note fields as indicated + N153, N154, \note fields as indicated + N155, N156, \note fields as indicated + N157, N158, \note fields as indicated + N159, N160, \note fields as indicated + N161, N162, \note fields as indicated + N163, N164, \note fields as indicated + N165, N166, \note fields as indicated + N167, N168, \note fields as indicated + N169, N170, \note fields as indicated + N171, N172, \note fields as indicated + N173, N174, \note fields as indicated + N175, N176, \note fields as indicated + N177, N178, \note fields as indicated + N179, N180, \note fields as indicated + N181, N182, \note fields as indicated + N183, N184, \note fields as indicated + N185, N186, \note fields as indicated + N187, N188, \note fields as indicated + N189, N190, \note fields as indicated + N191, N192, \note fields as indicated + N193, N194, \note fields as indicated + N195, N196, \note fields as indicated + N197, N198, \note fields as indicated + N199, N200, \note fields as indicated + N201, N202, \note fields as indicated + N203, N204, \note fields as indicated + N205, N206, \note fields as indicated + N207, N208, \note fields as indicated + N209, N210, \note fields as indicated + N211, N212, \note fields as indicated + N213, N214; \note fields as indicated + +\group Schedules + +ScheduleTypeLimits, + \memo ScheduleTypeLimits specifies the data types and limits for the values contained in schedules + A1, \field Name + \required-field + \reference ScheduleTypeLimitsNames + \note used to validate schedule types in various schedule objects + N1, \field Lower Limit Value + \note lower limit (real or integer) for the Schedule Type. e.g. if fraction, this is 0.0 + \unitsBasedOnField A3 + N2, \field Upper Limit Value + \note upper limit (real or integer) for the Schedule Type. e.g. if fraction, this is 1.0 + \unitsBasedOnField A3 + A2, \field Numeric Type + \note Numeric type is either Continuous (all numbers within the min and + \note max are valid or Discrete (only integer numbers between min and + \note max are valid. (Could also allow REAL and INTEGER to mean the + \note same things) + \type choice + \key Continuous + \key Discrete + A3; \field Unit Type + \note Temperature (C or F) + \note DeltaTemperature (C or F) + \note PrecipitationRate (m/hr or ft/hr) + \note Angle (degrees) + \note Convection Coefficient (W/m2-K or Btu/sqft-hr-F) + \note Activity Level (W/person) + \note Velocity (m/s or ft/min) + \note Capacity (W or Btu/h) + \note Power (W) + \type choice + \key Dimensionless + \key Temperature + \key DeltaTemperature + \key PrecipitationRate + \key Angle + \key ConvectionCoefficient + \key ActivityLevel + \key Velocity + \key Capacity + \key Power + \key Availability + \key Percent + \key Control + \key Mode + \default Dimensionless + +Schedule:Day:Hourly, + \min-fields 26 + \memo A Schedule:Day:Hourly contains 24 values for each hour of the day. + A1 , \field Name + \required-field + \type alpha + \reference DayScheduleNames + A2 , \field Schedule Type Limits Name + \type object-list + \object-list ScheduleTypeLimitsNames + N1 , \field Hour 1 + \type real + \default 0 + N2 , \field Hour 2 + \type real + \default 0 + N3 , \field Hour 3 + \type real + \default 0 + N4 , \field Hour 4 + \type real + \default 0 + N5 , \field Hour 5 + \type real + \default 0 + N6 , \field Hour 6 + \type real + \default 0 + N7 , \field Hour 7 + \type real + \default 0 + N8 , \field Hour 8 + \type real + \default 0 + N9 , \field Hour 9 + \type real + \default 0 + N10, \field Hour 10 + \type real + \default 0 + N11, \field Hour 11 + \type real + \default 0 + N12, \field Hour 12 + \type real + \default 0 + N13, \field Hour 13 + \type real + \default 0 + N14, \field Hour 14 + \type real + \default 0 + N15, \field Hour 15 + \type real + \default 0 + N16, \field Hour 16 + \type real + \default 0 + N17, \field Hour 17 + \type real + \default 0 + N18, \field Hour 18 + \type real + \default 0 + N19, \field Hour 19 + \type real + \default 0 + N20, \field Hour 20 + \type real + \default 0 + N21, \field Hour 21 + \type real + \default 0 + N22, \field Hour 22 + \type real + \default 0 + N23, \field Hour 23 + \type real + \default 0 + N24; \field Hour 24 + \type real + \default 0 + +Schedule:Day:Interval, + \extensible:2 - repeat last two fields, remembering to remove ; from "inner" fields. + \memo A Schedule:Day:Interval contains a full day of values with specified end times for each value + \memo Currently, is set up to allow for 10 minute intervals for an entire day. + \min-fields 5 + A1 , \field Name + \required-field + \type alpha + \reference DayScheduleNames + A2 , \field Schedule Type Limits Name + \type object-list + \object-list ScheduleTypeLimitsNames + A3 , \field Interpolate to Timestep + \note when the interval does not match the user specified timestep a Average choice will average between the intervals request (to + \note timestep resolution. A No choice will use the interval value at the simulation timestep without regard to if it matches + \note the boundary or not. A Linear choice will interpolate linearly between successive values. + \type choice + \key Average + \key Linear + \key No + \default No + A4 , \field Time 1 + \begin-extensible + \note "until" includes the time entered. + \units hh:mm + N1 , \field Value Until Time 1 + A5 , \field Time 2 + \note "until" includes the time entered. + \units hh:mm + N2 , \field Value Until Time 2 + A6 , \field Time 3 + \note "until" includes the time entered. + \units hh:mm + N3 , \field Value Until Time 3 + A7 , \field Time 4 + \note "until" includes the time entered. + \units hh:mm + N4 , \field Value Until Time 4 + A8 , \field Time 5 + \note "until" includes the time entered. + \units hh:mm + N5 , \field Value Until Time 5 + A9 , \field Time 6 + \note "until" includes the time entered. + \units hh:mm + N6 , \field Value Until Time 6 + A10 , \field Time 7 + \note "until" includes the time entered. + \units hh:mm + N7 , \field Value Until Time 7 + A11 , \field Time 8 + \note "until" includes the time entered. + \units hh:mm + N8 , \field Value Until Time 8 + A12 , \field Time 9 + \note "until" includes the time entered. + \units hh:mm + N9 , \field Value Until Time 9 + A13 , \field Time 10 + \note "until" includes the time entered. + \units hh:mm + N10 , \field Value Until Time 10 + A14 , \field Time 11 + \note "until" includes the time entered. + \units hh:mm + N11 , \field Value Until Time 11 + A15 , \field Time 12 + \note "until" includes the time entered. + \units hh:mm + N12 , \field Value Until Time 12 + A16 , \field Time 13 + \note "until" includes the time entered. + \units hh:mm + N13 , \field Value Until Time 13 + A17 , \field Time 14 + \note "until" includes the time entered. + \units hh:mm + N14 , \field Value Until Time 14 + A18 , \field Time 15 + \note "until" includes the time entered. + \units hh:mm + N15 , \field Value Until Time 15 + A19 , \field Time 16 + \note "until" includes the time entered. + \units hh:mm + N16 , \field Value Until Time 16 + A20 , \field Time 17 + \note "until" includes the time entered. + \units hh:mm + N17 , \field Value Until Time 17 + A21 , \field Time 18 + \note "until" includes the time entered. + \units hh:mm + N18 , \field Value Until Time 18 + A22 , \field Time 19 + \note "until" includes the time entered. + \units hh:mm + N19 , \field Value Until Time 19 + A23 , \field Time 20 + \note "until" includes the time entered. + \units hh:mm + N20 , \field Value Until Time 20 + A24 , \field Time 21 + \note "until" includes the time entered. + \units hh:mm + N21 , \field Value Until Time 21 + A25 , \field Time 22 + \note "until" includes the time entered. + \units hh:mm + N22 , \field Value Until Time 22 + A26 , \field Time 23 + \note "until" includes the time entered. + \units hh:mm + N23 , \field Value Until Time 23 + A27 , \field Time 24 + \note "until" includes the time entered. + \units hh:mm + N24 , \field Value Until Time 24 + A28 , \field Time 25 + \note "until" includes the time entered. + \units hh:mm + N25 , \field Value Until Time 25 + A29 , \field Time 26 + \note "until" includes the time entered. + \units hh:mm + N26 , \field Value Until Time 26 + A30 , \field Time 27 + \note "until" includes the time entered. + \units hh:mm + N27 , \field Value Until Time 27 + A31 , \field Time 28 + \note "until" includes the time entered. + \units hh:mm + N28 , \field Value Until Time 28 + A32 , \field Time 29 + \note "until" includes the time entered. + \units hh:mm + N29 , \field Value Until Time 29 + A33 , \field Time 30 + \note "until" includes the time entered. + \units hh:mm + N30 , \field Value Until Time 30 + A34 , \field Time 31 + \note "until" includes the time entered. + \units hh:mm + N31 , \field Value Until Time 31 + A35 , \field Time 32 + \note "until" includes the time entered. + \units hh:mm + N32 , \field Value Until Time 32 + A36 , \field Time 33 + \note "until" includes the time entered. + \units hh:mm + N33 , \field Value Until Time 33 + A37 , \field Time 34 + \note "until" includes the time entered. + \units hh:mm + N34 , \field Value Until Time 34 + A38 , \field Time 35 + \note "until" includes the time entered. + \units hh:mm + N35 , \field Value Until Time 35 + A39 , \field Time 36 + \note "until" includes the time entered. + \units hh:mm + N36 , \field Value Until Time 36 + A40 , \field Time 37 + \note "until" includes the time entered. + \units hh:mm + N37 , \field Value Until Time 37 + A41 , \field Time 38 + \note "until" includes the time entered. + \units hh:mm + N38 , \field Value Until Time 38 + A42 , \field Time 39 + \note "until" includes the time entered. + \units hh:mm + N39 , \field Value Until Time 39 + A43 , \field Time 40 + \note "until" includes the time entered. + \units hh:mm + N40 , \field Value Until Time 40 + A44 , \field Time 41 + \note "until" includes the time entered. + \units hh:mm + N41 , \field Value Until Time 41 + A45 , \field Time 42 + \note "until" includes the time entered. + \units hh:mm + N42 , \field Value Until Time 42 + A46 , \field Time 43 + \note "until" includes the time entered. + \units hh:mm + N43 , \field Value Until Time 43 + A47 , \field Time 44 + \note "until" includes the time entered. + \units hh:mm + N44 , \field Value Until Time 44 + A48 , \field Time 45 + \note "until" includes the time entered. + \units hh:mm + N45 , \field Value Until Time 45 + A49 , \field Time 46 + \note "until" includes the time entered. + \units hh:mm + N46 , \field Value Until Time 46 + A50 , \field Time 47 + \note "until" includes the time entered. + \units hh:mm + N47 , \field Value Until Time 47 + A51 , \field Time 48 + \note "until" includes the time entered. + \units hh:mm + N48 , \field Value Until Time 48 + A52 , \field Time 49 + \note "until" includes the time entered. + \units hh:mm + N49 , \field Value Until Time 49 + A53 , \field Time 50 + \note "until" includes the time entered. + \units hh:mm + N50 , \field Value Until Time 50 + A54 , \field Time 51 + \note "until" includes the time entered. + \units hh:mm + N51 , \field Value Until Time 51 + A55 , \field Time 52 + \note "until" includes the time entered. + \units hh:mm + N52 , \field Value Until Time 52 + A56 , \field Time 53 + \note "until" includes the time entered. + \units hh:mm + N53 , \field Value Until Time 53 + A57 , \field Time 54 + \note "until" includes the time entered. + \units hh:mm + N54 , \field Value Until Time 54 + A58 , \field Time 55 + \note "until" includes the time entered. + \units hh:mm + N55 , \field Value Until Time 55 + A59 , \field Time 56 + \note "until" includes the time entered. + \units hh:mm + N56 , \field Value Until Time 56 + A60 , \field Time 57 + \note "until" includes the time entered. + \units hh:mm + N57 , \field Value Until Time 57 + A61 , \field Time 58 + \note "until" includes the time entered. + \units hh:mm + N58 , \field Value Until Time 58 + A62 , \field Time 59 + \note "until" includes the time entered. + \units hh:mm + N59 , \field Value Until Time 59 + A63 , \field Time 60 + \note "until" includes the time entered. + \units hh:mm + N60 , \field Value Until Time 60 + A64 , \field Time 61 + \note "until" includes the time entered. + \units hh:mm + N61 , \field Value Until Time 61 + A65 , \field Time 62 + \note "until" includes the time entered. + \units hh:mm + N62 , \field Value Until Time 62 + A66 , \field Time 63 + \note "until" includes the time entered. + \units hh:mm + N63 , \field Value Until Time 63 + A67 , \field Time 64 + \note "until" includes the time entered. + \units hh:mm + N64 , \field Value Until Time 64 + A68 , \field Time 65 + \note "until" includes the time entered. + \units hh:mm + N65 , \field Value Until Time 65 + A69 , \field Time 66 + \note "until" includes the time entered. + \units hh:mm + N66 , \field Value Until Time 66 + A70 , \field Time 67 + \note "until" includes the time entered. + \units hh:mm + N67 , \field Value Until Time 67 + A71 , \field Time 68 + \note "until" includes the time entered. + \units hh:mm + N68 , \field Value Until Time 68 + A72 , \field Time 69 + \note "until" includes the time entered. + \units hh:mm + N69 , \field Value Until Time 69 + A73 , \field Time 70 + \note "until" includes the time entered. + \units hh:mm + N70 , \field Value Until Time 70 + A74 , \field Time 71 + \note "until" includes the time entered. + \units hh:mm + N71 , \field Value Until Time 71 + A75 , \field Time 72 + \note "until" includes the time entered. + \units hh:mm + N72 , \field Value Until Time 72 + A76 , \field Time 73 + \note "until" includes the time entered. + \units hh:mm + N73 , \field Value Until Time 73 + A77 , \field Time 74 + \note "until" includes the time entered. + \units hh:mm + N74 , \field Value Until Time 74 + A78 , \field Time 75 + \note "until" includes the time entered. + \units hh:mm + N75 , \field Value Until Time 75 + A79 , \field Time 76 + \note "until" includes the time entered. + \units hh:mm + N76 , \field Value Until Time 76 + A80 , \field Time 77 + \note "until" includes the time entered. + \units hh:mm + N77 , \field Value Until Time 77 + A81 , \field Time 78 + \note "until" includes the time entered. + \units hh:mm + N78 , \field Value Until Time 78 + A82 , \field Time 79 + \note "until" includes the time entered. + \units hh:mm + N79 , \field Value Until Time 79 + A83 , \field Time 80 + \note "until" includes the time entered. + \units hh:mm + N80 , \field Value Until Time 80 + A84 , \field Time 81 + \note "until" includes the time entered. + \units hh:mm + N81 , \field Value Until Time 81 + A85 , \field Time 82 + \note "until" includes the time entered. + \units hh:mm + N82 , \field Value Until Time 82 + A86 , \field Time 83 + \note "until" includes the time entered. + \units hh:mm + N83 , \field Value Until Time 83 + A87 , \field Time 84 + \note "until" includes the time entered. + \units hh:mm + N84 , \field Value Until Time 84 + A88 , \field Time 85 + \note "until" includes the time entered. + \units hh:mm + N85 , \field Value Until Time 85 + A89 , \field Time 86 + \note "until" includes the time entered. + \units hh:mm + N86 , \field Value Until Time 86 + A90 , \field Time 87 + \note "until" includes the time entered. + \units hh:mm + N87 , \field Value Until Time 87 + A91 , \field Time 88 + \note "until" includes the time entered. + \units hh:mm + N88 , \field Value Until Time 88 + A92 , \field Time 89 + \note "until" includes the time entered. + \units hh:mm + N89 , \field Value Until Time 89 + A93 , \field Time 90 + \note "until" includes the time entered. + \units hh:mm + N90 , \field Value Until Time 90 + A94 , \field Time 91 + \note "until" includes the time entered. + \units hh:mm + N91 , \field Value Until Time 91 + A95 , \field Time 92 + \note "until" includes the time entered. + \units hh:mm + N92 , \field Value Until Time 92 + A96 , \field Time 93 + \note "until" includes the time entered. + \units hh:mm + N93 , \field Value Until Time 93 + A97 , \field Time 94 + \note "until" includes the time entered. + \units hh:mm + N94 , \field Value Until Time 94 + A98 , \field Time 95 + \note "until" includes the time entered. + \units hh:mm + N95 , \field Value Until Time 95 + A99 , \field Time 96 + \note "until" includes the time entered. + \units hh:mm + N96 , \field Value Until Time 96 + A100, \field Time 97 + \note "until" includes the time entered. + \units hh:mm + N97 , \field Value Until Time 97 + A101, \field Time 98 + \note "until" includes the time entered. + \units hh:mm + N98 , \field Value Until Time 98 + A102, \field Time 99 + \note "until" includes the time entered. + \units hh:mm + N99 , \field Value Until Time 99 + A103, \field Time 100 + \note "until" includes the time entered. + \units hh:mm + N100, \field Value Until Time 100 + A104, \field Time 101 + \note "until" includes the time entered. + \units hh:mm + N101, \field Value Until Time 101 + A105, \field Time 102 + \note "until" includes the time entered. + \units hh:mm + N102, \field Value Until Time 102 + A106, \field Time 103 + \note "until" includes the time entered. + \units hh:mm + N103, \field Value Until Time 103 + A107, \field Time 104 + \note "until" includes the time entered. + \units hh:mm + N104, \field Value Until Time 104 + A108, \field Time 105 + \note "until" includes the time entered. + \units hh:mm + N105, \field Value Until Time 105 + A109, \field Time 106 + \note "until" includes the time entered. + \units hh:mm + N106, \field Value Until Time 106 + A110, \field Time 107 + \note "until" includes the time entered. + \units hh:mm + N107, \field Value Until Time 107 + A111, \field Time 108 + \note "until" includes the time entered. + \units hh:mm + N108, \field Value Until Time 108 + A112, \field Time 109 + \note "until" includes the time entered. + \units hh:mm + N109, \field Value Until Time 109 + A113, \field Time 110 + \note "until" includes the time entered. + \units hh:mm + N110, \field Value Until Time 110 + A114, \field Time 111 + \note "until" includes the time entered. + \units hh:mm + N111, \field Value Until Time 111 + A115, \field Time 112 + \note "until" includes the time entered. + \units hh:mm + N112, \field Value Until Time 112 + A116, \field Time 113 + \note "until" includes the time entered. + \units hh:mm + N113, \field Value Until Time 113 + A117, \field Time 114 + \note "until" includes the time entered. + \units hh:mm + N114, \field Value Until Time 114 + A118, \field Time 115 + \note "until" includes the time entered. + \units hh:mm + N115, \field Value Until Time 115 + A119, \field Time 116 + \note "until" includes the time entered. + \units hh:mm + N116, \field Value Until Time 116 + A120, \field Time 117 + \note "until" includes the time entered. + \units hh:mm + N117, \field Value Until Time 117 + A121, \field Time 118 + \note "until" includes the time entered. + \units hh:mm + N118, \field Value Until Time 118 + A122, \field Time 119 + \note "until" includes the time entered. + \units hh:mm + N119, \field Value Until Time 119 + A123, \field Time 120 + \note "until" includes the time entered. + \units hh:mm + N120, \field Value Until Time 120 + A124, \field Time 121 + \note "until" includes the time entered. + \units hh:mm + N121, \field Value Until Time 121 + A125, \field Time 122 + \note "until" includes the time entered. + \units hh:mm + N122, \field Value Until Time 122 + A126, \field Time 123 + \note "until" includes the time entered. + \units hh:mm + N123, \field Value Until Time 123 + A127, \field Time 124 + \note "until" includes the time entered. + \units hh:mm + N124, \field Value Until Time 124 + A128, \field Time 125 + \note "until" includes the time entered. + \units hh:mm + N125, \field Value Until Time 125 + A129, \field Time 126 + \note "until" includes the time entered. + \units hh:mm + N126, \field Value Until Time 126 + A130, \field Time 127 + \note "until" includes the time entered. + \units hh:mm + N127, \field Value Until Time 127 + A131, \field Time 128 + \note "until" includes the time entered. + \units hh:mm + N128, \field Value Until Time 128 + A132, \field Time 129 + \note "until" includes the time entered. + \units hh:mm + N129, \field Value Until Time 129 + A133, \field Time 130 + \note "until" includes the time entered. + \units hh:mm + N130, \field Value Until Time 130 + A134, \field Time 131 + \note "until" includes the time entered. + \units hh:mm + N131, \field Value Until Time 131 + A135, \field Time 132 + \note "until" includes the time entered. + \units hh:mm + N132, \field Value Until Time 132 + A136, \field Time 133 + \note "until" includes the time entered. + \units hh:mm + N133, \field Value Until Time 133 + A137, \field Time 134 + \note "until" includes the time entered. + \units hh:mm + N134, \field Value Until Time 134 + A138, \field Time 135 + \note "until" includes the time entered. + \units hh:mm + N135, \field Value Until Time 135 + A139, \field Time 136 + \note "until" includes the time entered. + \units hh:mm + N136, \field Value Until Time 136 + A140, \field Time 137 + \note "until" includes the time entered. + \units hh:mm + N137, \field Value Until Time 137 + A141, \field Time 138 + \note "until" includes the time entered. + \units hh:mm + N138, \field Value Until Time 138 + A142, \field Time 139 + \note "until" includes the time entered. + \units hh:mm + N139, \field Value Until Time 139 + A143, \field Time 140 + \note "until" includes the time entered. + \units hh:mm + N140, \field Value Until Time 140 + A144, \field Time 141 + \note "until" includes the time entered. + \units hh:mm + N141, \field Value Until Time 141 + A145, \field Time 142 + \note "until" includes the time entered. + \units hh:mm + N142, \field Value Until Time 142 + A146, \field Time 143 + \note "until" includes the time entered. + \units hh:mm + N143, \field Value Until Time 143 + A147, \field Time 144 + \note "until" includes the time entered. + \units hh:mm + N144; \field Value Until Time 144 + +Schedule:Day:List, + \memo Schedule:Day:List will allow the user to list 24 hours worth of values, which can be sub-hourly in nature. + \min-fields 5 + \extensible:1 + A1 , \field Name + \required-field + \type alpha + \reference DayScheduleNames + A2 , \field Schedule Type Limits Name + \type object-list + \object-list ScheduleTypeLimitsNames + A3 , \field Interpolate to Timestep + \note when the interval does not match the user specified timestep a "Average" choice will average between the intervals request (to + \note timestep resolution. A "No" choice will use the interval value at the simulation timestep without regard to if it matches + \note the boundary or not. A "Linear" choice will interpolate linearly between successive values. + \type choice + \key Average + \key Linear + \key No + \default No + N1 , \field Minutes per Item + \note Must be evenly divisible into 60 + \type integer + \minimum 1 + \maximum 60 + N2, \field Value 1 + \begin-extensible + \default 0.0 + N3,N4, N5,N6,N7,N8, N9,N10,N11,N12, N13,N14,N15,N16, N17,N18,N19,N20, \note fields as indicated + N21,N22,N23,N24, N25,N26,N27,N28, N29,N30,N31,N32, N33,N34,N35,N36, N37,N38,N39,N40, \note fields as indicated + N41,N42,N43,N44, N45,N46,N47,N48, N49,N50,N51,N52, N53,N54,N55,N56, N57,N58,N59,N60, \note fields as indicated + N61,N62,N63,N64, N65,N66,N67,N68, N69,N70,N71,N72, N73,N74,N75,N76, N77,N78,N79,N80, \note fields as indicated + N81,N82,N83,N84, N85,N86,N87,N88, N89,N90,N91,N92, N93,N94,N95,N96, N97,N98,N99,N100, \note fields as indicated + + N101,N102,N103,N104, N105,N106,N107,N108, N109,N110,N111,N112, N113,N114,N115,N116, N117,N118,N119,N120, \note fields as indicated + N121,N122,N123,N124, N125,N126,N127,N128, N129,N130,N131,N132, N133,N134,N135,N136, N137,N138,N139,N140, \note fields as indicated + N141,N142,N143,N144, N145,N146,N147,N148, N149,N150,N151,N152, N153,N154,N155,N156, N157,N158,N159,N160, \note fields as indicated + N161,N162,N163,N164, N165,N166,N167,N168, N169,N170,N171,N172, N173,N174,N175,N176, N177,N178,N179,N180, \note fields as indicated + N181,N182,N183,N184, N185,N186,N187,N188, N189,N190,N191,N192, N193,N194,N195,N196, N197,N198,N199,N200, \note fields as indicated + + N201,N202,N203,N204, N205,N206,N207,N208, N209,N210,N211,N212, N213,N214,N215,N216, N217,N218,N219,N220, \note fields as indicated + N221,N222,N223,N224, N225,N226,N227,N228, N229,N230,N231,N232, N233,N234,N235,N236, N237,N238,N239,N240, \note fields as indicated + N241,N242,N243,N244, N245,N246,N247,N248, N249,N250,N251,N252, N253,N254,N255,N256, N257,N258,N259,N260, \note fields as indicated + N261,N262,N263,N264, N265,N266,N267,N268, N269,N270,N271,N272, N273,N274,N275,N276, N277,N278,N279,N280, \note fields as indicated + N281,N282,N283,N284, N285,N286,N287,N288, N289,N290,N291,N292, N293,N294,N295,N296, N297,N298,N299,N300, \note fields as indicated + + N301,N302,N303,N304, N305,N306,N307,N308, N309,N310,N311,N312, N313,N314,N315,N316, N317,N318,N319,N320, \note fields as indicated + N321,N322,N323,N324, N325,N326,N327,N328, N329,N330,N331,N332, N333,N334,N335,N336, N337,N338,N339,N340, \note fields as indicated + N341,N342,N343,N344, N345,N346,N347,N348, N349,N350,N351,N352, N353,N354,N355,N356, N357,N358,N359,N360, \note fields as indicated + N361,N362,N363,N364, N365,N366,N367,N368, N369,N370,N371,N372, N373,N374,N375,N376, N377,N378,N379,N380, \note fields as indicated + N381,N382,N383,N384, N385,N386,N387,N388, N389,N390,N391,N392, N393,N394,N395,N396, N397,N398,N399,N400, \note fields as indicated + + N401,N402,N403,N404, N405,N406,N407,N408, N409,N410,N411,N412, N413,N414,N415,N416, N417,N418,N419,N420, \note fields as indicated + N421,N422,N423,N424, N425,N426,N427,N428, N429,N430,N431,N432, N433,N434,N435,N436, N437,N438,N439,N440, \note fields as indicated + N441,N442,N443,N444, N445,N446,N447,N448, N449,N450,N451,N452, N453,N454,N455,N456, N457,N458,N459,N460, \note fields as indicated + N461,N462,N463,N464, N465,N466,N467,N468, N469,N470,N471,N472, N473,N474,N475,N476, N477,N478,N479,N480, \note fields as indicated + N481,N482,N483,N484, N485,N486,N487,N488, N489,N490,N491,N492, N493,N494,N495,N496, N497,N498,N499,N500, \note fields as indicated + + N501,N502,N503,N504, N505,N506,N507,N508, N509,N510,N511,N512, N513,N514,N515,N516, N517,N518,N519,N520, \note fields as indicated + N521,N522,N523,N524, N525,N526,N527,N528, N529,N530,N531,N532, N533,N534,N535,N536, N537,N538,N539,N540, \note fields as indicated + N541,N542,N543,N544, N545,N546,N547,N548, N549,N550,N551,N552, N553,N554,N555,N556, N557,N558,N559,N560, \note fields as indicated + N561,N562,N563,N564, N565,N566,N567,N568, N569,N570,N571,N572, N573,N574,N575,N576, N577,N578,N579,N580, \note fields as indicated + N581,N582,N583,N584, N585,N586,N587,N588, N589,N590,N591,N592, N593,N594,N595,N596, N597,N598,N599,N600, \note fields as indicated + + N601,N602,N603,N604, N605,N606,N607,N608, N609,N610,N611,N612, N613,N614,N615,N616, N617,N618,N619,N620, \note fields as indicated + N621,N622,N623,N624, N625,N626,N627,N628, N629,N630,N631,N632, N633,N634,N635,N636, N637,N638,N639,N640, \note fields as indicated + N641,N642,N643,N644, N645,N646,N647,N648, N649,N650,N651,N652, N653,N654,N655,N656, N657,N658,N659,N660, \note fields as indicated + N661,N662,N663,N664, N665,N666,N667,N668, N669,N670,N671,N672, N673,N674,N675,N676, N677,N678,N679,N680, \note fields as indicated + N681,N682,N683,N684, N685,N686,N687,N688, N689,N690,N691,N692, N693,N694,N695,N696, N697,N698,N699,N700, \note fields as indicated + + N701,N702,N703,N704, N705,N706,N707,N708, N709,N710,N711,N712, N713,N714,N715,N716, N717,N718,N719,N720, \note fields as indicated + N721,N722,N723,N724, N725,N726,N727,N728, N729,N730,N731,N732, N733,N734,N735,N736, N737,N738,N739,N740, \note fields as indicated + N741,N742,N743,N744, N745,N746,N747,N748, N749,N750,N751,N752, N753,N754,N755,N756, N757,N758,N759,N760, \note fields as indicated + N761,N762,N763,N764, N765,N766,N767,N768, N769,N770,N771,N772, N773,N774,N775,N776, N777,N778,N779,N780, \note fields as indicated + N781,N782,N783,N784, N785,N786,N787,N788, N789,N790,N791,N792, N793,N794,N795,N796, N797,N798,N799,N800, \note fields as indicated + + N801,N802,N803,N804, N805,N806,N807,N808, N809,N810,N811,N812, N813,N814,N815,N816, N817,N818,N819,N820, \note fields as indicated + N821,N822,N823,N824, N825,N826,N827,N828, N829,N830,N831,N832, N833,N834,N835,N836, N837,N838,N839,N840, \note fields as indicated + N841,N842,N843,N844, N845,N846,N847,N848, N849,N850,N851,N852, N853,N854,N855,N856, N857,N858,N859,N860, \note fields as indicated + N861,N862,N863,N864, N865,N866,N867,N868, N869,N870,N871,N872, N873,N874,N875,N876, N877,N878,N879,N880, \note fields as indicated + N881,N882,N883,N884, N885,N886,N887,N888, N889,N890,N891,N892, N893,N894,N895,N896, N897,N898,N899,N900, \note fields as indicated + + N901,N902,N903,N904, N905,N906,N907,N908, N909,N910,N911,N912, N913,N914,N915,N916, N917,N918,N919,N920, \note fields as indicated + N921,N922,N923,N924, N925,N926,N927,N928, N929,N930,N931,N932, N933,N934,N935,N936, N937,N938,N939,N940, \note fields as indicated + N941,N942,N943,N944, N945,N946,N947,N948, N949,N950,N951,N952, N953,N954,N955,N956, N957,N958,N959,N960, \note fields as indicated + N961,N962,N963,N964, N965,N966,N967,N968, N969,N970,N971,N972, N973,N974,N975,N976, N977,N978,N979,N980, \note fields as indicated + N981,N982,N983,N984, N985,N986,N987,N988, N989,N990,N991,N992, N993,N994,N995,N996, N997,N998,N999,N1000, \note fields as indicated + + N1001,N1002,N1003,N1004, N1005,N1006,N1007,N1008, N1009,N1010,N1011,N1012, N1013,N1014,N1015,N1016, N1017,N1018,N1019,N1020, \note fields as indicated + N1021,N1022,N1023,N1024, N1025,N1026,N1027,N1028, N1029,N1030,N1031,N1032, N1033,N1034,N1035,N1036, N1037,N1038,N1039,N1040, \note fields as indicated + N1041,N1042,N1043,N1044, N1045,N1046,N1047,N1048, N1049,N1050,N1051,N1052, N1053,N1054,N1055,N1056, N1057,N1058,N1059,N1060, \note fields as indicated + N1061,N1062,N1063,N1064, N1065,N1066,N1067,N1068, N1069,N1070,N1071,N1072, N1073,N1074,N1075,N1076, N1077,N1078,N1079,N1080, \note fields as indicated + N1081,N1082,N1083,N1084, N1085,N1086,N1087,N1088, N1089,N1090,N1091,N1092, N1093,N1094,N1095,N1096, N1097,N1098,N1099,N1100, \note fields as indicated + + N1101,N1102,N1103,N1104, N1105,N1106,N1107,N1108, N1109,N1110,N1111,N1112, N1113,N1114,N1115,N1116, N1117,N1118,N1119,N1120, \note fields as indicated + N1121,N1122,N1123,N1124, N1125,N1126,N1127,N1128, N1129,N1130,N1131,N1132, N1133,N1134,N1135,N1136, N1137,N1138,N1139,N1140, \note fields as indicated + N1141,N1142,N1143,N1144, N1145,N1146,N1147,N1148, N1149,N1150,N1151,N1152, N1153,N1154,N1155,N1156, N1157,N1158,N1159,N1160, \note fields as indicated + N1161,N1162,N1163,N1164, N1165,N1166,N1167,N1168, N1169,N1170,N1171,N1172, N1173,N1174,N1175,N1176, N1177,N1178,N1179,N1180, \note fields as indicated + N1181,N1182,N1183,N1184, N1185,N1186,N1187,N1188, N1189,N1190,N1191,N1192, N1193,N1194,N1195,N1196, N1197,N1198,N1199,N1200, \note fields as indicated + + N1201,N1202,N1203,N1204, N1205,N1206,N1207,N1208, N1209,N1210,N1211,N1212, N1213,N1214,N1215,N1216, N1217,N1218,N1219,N1220,\note fields as indicated + N1221,N1222,N1223,N1224, N1225,N1226,N1227,N1228, N1229,N1230,N1231,N1232, N1233,N1234,N1235,N1236, N1237,N1238,N1239,N1240,\note fields as indicated + N1241,N1242,N1243,N1244, N1245,N1246,N1247,N1248, N1249,N1250,N1251,N1252, N1253,N1254,N1255,N1256, N1257,N1258,N1259,N1260,\note fields as indicated + N1261,N1262,N1263,N1264, N1265,N1266,N1267,N1268, N1269,N1270,N1271,N1272, N1273,N1274,N1275,N1276, N1277,N1278,N1279,N1280,\note fields as indicated + N1281,N1282,N1283,N1284, N1285,N1286,N1287,N1288, N1289,N1290,N1291,N1292, N1293,N1294,N1295,N1296, N1297,N1298,N1299,N1300,\note fields as indicated + + N1301,N1302,N1303,N1304, N1305,N1306,N1307,N1308, N1309,N1310,N1311,N1312, N1313,N1314,N1315,N1316, N1317,N1318,N1319,N1320,\note fields as indicated + N1321,N1322,N1323,N1324, N1325,N1326,N1327,N1328, N1329,N1330,N1331,N1332, N1333,N1334,N1335,N1336, N1337,N1338,N1339,N1340,\note fields as indicated + N1341,N1342,N1343,N1344, N1345,N1346,N1347,N1348, N1349,N1350,N1351,N1352, N1353,N1354,N1355,N1356, N1357,N1358,N1359,N1360,\note fields as indicated + N1361,N1362,N1363,N1364, N1365,N1366,N1367,N1368, N1369,N1370,N1371,N1372, N1373,N1374,N1375,N1376, N1377,N1378,N1379,N1380,\note fields as indicated + N1381,N1382,N1383,N1384, N1385,N1386,N1387,N1388, N1389,N1390,N1391,N1392, N1393,N1394,N1395,N1396, N1397,N1398,N1399,N1400,\note fields as indicated + + N1401,N1402,N1403,N1404, N1405,N1406,N1407,N1408, N1409,N1410,N1411,N1412, N1413,N1414,N1415,N1416, N1417,N1418,N1419,N1420,\note fields as indicated + N1421,N1422,N1423,N1424, N1425,N1426,N1427,N1428, N1429,N1430,N1431,N1432, N1433,N1434,N1435,N1436, N1437,N1438,N1439,N1440,\note fields as indicated + N1441;\note fields as indicated + +Schedule:Week:Daily, + \min-fields 13 + \memo A Schedule:Week:Daily contains 12 Schedule:Day:Hourly objects, one for each day type. + A1 , \field Name + \required-field + \reference WeekScheduleNames + \type alpha + A2 , \field Sunday Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A3 , \field Monday Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A4 , \field Tuesday Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A5 , \field Wednesday Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A6 , \field Thursday Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A7 , \field Friday Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A8 , \field Saturday Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A9 , \field Holiday Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A10, \field SummerDesignDay Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A11, \field WinterDesignDay Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A12, \field CustomDay1 Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + A13; \field CustomDay2 Schedule:Day Name + \required-field + \type object-list + \object-list DayScheduleNames + +Schedule:Week:Compact, + \extensible:2 - repeat last two fields, remembering to remove ; from "inner" fields. + \memo Compact definition for Schedule:Day:List + \min-fields 3 + A1 , \field Name + \required-field + \reference WeekScheduleNames + \type alpha + A2 , \field DayType List 1 + \begin-extensible + \note "For" is an optional prefix/start of the For fields. Choices can be combined on single line + \note if separated by spaces. i.e. "Holiday Weekends" + \note Should have a space after For, if it is included. i.e. "For Alldays" + \required-field + \type choice + \key AllDays + \key AllOtherDays + \key Weekdays + \key Weekends + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + \key Holiday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + A3 , \field Schedule:Day Name 1 + \required-field + \type object-list + \object-list DayScheduleNames + A4 , \field DayType List 2 + \type choice + \key AllDays + \key AllOtherDays + \key Weekdays + \key Weekends + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + \key Holiday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + A5 , \field Schedule:Day Name 2 + \type object-list + \object-list DayScheduleNames + A6 , \field DayType List 3 + \type choice + \key AllDays + \key AllOtherDays + \key Weekdays + \key Weekends + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + \key Holiday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + A7 , \field Schedule:Day Name 3 + \type object-list + \object-list DayScheduleNames + A8 , \field DayType List 4 + \type choice + \key AllDays + \key AllOtherDays + \key Weekdays + \key Weekends + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + \key Holiday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + A9 , \field Schedule:Day Name 4 + \type object-list + \object-list DayScheduleNames + A10, \field DayType List 5 + \type choice + \key AllDays + \key AllOtherDays + \key Weekdays + \key Weekends + \key Sunday + \key Monday + \key Tuesday + \key Wednesday + \key Thursday + \key Friday + \key Saturday + \key Holiday + \key SummerDesignDay + \key WinterDesignDay + \key CustomDay1 + \key CustomDay2 + A11; \field Schedule:Day Name 5 + \type object-list + \object-list DayScheduleNames + +Schedule:Year, + \min-fields 7 + \extensible:5 + \memo A Schedule:Year contains from 1 to 52 week schedules + A1 , \field Name + \required-field + \type alpha + \reference ScheduleNames + A2 , \field Schedule Type Limits Name + \type object-list + \object-list ScheduleTypeLimitsNames + A3 , \field Schedule:Week Name 1 + \begin-extensible + \required-field + \type object-list + \object-list WeekScheduleNames + N1 , \field Start Month 1 + \required-field + \type integer + \minimum 1 + \maximum 12 + N2 , \field Start Day 1 + \required-field + \type integer + \minimum 1 + \maximum 31 + N3 , \field End Month 1 + \required-field + \type integer + \minimum 1 + \maximum 12 + N4 , \field End Day 1 + \required-field + \type integer + \minimum 1 + \maximum 31 + A4 , \field Schedule:Week Name 2 + \type object-list + \object-list WeekScheduleNames + N5 , \field Start Month 2 + \type integer + \minimum 1 + \maximum 12 + N6 , \field Start Day 2 + \type integer + \minimum 1 + \maximum 31 + N7 , \field End Month 2 + \type integer + \minimum 1 + \maximum 12 + N8 , \field End Day 2 + \type integer + \minimum 1 + \maximum 31 + A5 , \field Schedule:Week Name 3 + \type object-list + \object-list WeekScheduleNames + N9 , \field Start Month 3 + \type integer + \minimum 1 + \maximum 12 + N10, \field Start Day 3 + \type integer + \minimum 1 + \maximum 31 + N11, \field End Month 3 + \type integer + \minimum 1 + \maximum 12 + N12, \field End Day 3 + \type integer + \minimum 1 + \maximum 31 + A6 , \field Schedule:Week Name 4 + \type object-list + \object-list WeekScheduleNames + N13, \field Start Month 4 + \type integer + \minimum 1 + \maximum 12 + N14, \field Start Day 4 + \type integer + \minimum 1 + \maximum 31 + N15, \field End Month 4 + \type integer + \minimum 1 + \maximum 12 + N16, \field End Day 4 + \type integer + \minimum 1 + \maximum 31 + A7 , \field Schedule:Week Name 5 + \type object-list + \object-list WeekScheduleNames + N17, \field Start Month 5 + \type integer + \minimum 1 + \maximum 12 + N18, \field Start Day 5 + \type integer + \minimum 1 + \maximum 31 + N19, \field End Month 5 + \type integer + \minimum 1 + \maximum 12 + N20, \field End Day 5 + \type integer + \minimum 1 + \maximum 31 + A8 , \field Schedule:Week Name 6 + \type object-list + \object-list WeekScheduleNames + N21, \field Start Month 6 + \type integer + \minimum 1 + \maximum 12 + N22, \field Start Day 6 + \type integer + \minimum 1 + \maximum 31 + N23, \field End Month 6 + \type integer + \minimum 1 + \maximum 12 + N24, \field End Day 6 + \type integer + \minimum 1 + \maximum 31 + A9 , \field Schedule:Week Name 7 + \type object-list + \object-list WeekScheduleNames + N25, \field Start Month 7 + \type integer + \minimum 1 + \maximum 12 + N26, \field Start Day 7 + \type integer + \minimum 1 + \maximum 31 + N27, \field End Month 7 + \type integer + \minimum 1 + \maximum 12 + N28, \field End Day 7 + \type integer + \minimum 1 + \maximum 31 + A10, \field Schedule:Week Name 8 + \type object-list + \object-list WeekScheduleNames + N29, \field Start Month 8 + \type integer + \minimum 1 + \maximum 12 + N30, \field Start Day 8 + \type integer + \minimum 1 + \maximum 31 + N31, \field End Month 8 + \type integer + \minimum 1 + \maximum 12 + N32, \field End Day 8 + \type integer + \minimum 1 + \maximum 31 + A11, \field Schedule:Week Name 9 + \type object-list + \object-list WeekScheduleNames + N33, \field Start Month 9 + \type integer + \minimum 1 + \maximum 12 + N34, \field Start Day 9 + \type integer + \minimum 1 + \maximum 31 + N35, \field End Month 9 + \type integer + \minimum 1 + \maximum 12 + N36, \field End Day 9 + \type integer + \minimum 1 + \maximum 31 + A12, \field Schedule:Week Name 10 + \type object-list + \object-list WeekScheduleNames + N37, \field Start Month 10 + \type integer + \minimum 1 + \maximum 12 + N38, \field Start Day 10 + \type integer + \minimum 1 + \maximum 31 + N39, \field End Month 10 + \type integer + \minimum 1 + \maximum 12 + N40, \field End Day 10 + \type integer + \minimum 1 + \maximum 31 + A13, \field Schedule:Week Name 11 + \type object-list + \object-list WeekScheduleNames + N41, \field Start Month 11 + \type integer + \minimum 1 + \maximum 12 + N42, \field Start Day 11 + \type integer + \minimum 1 + \maximum 31 + N43, \field End Month 11 + \type integer + \minimum 1 + \maximum 12 + N44, \field End Day 11 + \type integer + \minimum 1 + \maximum 31 + A14, \field Schedule:Week Name 12 + \type object-list + \object-list WeekScheduleNames + N45, \field Start Month 12 + \type integer + \minimum 1 + \maximum 12 + N46, \field Start Day 12 + \type integer + \minimum 1 + \maximum 31 + N47, \field End Month 12 + \type integer + \minimum 1 + \maximum 12 + N48, \field End Day 12 + \type integer + \minimum 1 + \maximum 31 + A15, \field Schedule:Week Name 13 + \type object-list + \object-list WeekScheduleNames + N49, \field Start Month 13 + \type integer + \minimum 1 + \maximum 12 + N50, \field Start Day 13 + \type integer + \minimum 1 + \maximum 31 + N51, \field End Month 13 + \type integer + \minimum 1 + \maximum 12 + N52, \field End Day 13 + \type integer + \minimum 1 + \maximum 31 + A16, \field Schedule:Week Name 14 + \type object-list + \object-list WeekScheduleNames + N53, \field Start Month 14 + \type integer + \minimum 1 + \maximum 12 + N54, \field Start Day 14 + \type integer + \minimum 1 + \maximum 31 + N55, \field End Month 14 + \type integer + \minimum 1 + \maximum 12 + N56, \field End Day 14 + \type integer + \minimum 1 + \maximum 31 + A17, \field Schedule:Week Name 15 + \type object-list + \object-list WeekScheduleNames + N57, \field Start Month 15 + \type integer + \minimum 1 + \maximum 12 + N58, \field Start Day 15 + \type integer + \minimum 1 + \maximum 31 + N59, \field End Month 15 + \type integer + \minimum 1 + \maximum 12 + N60, \field End Day 15 + \type integer + \minimum 1 + \maximum 31 + A18, \field Schedule:Week Name 16 + \type object-list + \object-list WeekScheduleNames + N61, \field Start Month 16 + \type integer + \minimum 1 + \maximum 12 + N62, \field Start Day 16 + \type integer + \minimum 1 + \maximum 31 + N63, \field End Month 16 + \type integer + \minimum 1 + \maximum 12 + N64, \field End Day 16 + \type integer + \minimum 1 + \maximum 31 + A19, \field Schedule:Week Name 17 + \type object-list + \object-list WeekScheduleNames + N65, \field Start Month 17 + \type integer + \minimum 1 + \maximum 12 + N66, \field Start Day 17 + \type integer + \minimum 1 + \maximum 31 + N67, \field End Month 17 + \type integer + \minimum 1 + \maximum 12 + N68, \field End Day 17 + \type integer + \minimum 1 + \maximum 31 + A20, \field Schedule:Week Name 18 + \type object-list + \object-list WeekScheduleNames + N69, \field Start Month 18 + \type integer + \minimum 1 + \maximum 12 + N70, \field Start Day 18 + \type integer + \minimum 1 + \maximum 31 + N71, \field End Month 18 + \type integer + \minimum 1 + \maximum 12 + N72, \field End Day 18 + \type integer + \minimum 1 + \maximum 31 + A21, \field Schedule:Week Name 19 + \type object-list + \object-list WeekScheduleNames + N73, \field Start Month 19 + \type integer + \minimum 1 + \maximum 12 + N74, \field Start Day 19 + \type integer + \minimum 1 + \maximum 31 + N75, \field End Month 19 + \type integer + \minimum 1 + \maximum 12 + N76, \field End Day 19 + \type integer + \minimum 1 + \maximum 31 + A22, \field Schedule:Week Name 20 + \type object-list + \object-list WeekScheduleNames + N77, \field Start Month 20 + \type integer + \minimum 1 + \maximum 12 + N78, \field Start Day 20 + \type integer + \minimum 1 + \maximum 31 + N79, \field End Month 20 + \type integer + \minimum 1 + \maximum 12 + N80, \field End Day 20 + \type integer + \minimum 1 + \maximum 31 + A23, \field Schedule:Week Name 21 + \type object-list + \object-list WeekScheduleNames + N81, \field Start Month 21 + \type integer + \minimum 1 + \maximum 12 + N82, \field Start Day 21 + \type integer + \minimum 1 + \maximum 31 + N83, \field End Month 21 + \type integer + \minimum 1 + \maximum 12 + N84, \field End Day 21 + \type integer + \minimum 1 + \maximum 31 + A24, \field Schedule:Week Name 22 + \type object-list + \object-list WeekScheduleNames + N85, \field Start Month 22 + \type integer + \minimum 1 + \maximum 12 + N86, \field Start Day 22 + \type integer + \minimum 1 + \maximum 31 + N87, \field End Month 22 + \type integer + \minimum 1 + \maximum 12 + N88, \field End Day 22 + \type integer + \minimum 1 + \maximum 31 + A25, \field Schedule:Week Name 23 + \type object-list + \object-list WeekScheduleNames + N89, \field Start Month 23 + \type integer + \minimum 1 + \maximum 12 + N90, \field Start Day 23 + \type integer + \minimum 1 + \maximum 31 + N91, \field End Month 23 + \type integer + \minimum 1 + \maximum 12 + N92, \field End Day 23 + \type integer + \minimum 1 + \maximum 31 + A26, \field Schedule:Week Name 24 + \type object-list + \object-list WeekScheduleNames + N93, \field Start Month 24 + \type integer + \minimum 1 + \maximum 12 + N94, \field Start Day 24 + \type integer + \minimum 1 + \maximum 31 + N95, \field End Month 24 + \type integer + \minimum 1 + \maximum 12 + N96, \field End Day 24 + \type integer + \minimum 1 + \maximum 31 + A27, \field Schedule:Week Name 25 + \type object-list + \object-list WeekScheduleNames + N97, \field Start Month 25 + \type integer + \minimum 1 + \maximum 12 + N98, \field Start Day 25 + \type integer + \minimum 1 + \maximum 31 + N99, \field End Month 25 + \type integer + \minimum 1 + \maximum 12 + N100, \field End Day 25 + \type integer + \minimum 1 + \maximum 31 + A28, \field Schedule:Week Name 26 + \type object-list + \object-list WeekScheduleNames + N101, \field Start Month 26 + \type integer + \minimum 1 + \maximum 12 + N102, \field Start Day 26 + \type integer + \minimum 1 + \maximum 31 + N103, \field End Month 26 + \type integer + \minimum 1 + \maximum 12 + N104, \field End Day 26 + \type integer + \minimum 1 + \maximum 31 + \note Schedule:Week for Weeks 27-53 are condensed + A29,N105,N106,N107,N108, \note For Week 27 + A30,N109,N110,N111,N112, \note For Week 28 + A31,N113,N114,N115,N116, \note For Week 29 + A32,N117,N118,N119,N120, \note For Week 30 + A33,N121,N122,N123,N124, \note For Week 31 + A34,N125,N126,N127,N128, \note For Week 32 + A35,N129,N130,N131,N132, \note For Week 33 + A36,N133,N134,N135,N136, \note For Week 34 + A37,N137,N138,N139,N140, \note For Week 35 + A38,N141,N142,N143,N144, \note For Week 36 + A39,N145,N146,N147,N148, \note For Week 37 + A40,N149,N150,N151,N152, \note For Week 38 + A41,N153,N154,N155,N156, \note For Week 39 + A42,N157,N158,N159,N160, \note For Week 40 + A43,N161,N162,N163,N164, \note For Week 41 + A44,N165,N166,N167,N168, \note For Week 42 + A45,N169,N170,N171,N172, \note For Week 43 + A46,N173,N174,N175,N176, \note For Week 44 + A47,N177,N178,N179,N180, \note For Week 45 + A48,N181,N182,N183,N184, \note For Week 46 + A49,N185,N186,N187,N188, \note For Week 47 + A50,N189,N190,N191,N192, \note For Week 48 + A51,N193,N194,N195,N196, \note For Week 49 + A52,N197,N198,N199,N200, \note For Week 50 + A53,N201,N202,N203,N204, \note For Week 51 + A54,N205,N206,N207,N208, \note For Week 52 + A55,N209,N210,N211,N212; \note For Week 53 + +Schedule:Compact, + \extensible:1 - repeat last field, remembering to remove ; from "inner" fields. + \min-fields 5 + \memo Irregular object. Does not follow the usual definition for fields. Fields A3... are: + \memo Through: Date + \memo For: Applicable days (ref: Schedule:Week:Compact) + \memo Interpolate: Average/Linear/No (ref: Schedule:Day:Interval) -- optional, if not used will be "No" + \memo Until: