diff --git a/city_model_structure/attributes/domestic_hot_water_facility.py b/city_model_structure/attributes/domestic_hot_water_facility.py new file mode 100644 index 00000000..ecb3a588 --- /dev/null +++ b/city_model_structure/attributes/domestic_hot_water_facility.py @@ -0,0 +1,53 @@ +""" +Building module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca +""" + + +class DomesticHotWaterFacility: + """ + Domestic Hot Water facilities class + """ + + def __init__(self, number_of_baths, number_of_showers, number_of_basin, water_storage_volume): + """ + Constructor + """ + + self._number_of_baths = number_of_baths + self._number_of_showers = number_of_showers + self._number_of_basin = number_of_basin + self._water_storage_volume = water_storage_volume + + @property + def number_of_baths(self): + """ + Get number of baths of a building unit + :return: number of baths + """ + return self._number_of_baths + + @property + def number_of_showers(self): + """ + Get number of showers of a building unit + :return: number of showers + """ + return self._number_of_showers + + @property + def number_of_basin(self): + """ + Get number of wash basins of a building unit + :return: number of wash basins + """ + return self._number_of_basin + + @property + def water_storage_volume(self): + """ + Get the volume of water storage + :return: volume of water storage + """ + return self._water_storage_volume diff --git a/city_model_structure/attributes/electric_appliances_facilities.py b/city_model_structure/attributes/electric_appliances_facilities.py new file mode 100644 index 00000000..357968bd --- /dev/null +++ b/city_model_structure/attributes/electric_appliances_facilities.py @@ -0,0 +1,26 @@ +""" +Building module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca +""" + + +class appliances_facilities: + """ + Electric appliance facilities class + """ + + def __init__(self, appliance_electric_power): + """ + Constructor + """ + + self._appliance_electric_power = appliance_electric_power + + @property + def appliance_electric_power(self): + """ + Get appliances electric power + :return: appliances electric power + """ + return self._appliance_electric_power diff --git a/city_model_structure/attributes/facility.py b/city_model_structure/attributes/facility.py new file mode 100644 index 00000000..186d8dfe --- /dev/null +++ b/city_model_structure/attributes/facility.py @@ -0,0 +1,59 @@ +""" +Building module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca +""" + + +class Facility: + """ + facilities class + """ + + def __init__(self, operation_schedules, convective_fraction, latent_fraction, + radiant_fraction, total_value_of_heat_dissipation): + self._operation_schedules = operation_schedules + self._convective_fraction = convective_fraction + self._latent_fraction = latent_fraction + self._radiant_fraction = radiant_fraction + self._total_value_of_heat_dissipation = total_value_of_heat_dissipation + + @property + def operation_schedules(self): + """ + Get operation schedules of the facilities + :return: operation schedules + """ + return self._operation_schedules + + @property + def convective_fraction(self): + """ + Get convective fraction value + :return: convective fraction + """ + return self._convective_fraction + + @property + def latent_fraction(self): + """ + Get latent fraction value + :return: latent fraction + """ + return self._latent_fraction + + @property + def radiant_fraction(self): + """ + Get radiant fraction value + :return: radiant fraction + """ + return self._radiant_fraction + + @property + def total_value_of_heat_dissipation(self): + """ + Get heat dissipation value + :return: heat dissipation + """ + return self._total_value_of_heat_dissipation diff --git a/city_model_structure/attributes/household.py b/city_model_structure/attributes/household.py new file mode 100644 index 00000000..81f9a19b --- /dev/null +++ b/city_model_structure/attributes/household.py @@ -0,0 +1,36 @@ +""" +Building module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca +""" + + +class household: + """ + Household class + """ + + def __init__(self, resident_type, household_type): + """ + Constructor + """ + + self._resident_type = resident_type + self._household_type = household_type + + + @property + def resident_type(self): + """ + Get resident type + :return: resident type + """ + return self._resident_type + + @property + def household_type(self): + """ + Get household type + :return: household type + """ + return self._household_type \ No newline at end of file diff --git a/city_model_structure/attributes/hvac_facility.py b/city_model_structure/attributes/hvac_facility.py new file mode 100644 index 00000000..9bde3160 --- /dev/null +++ b/city_model_structure/attributes/hvac_facility.py @@ -0,0 +1,36 @@ +""" +Building module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca +""" + + +class HvacFacility: + """ + HVAC facilities class + """ + + def __init__(self, temperature_setpoints, hvac_schedules): + """ + Constructor + """ + + self._temperature_setpoints = temperature_setpoints + self._hvac_schedules = hvac_schedules + + @property + def temperature_setpoints(self): + """ + Get temperature setpoints + :return: temperature setpoints + """ + return self._temperature_setpoints + + @property + def hvac_schedules(self): + """ + Get HVAC schedules + :return: HVAC schedules + """ + return self._hvac_schedules + diff --git a/city_model_structure/attributes/internal_gains.py b/city_model_structure/attributes/internal_gains.py new file mode 100644 index 00000000..4f4b1696 --- /dev/null +++ b/city_model_structure/attributes/internal_gains.py @@ -0,0 +1,84 @@ +""" +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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + self._latent_fraction = value diff --git a/city_model_structure/attributes/layer.py b/city_model_structure/attributes/layer.py new file mode 100644 index 00000000..54bc532d --- /dev/null +++ b/city_model_structure/attributes/layer.py @@ -0,0 +1,49 @@ +""" +Layers module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +from city_model_structure.attributes.material import Material + + +class Layer: + """ + Layer class + """ + def __init__(self): + self._material = None + self._thickness = None + + @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 + :return: None + """ + 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 + :return: None + """ + self._thickness = value diff --git a/city_model_structure/attributes/lighting_facilities.py b/city_model_structure/attributes/lighting_facilities.py new file mode 100644 index 00000000..62a84a04 --- /dev/null +++ b/city_model_structure/attributes/lighting_facilities.py @@ -0,0 +1,26 @@ +""" +Building module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca +""" + + +class lighting_facilities: + """ + Lighting facilities class + """ + + def __init__(self, electric_power): + """ + Constructor + """ + + self._electric_power = electric_power + + @property + def electric_power(self): + """ + Get lighting electric power + :return: lighting electric power + """ + return self._electric_power diff --git a/city_model_structure/attributes/material.py b/city_model_structure/attributes/material.py new file mode 100644 index 00000000..ddb23ba9 --- /dev/null +++ b/city_model_structure/attributes/material.py @@ -0,0 +1,156 @@ +""" +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._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 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + self._thermal_resistance = value diff --git a/city_model_structure/attributes/occupancy.py b/city_model_structure/attributes/occupancy.py new file mode 100644 index 00000000..6560e302 --- /dev/null +++ b/city_model_structure/attributes/occupancy.py @@ -0,0 +1,127 @@ +""" +Building module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca +""" + + +class Occupancy: + """ + Occupancy class + """ + + def __init__(self, internal_heat_gain, heat_dissipation, occupant_rate, occupant_type, occupant_zone, + number_of_occupants, arrival_time=None, departure_time=None, break_time=None, day_of_week=None, + pd_of_meetings_duration=None, occupant_schedule=None): + """ + Constructor + """ + + self._internal_heat_gain = internal_heat_gain + self._heat_dissipation = heat_dissipation + self._occupant_rate = occupant_rate + self._occupant_type = occupant_type + self._occupant_zone = occupant_zone + self._occupant_schedule = occupant_schedule + self._number_of_occupants = number_of_occupants + self._arrival_time = arrival_time + self._departure_time = departure_time + self._break_time = break_time + self._day_of_week = day_of_week + self._pd_of_meetings_duration = pd_of_meetings_duration + + @property + def internal_heat_gain(self): + """ + Get internal heat gain of occupants + :return: occupant heat gain + """ + return self._internal_heat_gain + + @property + def heat_dissipation(self): + """ + Get heat dissipation of occupants + :return: heat dissipation + """ + return self._heat_dissipation + + @property + def occupant_rate(self): + """ + Get rate of occupancy + :return: rate of occupancy + """ + return self._occupant_rate + + @property + def occupant_type(self): + """ + Get type of occupancy + :return: type of occupancy + """ + return self._occupant_type + + @property + def occupant_zone(self): + """ + Get the zone that occupant is in it + :return: occupant zone + """ + return self._occupant_zone + + @property + def occupant_schedule(self): + """ + Get the schedule when an occupant is in a zone + :return: occupant schedule + """ + return self._occupant_schedule + + @property + def number_of_occupants(self): + """ + Get the number of occupants + :return: number of occupants + """ + return self._number_of_occupants + + @property + def arrival_time(self): + """ + Get the arrival time of the occupant (for office building) + :return: arrival time + """ + return self._arrival_time + + @property + def departure_time(self): + """ + Get the departure time of the occupant (for office building) + :return: departure time + """ + return self._departure_time + + @property + def break_time(self): + """ + Get the lunch or break time of the occupant (for office building) + :return: break time + """ + return self._break_time + + @property + def day_of_week(self): + """ + Get the day of the week + :return: day of the week + """ + return self._day_of_week + + @property + def pd_of_meetings_duration(self): + """ + Get the probability distribution of the meeting duration + :return: probability distribution of the meeting duration + """ + return self._pd_of_meetings_duration diff --git a/city_model_structure/attributes/polyhedron.py b/city_model_structure/attributes/polyhedron.py new file mode 100644 index 00000000..a3ba84db --- /dev/null +++ b/city_model_structure/attributes/polyhedron.py @@ -0,0 +1,116 @@ +""" +Polyhedron module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +import numpy as np +from trimesh import Trimesh + +from helpers.geometry_helper import GeometryHelper + + +class Polyhedron: + """ + Polyhedron class + """ + def __init__(self, surfaces): + self._surfaces = list(surfaces) + self._polygons = [s.polygon for s in surfaces] + self._polyhedron = None + self._volume = None + self._faces = None + self._vertices = None + self._mesh = None + self._centroid = None + self._max_z = None + self._geometry = GeometryHelper() + + def _position_of(self, point): + vertices = self.vertices + for i in range(len(vertices)): + if self._geometry.almost_equal(vertices[i], point): + 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.points) for s in self._surfaces] + for vertex_1 in vertices: + found = False + for vertex_2 in self._vertices: + found = False + if self._geometry.almost_equal(vertex_1, vertex_2): + found = True + break + if not found: + self._vertices.append(vertex_1) + self._vertices = np.asarray(self._vertices) + return self._vertices + + @property + def faces(self) -> np.ndarray: + """ + Polyhedron faces + :return: np.ndarray([int]) + """ + if self._faces is None: + self._faces = [] + for surface in self._surfaces: + face = [] + points = surface.points + for point in points: + face.append(self._position_of(point)) + self._faces.append(face) + return self._faces + + @property + def _polyhedron_mesh(self): + if self._mesh is None: + self._mesh = Trimesh(vertices=self.vertices, faces=self.faces) + return self._mesh + + @property + def volume(self): + """ + Polyhedron volume in cubic meters + :return: float + """ + if self._volume is None: + if not self._polyhedron_mesh.is_volume: + print('The geometry is not a closed volume') + self._volume = np.inf + else: + self._volume = self._polyhedron_mesh.volume + return self._volume + + @property + def max_z(self): + """ + Polyhedron maximal z value + :return: float + """ + bounds = self._polyhedron_mesh.bounds + z_max = max(bounds[:, 2]) + return z_max + + @property + def centroid(self): + """ + Polyhedron centroid + :return: [x,y,z] + """ + return self._polyhedron_mesh.centroid + + def export(self, full_path): + """ + Export the polyhedron to stl given file + :param full_path: str + :return: None + """ + self._polyhedron_mesh.export(full_path) diff --git a/city_model_structure/attributes/schedule_value.py b/city_model_structure/attributes/schedule_value.py new file mode 100644 index 00000000..c949bdaa --- /dev/null +++ b/city_model_structure/attributes/schedule_value.py @@ -0,0 +1,34 @@ +""" +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): + """ + Constructor + """ + 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 \ No newline at end of file diff --git a/city_model_structure/attributes/surface.py b/city_model_structure/attributes/surface.py new file mode 100644 index 00000000..de206820 --- /dev/null +++ b/city_model_structure/attributes/surface.py @@ -0,0 +1,443 @@ +""" +Surface module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +from __future__ import annotations +from typing import Union + +import numpy as np +import pyny3d.geoms as pn +import pandas as pd +import helpers.constants as cte + +from helpers.geometry_helper import GeometryHelper + + +class Surface: + """ + Surface class + """ + def __init__(self, coordinates, surface_type=None, name=None, swr='0.2', remove_last=True, is_projected=False): + self._coordinates = coordinates + self._type = surface_type + self._name = name + self._swr = swr + self._remove_last = remove_last + self._is_projected = is_projected + self._geometry_helper = GeometryHelper() + self._polygon = None + self._ground_polygon = None + self._area = None + self._points = None + self._ground_points = None + self._points_list = None + self._normal = None + self._azimuth = None + self._inclination = None + self._area_above_ground = None + self._area_below_ground = None + self._parent = None + self._shapely = None + self._projected_surface = None + self._min_x = None + self._min_y = None + self._min_z = None + self._shared_surfaces = [] + self._global_irradiance = pd.DataFrame() + self._global_irradiance_hour = np.zeros(8760) + self._global_irradiance_month = np.zeros(12) + self._ground_coordinates = (self.min_x, self.min_y, self.min_z) + + def parent(self, parent, surface_id): + """ + Assign a city object as surface parent and a surface id + :param parent: CityObject + :param surface_id: str + :return: None + """ + self._parent = parent + self._name = str(surface_id) + + @property + def name(self): + """ + Surface name + :return: str + """ + if self._name is None: + raise Exception('surface has no name') + return self._name + + @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 + :return: None + """ + self._swr = value + + @property + def points(self) -> np.ndarray: + """ + Surface point matrix + :return: np.ndarray + """ + if self._points is None: + self._points = np.fromstring(self._coordinates, dtype=float, sep=' ') + self._points = GeometryHelper.to_points_matrix(self._points, self._remove_last) + return self._points + + def _min_coord(self, axis): + if axis == 'x': + axis = 0 + elif axis == 'y': + axis = 1 + else: + axis = 2 + min_coordinate = '' + for point in self.points: + if min_coordinate == '': + min_coordinate = point[axis] + elif min_coordinate > point[axis]: + min_coordinate = point[axis] + return min_coordinate + + @property + def min_x(self): + """ + Surface minimal x value + :return: float + """ + if self._min_x is None: + self._min_x = self._min_coord('x') + return self._min_x + + @property + def min_y(self): + """ + Surface minimal y value + :return: float + """ + if self._min_y is None: + self._min_y = self._min_coord('y') + return self._min_y + + @property + def min_z(self): + """ + Surface minimal z value + :return: float + """ + if self._min_z is None: + self._min_z = self._min_coord('z') + return self._min_z + + @property + def ground_points(self) -> np.ndarray: + """ + Surface grounded points matrix + :return: np.ndarray + """ + if self._ground_points is None: + coordinates = '' + for point in self.points: + x = point[0] - self._ground_coordinates[0] + y = point[1] - self._ground_coordinates[1] + z = point[2] - self._ground_coordinates[2] + if coordinates != '': + coordinates = coordinates + ' ' + coordinates = coordinates + str(x) + ' ' + str(y) + ' ' + str(z) + self._ground_points = np.fromstring(coordinates, dtype=float, sep=' ') + self._ground_points = GeometryHelper.to_points_matrix(self._ground_points, False) + return self._ground_points + + @property + def points_list(self) -> np.ndarray: + """ + Surface point list + :return: np.ndarray + """ + if self._points_list is None: + s = self.points + self._points_list = np.reshape(s, len(s) * 3) + return self._points_list + + @property + def polygon(self) -> Union[pn.Polygon, None]: + """ + Surface polygon + :return: None or pyny3d.Polygon + """ + if self._polygon is None: + try: + self._polygon = pn.Polygon(self.points) + except ValueError: + # is not really a polygon but a line so just return none + self._polygon = None + return self._polygon + + @property + def ground_polygon(self) -> Union[pn.Polygon, None]: + """ + Surface grounded polygon + :return: None or pyny3d.Polygon + """ + if self._ground_polygon is None: + try: + self._ground_polygon = pn.Polygon(self.ground_points) + except ValueError: + # is not really a polygon but a line so just return none + self._ground_polygon = None + return self._ground_polygon + + @property + def area(self): + """ + Surface area in square meters + :return: float + """ + if self._area is None: + self._area = self.polygon.get_area() + return self._area + + def _is_almost_same_terrain(self, terrain_points, ground_points): + equal = 0 + for terrain_point in terrain_points: + for ground_point in ground_points: + if self._geometry_helper.almost_equal(terrain_point, ground_point): + equal += 1 + return equal == len(terrain_points) + + @property + def _is_terrain(self): + for t_points in self._parent.terrains: + if len(t_points) == len(self.points) and self._is_almost_same_terrain(t_points, self.points): + return True + return False + + @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.area - self.area_below_ground + return self._area_above_ground + + @property + def area_below_ground(self): + """ + Surface area below ground in square meters + :return: float + """ + if self._area_below_ground is None: + self._area_below_ground = 0.0 + if self._is_terrain: + self._area_below_ground = self.area + return self._area_below_ground + + @property + def normal(self) -> np.ndarray: + """ + Surface normal vector + :return: np.ndarray + """ + if self._normal is None: + points = self.points + cross_product = np.cross(points[1] - points[0], points[2] - points[0]) + self._normal = cross_product / np.linalg.norm(cross_product) + return self._normal + + @property + def azimuth(self): + """ + Surface azimuth in radians + :return: float + """ + if self._azimuth is None: + normal = self.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.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 + + def add_shared(self, surface, intersection_area): + """ + Add a given surface and shared area in percent to this surface. + :param surface: + :param intersection_area: + :return: + """ + percent = intersection_area / self.area + self._shared_surfaces.append((percent, surface)) + + def shared(self, surface): + """ + Check if given surface share some area with this surface + :param surface: Surface + :return: None + """ + if self.type != 'Wall' or surface.type != 'Wall': + return + if self._geometry_helper.is_almost_same_surface(self, surface): + intersection_area = self.intersect(surface).area + self.add_shared(surface, intersection_area) + surface.add_shared(self, intersection_area) + + @property + def global_irradiance_hour(self): + """ + Get surface global irradiance hour in Wh/m2 + :return: float + """ + return self._global_irradiance_hour + + @global_irradiance_hour.setter + def global_irradiance_hour(self, value): + """ + Set surface global irradiance per hour in Wh/m2 + :param value: float + :return: None + """ + self._global_irradiance_hour = value + + @property + def global_irradiance_month(self): + """ + Get surface global irradiance per month in Wh/m2 + :return: float + """ + return self._global_irradiance_month + + @global_irradiance_month.setter + def global_irradiance_month(self, value): + """ + Set surface global irradiance per month in Wh/m2 + :param value: float + :return: None + """ + self._global_irradiance_month = value + + def global_irradiance(self, time_scale): + """ + Get surface global irradiance in Wh/m2 in a defined time_scale + :param time_scale: string. + :return: DataFrame(float) + """ + if time_scale == cte.time_scale['hour']: + self._global_irradiance = self.global_irradiance_hour + elif time_scale == cte.time_scale['month']: + self._global_irradiance = self.global_irradiance_month + else: + raise NotImplementedError + return self._global_irradiance + + @property + def shapely(self) -> Union[None, pn.Polygon]: + """ + Surface shapely (Z projection) + :return: None or pyny3d.Polygon + """ + if self.polygon is None: + return None + if self._shapely is None: + self._shapely = self.polygon.get_shapely() + return self._shapely + + @staticmethod + def _polygon_to_surface(polygon) -> Surface: + coordinates = '' + for coordinate in polygon.exterior.coords: + if coordinates != '': + coordinates = coordinates + ' ' + coordinates = coordinates + str(coordinate[0]) + ' ' + str(coordinate[1]) + ' 0.0' + return Surface(coordinates, remove_last=False) + + @property + def projection(self) -> Surface: + """ + Projected surface (Z projection) + :return: Surface + """ + if self._is_projected: + return self + if self._projected_surface is None: + shapely = self.shapely + if shapely is not None: + self._projected_surface = self._polygon_to_surface(shapely) + return self._projected_surface + + def intersect(self, surface) -> Union[Surface, None]: + """ + Get the intersection surface, if any, between the given surface and this surface + :param surface: Surface + :return: None or Surface + """ + min_x = min(self.min_x, surface.min_x) + min_y = min(self.min_y, surface.min_y) + min_z = min(self.min_z, surface.min_z) + self._ground_coordinates = (min_x, min_y, min_z) + surface._ground_coordinates = (min_x, min_y, min_z) + origin = (0, 0, 0) + azimuth = self.azimuth - (np.pi / 2) + while azimuth < 0: + azimuth += (np.pi / 2) + inclination = self.inclination - np.pi + while inclination < 0: + inclination += np.pi + polygon1 = self.ground_polygon.rotate(azimuth, 'z', origin).rotate(inclination, 'x', origin) + polygon2 = surface.ground_polygon.rotate(azimuth, 'z', origin).rotate(inclination, 'x', origin) + try: + coordinates = '' + intersection = pn.Surface([polygon1]).intersect_with(polygon2) + if len(intersection) == 0: + return None + for coordinate in pn.Surface([polygon1]).intersect_with(polygon2)[0]: + if coordinates != '': + coordinates = coordinates + ' ' + coordinates = coordinates + str(coordinate[0]) + ' ' + str(coordinate[1]) + ' 0.0' + if coordinates == '': + return None + intersect_surface = Surface(coordinates, remove_last=False) + if intersect_surface.polygon is None: + return None + + return Surface(coordinates, remove_last=False) + except Exception as err: + print('Error', err) + return None diff --git a/city_model_structure/attributes/thermal_boundary.py b/city_model_structure/attributes/thermal_boundary.py new file mode 100644 index 00000000..1d7d5dc8 --- /dev/null +++ b/city_model_structure/attributes/thermal_boundary.py @@ -0,0 +1,242 @@ +""" +ThermalBoundary module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +from typing import List + +from city_model_structure.attributes.layer import Layer +from city_model_structure.attributes.thermal_opening import ThermalOpening +from city_model_structure.attributes.thermal_zone import ThermalZone +from helpers.configuration_helper import ConfigurationHelper + + +class ThermalBoundary: + """ + ThermalBoundary class + """ + def __init__(self, surface, delimits): + self._surface = surface + self._delimits = delimits + # ToDo: up to at least LOD2 will be just one thermal opening per Thermal boundary, review for LOD3 and LOD4 + self._thermal_openings = [ThermalOpening()] + self._layers = None + self._outside_solar_absorptance = ConfigurationHelper().outside_solar_absorptance + self._outside_thermal_absorptance = None + self._outside_visible_absorptance = None + self._window_ratio = None + self._u_value = None + self._window_area = None + self._shortwave_reflectance = 1 - self._outside_solar_absorptance + + @property + def delimits(self) -> List[ThermalZone]: + """ + Get the thermal zones delimited by the thermal boundary + :return: [ThermalZone] + """ + return self._delimits + + @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 + """ + return self._surface.area + + @property + def area_above_ground(self): + """ + Thermal boundary area above ground in square meters + :return: float + """ + return self._surface.area_above_ground + + @property + def area_below_ground(self): + """ + Thermal boundary area below ground in square meters + :return: float + """ + return self._surface.area_below_ground + + @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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + self._outside_visible_absorptance = value + + @property + def thermal_openings(self) -> List[ThermalOpening]: + """ + Get thermal boundary thermal openings + :return: [ThermalOpening] + """ + return self._thermal_openings + + @thermal_openings.setter + def thermal_openings(self, value): + """ + Set thermal boundary thermal openings + :param value: [ThermalOpening] + :return: None + """ + self._thermal_openings = 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] + :return: None + """ + 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 + :return: None + """ + self._window_ratio = value + + @property + def window_area(self): + """ + Thermal boundary window area in square meters + :return: float + """ + if self._window_area is None: + try: + self._window_area = float(self._surface.area) * float(self.window_ratio) + except TypeError: + raise Exception('Window ratio is not defined or invalid surface area') + return self._window_area + + @property + def u_value(self): + """ + Thermal boundary u value in W/m2K + internal and external convective coefficient in W/m2K values, can be configured at configuration.ini + :return: float + """ + if self._u_value is None: + h_i = ConfigurationHelper().h_i + h_e = ConfigurationHelper().h_e + r_value = 1.0/h_i + 1.0/h_e + try: + for layer in self.layers: + 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') + return self._u_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 + :return: + """ + self._shortwave_reflectance = value + self._outside_solar_absorptance = 1.0 - float(value) diff --git a/city_model_structure/attributes/thermal_opening.py b/city_model_structure/attributes/thermal_opening.py new file mode 100644 index 00000000..3ed23816 --- /dev/null +++ b/city_model_structure/attributes/thermal_opening.py @@ -0,0 +1,171 @@ +""" +ThermalOpening module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +from helpers.configuration_helper import ConfigurationHelper + + +class ThermalOpening: + """ + ThermalOpening class + """ + def __init__(self): + self._openable_ratio = None + self._conductivity = None + self._frame_ratio = ConfigurationHelper().frame_ratio + 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 + + @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 + :return: None + """ + # 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 = ConfigurationHelper().h_i + h_e = ConfigurationHelper().h_e + r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self.thickness) + self._overall_u_value = 1 / r_value + + @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 + :return: None + """ + 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: + :return: + """ + self._g_value = value + + @property + def thickness(self): + """ + Get thermal opening thickness in meters + :return: + """ + return self._thickness + + @thickness.setter + def thickness(self, value): + """ + Set thermal opening thickness in meters + :param value: float + :return: None + """ + # The code to calculate overall_u_value is duplicated here and in conductivity. + # This ensures a more robust code that returns the overall_u_value regardless the order the parameters are read. + self._thickness = value + if self._overall_u_value is None and self.conductivity is not None: + h_i = ConfigurationHelper().h_i + h_e = ConfigurationHelper().h_e + r_value = 1 / h_i + 1 / h_e + float(self.conductivity) / float(self.thickness) + self._overall_u_value = 1 / r_value + + @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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + self._overall_u_value = value diff --git a/city_model_structure/attributes/thermal_zone.py b/city_model_structure/attributes/thermal_zone.py new file mode 100644 index 00000000..f6eea62a --- /dev/null +++ b/city_model_structure/attributes/thermal_zone.py @@ -0,0 +1,187 @@ +""" +ThermalZone module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +from typing import List, TypeVar + +from city_model_structure.attributes.surface import Surface +from city_model_structure.attributes.usage_zone import UsageZone +from helpers.configuration_helper import ConfigurationHelper + +ThermalBoundary = TypeVar('ThermalBoundary') + + +class ThermalZone: + """ + ThermalZone class + """ + def __init__(self, surfaces): + self._surfaces = surfaces + self._floor_area = None + self._bounded = None + self._heated = ConfigurationHelper().heated + self._cooled = ConfigurationHelper().cooled + self._additional_thermal_bridge_u_value = ConfigurationHelper().additional_thermal_bridge_u_value + self._effective_thermal_capacity = None + self._indirectly_heated_area_ratio = ConfigurationHelper().indirectly_heated_area_ratio + self._infiltration_rate_system_on = ConfigurationHelper().infiltration_rate_system_on + self._infiltration_rate_system_off = None + self._usage_zones = None + + @property + def heated(self): + """ + Get thermal zone heated flag + :return: Boolean + """ + return self._heated + + @property + def cooled(self): + """ + Get thermal zone cooled flag + :return: Boolean + """ + return self._cooled + + @property + def floor_area(self): + """ + Get thermal zone floor area in square meters + :return: float + """ + if self._floor_area is None: + self._floor_area = 0 + for s in self._surfaces: + if s.type == 'Ground': + self._floor_area += s.area + return self._floor_area + + @property + def bounded(self) -> List[ThermalBoundary]: + """ + Get thermal boundaries bounding with the thermal zone + :return: [ThermalBoundary] + """ + return self._bounded + + @bounded.setter + def bounded(self, value): + """ + Set thermal boundaries bounding with the thermal zone + :param value: [ThermalBoundary] + :return: None + """ + self._bounded = value + + @property + def surfaces(self) -> List[Surface]: + # todo: This property should be erased + """ + Get thermal zone surfaces + :return: [Surface] + """ + return self._surfaces + + @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 + :return: None + """ + self._additional_thermal_bridge_u_value = value + + @property + def effective_thermal_capacity(self): + """ + Get thermal zone effective thermal capacity + :return: float + """ + return self._effective_thermal_capacity + + @effective_thermal_capacity.setter + def effective_thermal_capacity(self, value): + """ + Set thermal zone effective thermal capacity + :param value: float + :return: None + """ + 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 + :return: None + """ + 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 + :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 + :param value: float + :return: None + """ + 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 + :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 + :param value: float + :return: None + """ + 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] + :return: None + """ + self._usage_zones = values diff --git a/city_model_structure/attributes/usage_zone.py b/city_model_structure/attributes/usage_zone.py new file mode 100644 index 00000000..e82f9be0 --- /dev/null +++ b/city_model_structure/attributes/usage_zone.py @@ -0,0 +1,195 @@ +""" +UsageZone module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" +from typing import List + +from city_model_structure.attributes.internal_gains import InternalGains +from helpers.configuration_helper import ConfigurationHelper + + +class UsageZone: + """ + UsageZone class + """ + def __init__(self): + self._usage = None + self._internal_gains = None + self._heating_setpoint = None + self._heating_setback = None + self._cooling_setpoint = None + self._hours_day = None + self._days_year = None + # todo: this must come from library, talk to Rabeeh + self._mechanical_air_change = ConfigurationHelper().min_air_change + self._occupancy = None + self._schedules = None + + @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] + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: None + """ + 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 + :return: 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 + :return: None + """ + self._days_year = value + + @property + def mechanical_air_change(self): + """ + Set usage zone mechanical air change in air change per hour + :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 + :param value: float + :return: None + """ + 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 + :return: None + """ + self._usage = value + + @property + def occupancy(self): + """ + Get occupancy data + :return: [Occupancy] + """ + return self._occupancy + + @occupancy.setter + def occupancy(self, values): + """ + Set occupancy + :param values: [Occupancy] + """ + self._occupancy = values + + @property + def schedules(self): + """ + Get schedule of Sundays + :return: [Sundays Schedule_Values] + """ + return self._schedules + + @schedules.setter + def schedules(self, value): + """ + occupancy schedules of Sundays + :param value: Sunday schedules + """ + self._schedules = value diff --git a/factories/weather/weather_factory.py b/factories/weather/weather_factory.py new file mode 100644 index 00000000..2e89266d --- /dev/null +++ b/factories/weather/weather_factory.py @@ -0,0 +1,37 @@ +""" +WeatherFactory retrieve the specific weather module for the given source format +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete pilar_monsalvete@yahoo.es +""" + +from factories.weather.weather_feeders.cli_weather_parameters import CliWeatherParameters +from factories.weather.weather_feeders.dat_weather_parameters import DatWeatherParameters + + +class WeatherFactory: + """ + WeatherFactory class + """ + + # todo: modify full_path_weather to make it depending on "city" + def __init__(self, handler, city, full_path_weather): + self._handler = '_' + handler.lower().replace(' ', '_') + self._city = city + self._full_path_weather = full_path_weather + self.factory() + + def _cli(self): + CliWeatherParameters(self._full_path_weather) + + def _dat(self): + DatWeatherParameters(self._full_path_weather) + + def _tmy(self): + raise Exception('Not implemented') + + def factory(self): + """ + Enrich the city with the usage information + :return: None + """ + getattr(self, self._handler, lambda: None)() diff --git a/factories/weather/weather_feeders/cli_weather_parameters.py b/factories/weather/weather_feeders/cli_weather_parameters.py new file mode 100644 index 00000000..aabfb4ab --- /dev/null +++ b/factories/weather/weather_feeders/cli_weather_parameters.py @@ -0,0 +1,13 @@ +""" +CliWeatherParameters class to extract weather parameters from a defined region in .cli format +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete pilar_monsalvete@yahoo.es +""" + + +class CliWeatherParameters: + """ + CliWeatherParameters class + """ + def __init__(self, full_path_weather): + self._full_path_weather = full_path_weather diff --git a/factories/weather/weather_feeders/dat_weather_parameters.py b/factories/weather/weather_feeders/dat_weather_parameters.py new file mode 100644 index 00000000..5145f524 --- /dev/null +++ b/factories/weather/weather_feeders/dat_weather_parameters.py @@ -0,0 +1,24 @@ +""" +CliWeatherParameters class to extract weather parameters from a defined region in .dat format +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete pilar_monsalvete@yahoo.es +""" + +import pandas as pd + + +class DatWeatherParameters: + """ + DatWeatherParameters class + """ + def __init__(self, full_path_weather): + self._full_path_weather = full_path_weather + self._weather_values = None + + def weather_values(self): + if self._full_path_weather is not None: + # TODO: catch error if file does not exist + if self._weather_values is None: + self._weather_values = pd.read_csv(self._full_path_weather, sep='\s+', header=None, + names=['hour', 'global_horiz', 'temperature', 'diffuse', 'beam', 'empty']) + return self._weather_values diff --git a/factories/weather/weather_feeders/helpers/weather.py b/factories/weather/weather_feeders/helpers/weather.py new file mode 100644 index 00000000..b8cadb17 --- /dev/null +++ b/factories/weather/weather_feeders/helpers/weather.py @@ -0,0 +1,31 @@ +import math +import pandas as pd +import helpers.constants as cte +from helpers import monthly_values as mv + + +class Weather(object): + def __init__(self, weather_values): + self._weather_values = pd.concat([mv.MonthlyValues().month_hour, weather_values], axis=1) + self._temperatures_hourly = None + + @property + def external_and_sky_temperatures(self): + if self._temperatures_hourly is None: + self._temperatures_hourly = self._weather_values[['month', 'temperature']] + sky_temperature = self.sky_temperature(self._temperatures_hourly) + self._temperatures_hourly = pd.concat([self._temperatures_hourly, sky_temperature], axis=1) + return self._temperatures_hourly + + @staticmethod + def sky_temperature(ambient_temperature): + # Swinbank - Fuentes sky model approximation(1963) based on cloudiness statistics(32 %) in United States + # ambient temperatures( in °C) + # sky temperatures( in °C) + _ambient_temperature = ambient_temperature[['temperature']].to_numpy() + values = [] + for temperature in _ambient_temperature: + value = 0.037536 * math.pow((temperature + cte.celsius_to_kelvin), 1.5) \ + + 0.32 * (temperature + cte.celsius_to_kelvin) - cte.celsius_to_kelvin + values.append(value) + return pd.DataFrame(values, columns=['sky temperature'])