diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 296182f1..d5129142 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -6,8 +6,10 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ +import sys from typing import List, Union import numpy as np +from hub.hub_logger import logger import hub.helpers.constants as cte from hub.city_model_structure.building_demand.surface import Surface from hub.city_model_structure.city_object import CityObject @@ -46,20 +48,31 @@ class Building(CityObject): self._roofs = [] self._walls = [] self._internal_walls = [] + self._ground_walls = [] + self._attic_floors = [] + self._interior_slabs = [] 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 == cte.GROUND: self._grounds.append(surface) elif surface.type == cte.WALL: self._walls.append(surface) elif surface.type == cte.ROOF: self._roofs.append(surface) - else: + elif surface.type == cte.INTERIOR_WALL: self._internal_walls.append(surface) + elif surface.type == cte.GROUND_WALL: + self._ground_walls.append(surface) + elif surface.type == cte.ATTIC_FLOOR: + self._attic_floors.append(surface) + elif surface.type == cte.INTERIOR_SLAB: + self._interior_slabs.append(surface) + else: + logger.error(f'Building {self.name} [alias {self.alias}] has an unexpected surface type {surface.type}.\n') + sys.stderr.write(f'Building {self.name} [alias {self.alias}] has an unexpected surface type {surface.type}.\n') @property def shell(self) -> Polyhedron: diff --git a/hub/city_model_structure/building_demand/occupancy.py b/hub/city_model_structure/building_demand/occupancy.py index 99e4ab23..46d34aa1 100644 --- a/hub/city_model_structure/building_demand/occupancy.py +++ b/hub/city_model_structure/building_demand/occupancy.py @@ -6,7 +6,6 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ from typing import Union, List from hub.city_model_structure.attributes.schedule import Schedule -from hub.city_model_structure.building_demand.occupant import Occupant class Occupancy: @@ -106,19 +105,3 @@ class Occupancy: :param value: [Schedule] """ self._occupancy_schedules = value - - @property - def occupants(self) -> Union[None, List[Occupant]]: - """ - Get list of occupants - :return: None or List of Occupant - """ - return self._occupants - - @occupants.setter - def occupants(self, value): - """ - Set list of occupants - :param value: [Occupant] - """ - self._occupants = value diff --git a/hub/city_model_structure/building_demand/occupant.py b/hub/city_model_structure/building_demand/occupant.py deleted file mode 100644 index a5916d80..00000000 --- a/hub/city_model_structure/building_demand/occupant.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -Occupant module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Sanam Dabirian sanam.dabirian@mail.concordia.ca -Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - - -class Occupant: - """ - Occupant class - """ - - def __init__(self): - """ - Constructor - """ - - self._heat_dissipation = None - self._occupancy_rate = None - self._occupant_type = None - self._arrival_time = None - self._departure_time = None - self._break_time = None - self._day_of_week = None - self._pd_of_meetings_duration = 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 = float(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 = float(value) - - @property - def occupant_type(self): - """ - Get type of schedules - :return: str - """ - return self._occupant_type - - @occupant_type.setter - def occupant_type(self, value): - """ - Set type of schedules - :param value: float - """ - self._occupant_type = float(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: str - """ - 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: str - """ - # 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 diff --git a/hub/city_model_structure/building_demand/surface.py b/hub/city_model_structure/building_demand/surface.py index 1c4e4cac..dc392160 100644 --- a/hub/city_model_structure/building_demand/surface.py +++ b/hub/city_model_structure/building_demand/surface.py @@ -72,14 +72,6 @@ class Surface: if value is not None: self._id = str(value) - # todo: implement share surfaces - @property - def share_surfaces(self): - """ - Raises not implemented error - """ - raise NotImplementedError - def _max_coord(self, axis): if axis == 'x': axis = 0 @@ -163,11 +155,11 @@ class Surface: @property def type(self): """ - Get surface type Ground, Wall or Roof + Get surface type Ground, Ground wall, Wall, Attic floor, Interior slab, Interior wall, Roof or Virtual internal + If the geometrical LoD is lower than 4, + the surfaces' types are not defined in the importer and can only be Ground, Wall or Roof :return: str """ - - # todo: there are more types: internal wall, internal floor... this method must be redefined if self._type is None: grad = np.rad2deg(self.inclination) if grad >= 170: @@ -304,7 +296,7 @@ class Surface: :return: Surface, Surface, Any """ # todo: check return types - # todo: recheck this method for LoD3 (windows) + # recheck this method for LoD3 (windows) origin = Point([0, 0, z]) normal = np.array([0, 0, 1]) plane = Plane(normal=normal, origin=origin) diff --git a/hub/city_model_structure/city.py b/hub/city_model_structure/city.py index ef8ebbd1..be5d954f 100644 --- a/hub/city_model_structure/city.py +++ b/hub/city_model_structure/city.py @@ -23,7 +23,6 @@ from hub.city_model_structure.iot.station import Station from hub.city_model_structure.level_of_detail import LevelOfDetail from hub.city_model_structure.machine import Machine from hub.city_model_structure.parts_consisting_building import PartsConsistingBuilding -from hub.city_model_structure.subway_entrance import SubwayEntrance from hub.helpers.geometry_helper import GeometryHelper from hub.helpers.location import Location from hub.city_model_structure.energy_system import EnergySystem @@ -40,9 +39,7 @@ class City: self._lower_corner = lower_corner self._upper_corner = upper_corner self._buildings = None - self._subway_entrances = None self._srs_name = srs_name - # 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 @@ -163,9 +160,6 @@ class City: if self.buildings is not None: for building in self.buildings: self._city_objects.append(building) - if self.subway_entrances is not None: - for subway_entrance in self.subway_entrances: - self._city_objects.append(subway_entrance) if self.energy_systems is not None: for energy_system in self.energy_systems: self._city_objects.append(energy_system) @@ -179,14 +173,6 @@ class City: """ return self._buildings - @property - def subway_entrances(self) -> Union[List[SubwayEntrance], None]: - """ - Get the subway entrances belonging to the city - :return: a list of subway entrances objects or none - """ - return self._subway_entrances - @property def lower_corner(self) -> List[float]: """ @@ -224,10 +210,6 @@ class City: if self._buildings is None: self._buildings = [] self._buildings.append(new_city_object) - elif new_city_object.type == 'subway_entrance': - if self._subway_entrances is None: - self._subway_entrances = [] - self._subway_entrances.append(new_city_object) elif new_city_object.type == 'energy_system': if self._energy_systems is None: self._energy_systems = [] diff --git a/hub/city_model_structure/subway_entrance.py b/hub/city_model_structure/subway_entrance.py deleted file mode 100644 index cd0c8598..00000000 --- a/hub/city_model_structure/subway_entrance.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -Subway entrance module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -from hub.city_model_structure.city_object import CityObject - - -class SubwayEntrance(CityObject): - """ - SubwayEntrance(CityObject) class - """ - def __init__(self, name, latitude, longitude): - super().__init__(name, 0) - 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: str - """ - return self._name diff --git a/hub/helpers/monthly_to_hourly_demand.py b/hub/helpers/monthly_to_hourly_demand.py deleted file mode 100644 index a83b9673..00000000 --- a/hub/helpers/monthly_to_hourly_demand.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -monthly_to_hourly_demand module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import calendar as cal -import pandas as pd -from hub.city_model_structure.building_demand.occupant import Occupant -import hub.helpers.constants as cte - - -class MonthlyToHourlyDemand: - """ - MonthlyToHourlyDemand class - """ - def __init__(self, building, conditioning_seasons): - self._hourly_heating = pd.DataFrame() - self._hourly_cooling = pd.DataFrame() - self._building = building - self._conditioning_seasons = conditioning_seasons - - def hourly_heating(self, key): - """ - hourly distribution of the monthly heating of a building - :param key: string - :return: [hourly_heating] - """ - # todo: this method and the insel model have to be reviewed for more than one thermal zone - external_temp = self._building.external_temperature[cte.HOUR] - # todo: review index depending on how the schedules are defined, either 8760 or 24 hours - for usage in self._building.usages: - temp_set = float(usage.heating_setpoint)-3 - temp_back = float(usage.heating_setback)-3 - # todo: if these are data frames, then they should be called as (Occupancy should be in low case): - # usage.schedules.Occupancy - # self._conditioning_seasons.heating - occupancy = Occupant().get_complete_year_schedule(usage.schedules['Occupancy']) - heating_schedule = self._conditioning_seasons['heating'] - - hourly_heating = [] - i = 0 - j = 0 - temp_grad_day = [] - for month in range(1, 13): - temp_grad_month = 0 - month_range = cal.monthrange(2015, month)[1] - for _ in range(1, month_range+1): - external_temp_med = 0 - for hour in range(0, 24): - external_temp_med += external_temp[key][i]/24 - for hour in range(0, 24): - if external_temp_med < temp_set and heating_schedule[month-1] == 1: - if occupancy[hour] > 0: - hdd = temp_set - external_temp[key][i] - if hdd < 0: - hdd = 0 - temp_grad_day.append(hdd) - else: - hdd = temp_back - external_temp[key][i] - if hdd < 0: - hdd = 0 - temp_grad_day.append(hdd) - else: - temp_grad_day.append(0) - - temp_grad_month += temp_grad_day[i] - i += 1 - - for _ in range(1, month_range + 1): - for hour in range(0, 24): - monthly_demand = self._building.heating[cte.MONTH][month-1] - if monthly_demand == 'NaN': - monthly_demand = 0 - if temp_grad_month == 0: - hourly_demand = 0 - else: - hourly_demand = float(monthly_demand)*float(temp_grad_day[j])/float(temp_grad_month) - hourly_heating.append(hourly_demand) - j += 1 - self._hourly_heating = pd.DataFrame(data=hourly_heating, columns=['monthly to hourly']) - return self._hourly_heating - - def hourly_cooling(self, key): - """ - hourly distribution of the monthly cooling of a building - :param key: string - :return: [hourly_cooling] - """ - # todo: this method and the insel model have to be reviewed for more than one thermal zone - external_temp = self._building.external_temperature[cte.HOUR] - # todo: review index depending on how the schedules are defined, either 8760 or 24 hours - for usage in self._building.usages: - temp_set = float(usage.cooling_setpoint) - temp_back = 100 - occupancy = Occupant().get_complete_year_schedule(usage.schedules['Occupancy']) - cooling_schedule = self._conditioning_seasons['cooling'] - - hourly_cooling = [] - i = 0 - j = 0 - temp_grad_day = [] - for month in range(1, 13): - temp_grad_month = 0 - month_range = cal.monthrange(2015, month)[1] - for _ in range(1, month_range[1] + 1): - for hour in range(0, 24): - if external_temp[key][i] > temp_set and cooling_schedule[month - 1] == 1: - if occupancy[hour] > 0: - cdd = external_temp[key][i] - temp_set - if cdd < 0: - cdd = 0 - temp_grad_day.append(cdd) - else: - cdd = external_temp[key][i] - temp_back - if cdd < 0: - cdd = 0 - temp_grad_day.append(cdd) - else: - temp_grad_day.append(0) - - temp_grad_month += temp_grad_day[i] - i += 1 - - for _ in range(1, month_range[1] + 1): - for hour in range(0, 24): - # monthly_demand = self._building.heating[cte.MONTH]['INSEL'][month-1] - monthly_demand = self._building.cooling[cte.MONTH][month - 1] - if monthly_demand == 'NaN': - monthly_demand = 0 - if temp_grad_month == 0: - hourly_demand = 0 - else: - hourly_demand = float(monthly_demand) * float(temp_grad_day[j]) / float(temp_grad_month) - hourly_cooling.append(hourly_demand) - j += 1 - self._hourly_cooling = pd.DataFrame(data=hourly_cooling, columns=['monthly to hourly']) - return self._hourly_cooling diff --git a/hub/imports/geometry/osm_subway.py b/hub/imports/geometry/osm_subway.py deleted file mode 100644 index 8e0bd27b..00000000 --- a/hub/imports/geometry/osm_subway.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -OsmSubway module parses osm files and import the metro location into the city model structure -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -""" - -import sys -import xmltodict -from pyproj import Transformer -from hub.city_model_structure.city import City -from hub.city_model_structure.subway_entrance import SubwayEntrance - - -class OsmSubway: - """ - Open street map subway - """ - def __init__(self, path): - self._city = None - self._subway_entrances = [] - with open(path) as osm: - self._osm = xmltodict.parse(osm.read(), force_list='tag') - for node in self._osm['osm']['node']: - if 'tag' not in node: - continue - for tag in node['tag']: - if '@v' not in tag: - continue - if tag['@v'] == 'subway_entrance': - subway_entrance = SubwayEntrance(node['@id'], node['@lat'], node['@lon']) - self._subway_entrances.append(subway_entrance) - - @property - def city(self) -> City: - """ - Get a city with subway entrances - """ - transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857") - lower_corner = [sys.float_info.max, sys.float_info.max, 0] - upper_corner = [sys.float_info.min, sys.float_info.min, 0] - x = 0 - y = 1 - for subway_entrance in self._subway_entrances: - coordinate = transformer.transform(subway_entrance.longitude, subway_entrance.latitude) - if coordinate[x] >= upper_corner[x]: - upper_corner[x] = coordinate[x] - if coordinate[y] >= upper_corner[y]: - upper_corner[y] = coordinate[y] - if coordinate[x] < lower_corner[x]: - lower_corner[x] = coordinate[x] - if coordinate[y] < lower_corner[y]: - lower_corner[y] = coordinate[y] - - city = City(lower_corner, upper_corner, 'unknown') - for subway_entrance in self._subway_entrances: - city.add_city_object(subway_entrance) - return city