2020-06-09 11:28:50 -04:00
|
|
|
"""
|
|
|
|
City module
|
|
|
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
2022-04-08 09:35:33 -04:00
|
|
|
Copyright © 2022 Concordia CERC group
|
|
|
|
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
|
|
Code contributors: Peter Yefi peteryefi@gmail.com
|
2020-06-09 11:28:50 -04:00
|
|
|
"""
|
2020-12-02 11:56:33 -05:00
|
|
|
from __future__ import annotations
|
2023-02-13 05:17:25 -05:00
|
|
|
|
|
|
|
import bz2
|
2023-05-26 11:20:10 -04:00
|
|
|
import logging
|
2020-06-09 11:28:50 -04:00
|
|
|
import sys
|
2020-12-02 11:56:33 -05:00
|
|
|
import pickle
|
2021-08-26 09:19:38 -04:00
|
|
|
import math
|
2022-04-04 19:51:30 -04:00
|
|
|
import copy
|
2020-05-28 15:22:46 -04:00
|
|
|
import pyproj
|
2022-04-04 19:51:30 -04:00
|
|
|
from typing import List, Union
|
2020-06-16 10:34:17 -04:00
|
|
|
from pyproj import Transformer
|
2021-09-14 13:46:48 -04:00
|
|
|
from pathlib import Path
|
2023-05-15 11:03:54 -04:00
|
|
|
from pandas import DataFrame
|
2023-01-24 10:51:50 -05:00
|
|
|
from hub.city_model_structure.building import Building
|
|
|
|
from hub.city_model_structure.city_object import CityObject
|
|
|
|
from hub.city_model_structure.city_objects_cluster import CityObjectsCluster
|
|
|
|
from hub.city_model_structure.buildings_cluster import BuildingsCluster
|
2023-05-17 17:10:30 -04:00
|
|
|
|
2023-01-24 10:51:50 -05:00
|
|
|
from hub.city_model_structure.iot.station import Station
|
|
|
|
from hub.city_model_structure.level_of_detail import LevelOfDetail
|
|
|
|
from hub.city_model_structure.parts_consisting_building import PartsConsistingBuilding
|
|
|
|
from hub.helpers.geometry_helper import GeometryHelper
|
|
|
|
from hub.helpers.location import Location
|
|
|
|
from hub.city_model_structure.energy_system import EnergySystem
|
2023-02-22 20:17:04 -05:00
|
|
|
import pandas as pd
|
2020-05-18 13:25:08 -04:00
|
|
|
|
2022-11-03 15:29:30 -04:00
|
|
|
|
2020-05-18 13:25:08 -04:00
|
|
|
class City:
|
2021-11-12 04:41:17 -05:00
|
|
|
"""
|
2020-06-09 11:28:50 -04:00
|
|
|
City class
|
|
|
|
"""
|
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
def __init__(self, lower_corner, upper_corner, srs_name):
|
|
|
|
self._name = None
|
|
|
|
self._lower_corner = lower_corner
|
|
|
|
self._upper_corner = upper_corner
|
2023-03-29 18:22:23 -04:00
|
|
|
self._buildings = []
|
2021-11-12 04:41:17 -05:00
|
|
|
self._srs_name = srs_name
|
|
|
|
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
|
|
|
|
self._energy_systems = None
|
|
|
|
self._fuels = None
|
2022-02-22 16:24:12 -05:00
|
|
|
self._machines = None
|
2022-02-21 13:26:14 -05:00
|
|
|
self._stations = []
|
2022-03-02 12:13:11 -05:00
|
|
|
self._lca_materials = None
|
2022-11-25 15:03:27 -05:00
|
|
|
self._level_of_detail = LevelOfDetail()
|
2023-03-17 16:32:54 -04:00
|
|
|
self._city_objects_dictionary = {}
|
2023-05-26 18:21:35 -04:00
|
|
|
self._city_objects_alias_dictionary = {}
|
2023-05-15 11:03:54 -04:00
|
|
|
self._energy_systems_connection_table = None
|
|
|
|
self._generic_energy_systems = None
|
2021-11-12 04:41:17 -05:00
|
|
|
|
|
|
|
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:
|
2023-01-16 07:57:16 -05:00
|
|
|
if self._srs_name in GeometryHelper.srs_transformations.keys():
|
|
|
|
self._srs_name = GeometryHelper.srs_transformations[self._srs_name]
|
2021-11-12 04:41:17 -05:00
|
|
|
input_reference = pyproj.CRS(self.srs_name) # Projected coordinate system from input data
|
2023-05-26 11:20:10 -04:00
|
|
|
except pyproj.exceptions.CRSError as err:
|
|
|
|
logging.error('Invalid projection reference system, please check the input data. (e.g. in CityGML files: srs_name)')
|
|
|
|
raise pyproj.exceptions.CRSError from err
|
2021-11-12 04:41:17 -05:00
|
|
|
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):
|
|
|
|
"""
|
2022-11-03 15:29:30 -04:00
|
|
|
Get models country code
|
2020-06-09 11:28:50 -04:00
|
|
|
:return: str
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._get_location().country
|
2020-05-28 15:22:46 -04:00
|
|
|
|
2023-02-07 13:01:49 -05:00
|
|
|
@property
|
|
|
|
def location(self):
|
|
|
|
return self._get_location().city
|
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get city name
|
2020-06-09 11:28:50 -04:00
|
|
|
:return: str
|
|
|
|
"""
|
2023-02-07 13:01:49 -05:00
|
|
|
if self._name is None:
|
|
|
|
return self._get_location().city
|
|
|
|
return self._name
|
2021-04-13 14:12:45 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def climate_reference_city(self) -> Union[None, str]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get the name for the climatic information reference city
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or str
|
2021-06-09 14:23:45 -04:00
|
|
|
"""
|
2023-04-27 10:59:03 -04:00
|
|
|
if self._climate_reference_city is None:
|
|
|
|
self._climate_reference_city = self._get_location().city
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._climate_reference_city
|
2021-04-13 14:12:45 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@climate_reference_city.setter
|
|
|
|
def climate_reference_city(self, value):
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Set the name for the climatic information reference city
|
2021-06-09 14:23:45 -04:00
|
|
|
:param value: str
|
|
|
|
"""
|
2023-04-27 10:59:03 -04:00
|
|
|
self._climate_reference_city = str(value)
|
2021-04-13 14:12:45 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def climate_file(self) -> Union[None, Path]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get the climate file full path
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or Path
|
2021-06-09 14:23:45 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._climate_file
|
2021-04-13 14:12:45 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@climate_file.setter
|
|
|
|
def climate_file(self, value):
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Set the climate file full path
|
2021-06-09 14:23:45 -04:00
|
|
|
:param value: Path
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
if value is not None:
|
|
|
|
self._climate_file = Path(value)
|
2020-05-18 13:25:08 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def city_objects(self) -> Union[List[CityObject], None]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get the city objects belonging to the city
|
2020-06-16 15:12:18 -04:00
|
|
|
:return: None or [CityObject]
|
2020-06-09 11:28:50 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
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)
|
|
|
|
if self.energy_systems is not None:
|
|
|
|
for energy_system in self.energy_systems:
|
|
|
|
self._city_objects.append(energy_system)
|
|
|
|
return self._city_objects
|
|
|
|
|
|
|
|
@property
|
|
|
|
def buildings(self) -> Union[List[Building], None]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get the buildings belonging to the city
|
2020-06-16 15:12:18 -04:00
|
|
|
:return: None or [Building]
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._buildings
|
2020-06-16 15:12:18 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def lower_corner(self) -> List[float]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get city lower corner
|
2020-06-09 11:28:50 -04:00
|
|
|
:return: [x,y,z]
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._lower_corner
|
2020-05-18 13:25:08 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def upper_corner(self) -> List[float]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get city upper corner
|
2020-06-09 11:28:50 -04:00
|
|
|
:return: [x,y,z]
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._upper_corner
|
2020-05-18 13:25:08 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
def city_object(self, name) -> Union[CityObject, None]:
|
|
|
|
"""
|
2020-06-09 11:28:50 -04:00
|
|
|
Retrieve the city CityObject with the given name
|
|
|
|
:param name:str
|
|
|
|
:return: None or CityObject
|
|
|
|
"""
|
2023-03-17 16:32:54 -04:00
|
|
|
if name in self._city_objects_dictionary:
|
|
|
|
return self.buildings[self._city_objects_dictionary[name]]
|
2021-11-12 04:41:17 -05:00
|
|
|
return None
|
2020-05-18 13:25:08 -04:00
|
|
|
|
2023-05-26 18:21:35 -04:00
|
|
|
def building_alias(self, alias) -> Union[CityObject, None]:
|
|
|
|
"""
|
|
|
|
Retrieve the city CityObject with the given alias alias
|
|
|
|
:alert: Building alias is not guaranteed to be unique
|
|
|
|
:param alias:str
|
|
|
|
:return: None or [CityObject]
|
|
|
|
"""
|
|
|
|
if alias in self._city_objects_alias_dictionary:
|
|
|
|
return [self.buildings[i] for i in self._city_objects_alias_dictionary[alias]]
|
|
|
|
return None
|
|
|
|
|
|
|
|
def add_building_alias(self, building, alias):
|
|
|
|
building_index = self._city_objects_dictionary[building.name]
|
|
|
|
if alias in self._city_objects_alias_dictionary.keys():
|
|
|
|
self._city_objects_alias_dictionary[alias].append(building_index)
|
|
|
|
else:
|
|
|
|
self._city_objects_alias_dictionary[alias] = [building_index]
|
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
def add_city_object(self, new_city_object):
|
|
|
|
"""
|
2020-06-09 11:28:50 -04:00
|
|
|
Add a CityObject to the city
|
|
|
|
:param new_city_object:CityObject
|
2021-06-09 14:23:45 -04:00
|
|
|
:return: None or not implemented error
|
2020-06-09 11:28:50 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
if new_city_object.type == 'building':
|
|
|
|
if self._buildings is None:
|
|
|
|
self._buildings = []
|
2023-05-26 18:21:35 -04:00
|
|
|
new_city_object._alias_dictionary = self._city_objects_alias_dictionary
|
2021-11-12 04:41:17 -05:00
|
|
|
self._buildings.append(new_city_object)
|
2023-03-17 16:32:54 -04:00
|
|
|
self._city_objects_dictionary[new_city_object.name] = len(self._buildings) - 1
|
2023-05-26 18:21:35 -04:00
|
|
|
if new_city_object.aliases is not None:
|
|
|
|
for alias in new_city_object.aliases:
|
|
|
|
if alias in self._city_objects_alias_dictionary:
|
|
|
|
self._city_objects_alias_dictionary[alias].append(len(self._buildings) - 1)
|
|
|
|
else:
|
|
|
|
self._city_objects_alias_dictionary[alias] = [len(self._buildings) - 1]
|
2021-11-12 04:41:17 -05:00
|
|
|
elif new_city_object.type == 'energy_system':
|
|
|
|
if self._energy_systems is None:
|
|
|
|
self._energy_systems = []
|
|
|
|
self._energy_systems.append(new_city_object)
|
|
|
|
else:
|
|
|
|
raise NotImplementedError(new_city_object.type)
|
|
|
|
|
|
|
|
def remove_city_object(self, city_object):
|
|
|
|
"""
|
2021-05-25 13:34:57 -04:00
|
|
|
Remove a CityObject from the city
|
|
|
|
:param city_object:CityObject
|
|
|
|
:return: None
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
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)
|
2023-03-21 14:22:28 -04:00
|
|
|
# regenerate hash map
|
|
|
|
self._city_objects_dictionary.clear()
|
2023-05-26 18:21:35 -04:00
|
|
|
self._city_objects_alias_dictionary.clear()
|
2023-03-21 14:22:28 -04:00
|
|
|
for i, city_object in enumerate(self._buildings):
|
|
|
|
self._city_objects_dictionary[city_object.name] = i
|
2023-05-26 18:21:35 -04:00
|
|
|
for alias in city_object.aliases:
|
|
|
|
if alias in self._city_objects_alias_dictionary:
|
|
|
|
self._city_objects_alias_dictionary[alias].append(i)
|
|
|
|
else:
|
|
|
|
self._city_objects_alias_dictionary[alias] = [i]
|
2021-11-12 04:41:17 -05:00
|
|
|
|
|
|
|
@property
|
|
|
|
def srs_name(self) -> Union[None, str]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get city srs name
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or str
|
2020-06-09 11:28:50 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._srs_name
|
2020-05-18 13:25:08 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@name.setter
|
|
|
|
def name(self, value):
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Set city name
|
2020-06-10 11:08:38 -04:00
|
|
|
:param value:str
|
2020-06-09 11:28:50 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
if value is not None:
|
|
|
|
self._name = str(value)
|
2020-12-02 11:56:33 -05:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@staticmethod
|
|
|
|
def load(city_filename) -> City:
|
|
|
|
"""
|
2020-12-02 11:56:33 -05:00
|
|
|
Load a city saved with city.save(city_filename)
|
|
|
|
:param city_filename: city filename
|
|
|
|
:return: City
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
with open(city_filename, 'rb') as file:
|
|
|
|
return pickle.load(file)
|
2020-12-02 11:56:33 -05:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
def save(self, city_filename):
|
|
|
|
"""
|
2020-12-02 11:56:33 -05:00
|
|
|
Save a city into the given filename
|
|
|
|
:param city_filename: destination city filename
|
2021-09-16 13:45:27 -04:00
|
|
|
:return: None
|
2020-12-02 11:56:33 -05:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
with open(city_filename, 'wb') as file:
|
|
|
|
pickle.dump(self, file)
|
2021-01-27 16:08:11 -05:00
|
|
|
|
2023-02-13 05:17:25 -05:00
|
|
|
def save_compressed(self, city_filename):
|
|
|
|
"""
|
|
|
|
Save a city into the given filename
|
|
|
|
:param city_filename: destination city filename
|
|
|
|
:return: None
|
|
|
|
"""
|
2023-02-13 06:12:07 -05:00
|
|
|
with bz2.BZ2File(city_filename, 'wb') as f:
|
2023-02-13 05:17:25 -05:00
|
|
|
pickle.dump(self, f)
|
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
def region(self, center, radius) -> City:
|
|
|
|
"""
|
2021-09-16 13:45:27 -04:00
|
|
|
Get a region from the city
|
2021-01-27 16:08:11 -05:00
|
|
|
:param center: specific point in space [x, y, z]
|
|
|
|
:param radius: distance to center of the sphere selected in meters
|
|
|
|
:return: selected_region_city
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
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
|
2022-11-03 15:29:30 -04:00
|
|
|
# selected_region_city.climate_reference_city = self.climate_reference_city
|
2021-11-12 04:41:17 -05:00
|
|
|
for city_object in self.city_objects:
|
|
|
|
location = city_object.centroid
|
|
|
|
if location is not None:
|
2022-11-03 15:29:30 -04:00
|
|
|
distance = math.sqrt(math.pow(location[0] - center[0], 2) + math.pow(location[1] - center[1], 2)
|
|
|
|
+ math.pow(location[2] - center[2], 2))
|
2021-11-12 04:41:17 -05:00
|
|
|
if distance < radius:
|
|
|
|
selected_region_city.add_city_object(city_object)
|
|
|
|
return selected_region_city
|
|
|
|
|
|
|
|
@property
|
|
|
|
def latitude(self) -> Union[None, float]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get city latitude in degrees
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or float
|
2021-04-13 15:09:13 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._latitude
|
2021-04-13 19:00:28 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@latitude.setter
|
|
|
|
def latitude(self, value):
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Set city latitude in degrees
|
2021-06-09 14:23:45 -04:00
|
|
|
:parameter value: float
|
2021-04-13 19:00:28 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
if value is not None:
|
|
|
|
self._latitude = float(value)
|
2021-04-13 19:00:28 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def longitude(self) -> Union[None, float]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get city longitude in degrees
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or float
|
2021-04-13 19:00:28 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._longitude
|
2021-04-13 19:00:28 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@longitude.setter
|
|
|
|
def longitude(self, value):
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Set city longitude in degrees
|
2021-06-09 14:23:45 -04:00
|
|
|
:parameter value: float
|
2021-04-13 19:00:28 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
if value is not None:
|
|
|
|
self._longitude = float(value)
|
2021-04-13 19:00:28 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def time_zone(self) -> Union[None, float]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get city time_zone
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or float
|
2021-04-13 19:00:28 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._time_zone
|
2021-04-13 19:00:28 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@time_zone.setter
|
|
|
|
def time_zone(self, value):
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Set city time_zone
|
2021-06-09 14:23:45 -04:00
|
|
|
:parameter value: float
|
2021-04-13 19:00:28 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
if value is not None:
|
|
|
|
self._time_zone = float(value)
|
2021-06-03 11:45:17 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def buildings_clusters(self) -> Union[List[BuildingsCluster], None]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get buildings clusters belonging to the city
|
2021-06-03 11:45:17 -04:00
|
|
|
:return: None or [BuildingsCluster]
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._buildings_clusters
|
2021-06-03 11:45:17 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def parts_consisting_buildings(self) -> Union[List[PartsConsistingBuilding], None]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get parts consisting buildings belonging to the city
|
2021-06-03 11:45:17 -04:00
|
|
|
:return: None or [PartsConsistingBuilding]
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._parts_consisting_buildings
|
2021-06-03 11:45:17 -04:00
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def energy_systems(self) -> Union[List[EnergySystem], None]:
|
|
|
|
"""
|
2022-02-21 13:26:14 -05:00
|
|
|
Get energy systems belonging to the city
|
|
|
|
:return: None or [EnergySystem]
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
return self._energy_systems
|
2021-10-21 09:49:26 -04:00
|
|
|
|
2022-02-21 13:26:14 -05:00
|
|
|
@property
|
|
|
|
def stations(self) -> [Station]:
|
|
|
|
"""
|
|
|
|
Get the sensors stations belonging to the city
|
|
|
|
:return: [Station]
|
|
|
|
"""
|
|
|
|
return self._stations
|
|
|
|
|
2021-11-12 04:41:17 -05:00
|
|
|
@property
|
|
|
|
def city_objects_clusters(self) -> Union[List[CityObjectsCluster], None]:
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get city objects clusters belonging to the city
|
2021-06-03 11:45:17 -04:00
|
|
|
:return: None or [CityObjectsCluster]
|
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
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):
|
|
|
|
"""
|
2021-06-03 11:45:17 -04:00
|
|
|
Add a CityObject to the city
|
|
|
|
:param new_city_objects_cluster:CityObjectsCluster
|
2021-06-09 14:23:45 -04:00
|
|
|
:return: None or NotImplementedError
|
2021-06-03 11:45:17 -04:00
|
|
|
"""
|
2021-11-12 04:41:17 -05:00
|
|
|
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
|
2022-03-02 12:13:11 -05:00
|
|
|
|
2022-04-04 19:51:30 -04:00
|
|
|
@property
|
|
|
|
def copy(self) -> City:
|
|
|
|
"""
|
|
|
|
Get a copy of the current city
|
|
|
|
"""
|
|
|
|
return copy.deepcopy(self)
|
2022-10-20 11:34:22 -04:00
|
|
|
|
|
|
|
def merge(self, city) -> City:
|
|
|
|
_merge_city = self.copy
|
2023-02-22 20:17:04 -05:00
|
|
|
selected_city_object = None
|
2023-03-01 20:36:01 -05:00
|
|
|
building = None
|
2023-02-22 20:17:04 -05:00
|
|
|
# set initial minimum radiation to a higher number
|
2023-03-01 20:36:01 -05:00
|
|
|
min_radiation = 1999999
|
2022-10-20 11:34:22 -04:00
|
|
|
for city_object in city.city_objects:
|
2023-02-22 20:17:04 -05:00
|
|
|
if city_object.type == 'building':
|
2023-03-01 20:36:01 -05:00
|
|
|
building = city_object
|
2023-02-22 20:17:04 -05:00
|
|
|
building_radiation = 0
|
|
|
|
for surface in city_object.surfaces:
|
|
|
|
radiation = surface.global_irradiance
|
2023-03-01 20:36:01 -05:00
|
|
|
if 'year' not in radiation and 'month' not in radiation:
|
2023-03-14 11:28:34 -04:00
|
|
|
|
2023-03-01 20:36:01 -05:00
|
|
|
continue
|
|
|
|
elif "year" in radiation:
|
|
|
|
building_radiation += radiation["year"].iloc[0]
|
|
|
|
elif "month" in radiation and "year" not in radiation:
|
|
|
|
surface.global_irradiance["year"] = pd.DataFrame({radiation["month"].sum()})
|
|
|
|
building_radiation += radiation["year"].iloc[0]
|
2023-02-22 20:17:04 -05:00
|
|
|
if building_radiation < min_radiation:
|
|
|
|
min_radiation = building_radiation
|
|
|
|
selected_city_object = city_object
|
2023-03-14 11:28:34 -04:00
|
|
|
# merge the city object with the minimum radiation
|
|
|
|
if selected_city_object is not None:
|
|
|
|
_merge_city.add_city_object(selected_city_object)
|
|
|
|
else:
|
|
|
|
_merge_city.add_city_object(building)
|
2022-10-20 11:34:22 -04:00
|
|
|
return _merge_city
|
2022-11-25 15:03:27 -05:00
|
|
|
|
|
|
|
@property
|
2022-12-01 15:33:14 -05:00
|
|
|
def level_of_detail(self) -> LevelOfDetail:
|
|
|
|
"""
|
|
|
|
Get level of detail of different aspects of the city: geometry, construction and usage
|
|
|
|
:return: LevelOfDetail
|
|
|
|
"""
|
|
|
|
return self._level_of_detail
|
2023-02-23 06:56:13 -05:00
|
|
|
|
2023-05-15 11:03:54 -04:00
|
|
|
@property
|
|
|
|
def energy_systems_connection_table(self) -> Union[None, DataFrame]:
|
|
|
|
"""
|
|
|
|
Get energy systems connection table which includes at least two columns: energy_system_type and associated_building
|
|
|
|
and may also include dimensioned_energy_system and connection_building_to_dimensioned_energy_system
|
|
|
|
:return: DataFrame
|
|
|
|
"""
|
|
|
|
return self._energy_systems_connection_table
|
|
|
|
|
|
|
|
@energy_systems_connection_table.setter
|
|
|
|
def energy_systems_connection_table(self, value):
|
|
|
|
"""
|
|
|
|
Set energy systems connection table which includes at least two columns: energy_system_type and associated_building
|
|
|
|
and may also include dimensioned_energy_system and connection_building_to_dimensioned_energy_system
|
|
|
|
:param value: DataFrame
|
|
|
|
"""
|
|
|
|
self._energy_systems_connection_table = value
|
2023-02-23 06:56:13 -05:00
|
|
|
|
2023-05-15 11:03:54 -04:00
|
|
|
@property
|
|
|
|
def generic_energy_systems(self) -> dict:
|
|
|
|
"""
|
|
|
|
Get dictionary with generic energy systems installed in the city
|
|
|
|
:return: dict
|
|
|
|
"""
|
|
|
|
return self._generic_energy_systems
|
|
|
|
|
|
|
|
@generic_energy_systems.setter
|
|
|
|
def generic_energy_systems(self, value):
|
|
|
|
"""
|
|
|
|
Set dictionary with generic energy systems installed in the city
|
|
|
|
:return: dict
|
|
|
|
"""
|
|
|
|
self._generic_energy_systems = value
|