Hub now create a list of aliases instead of one

This commit is contained in:
Guille Gutierrez 2023-05-26 18:21:35 -04:00
parent d650a713c4
commit 04bf3bbef3
8 changed files with 100 additions and 53 deletions

View File

@ -7,7 +7,7 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
""" """
import logging import logging
from typing import List, Union from typing import List, Union, TypeVar
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@ -21,13 +21,16 @@ from hub.city_model_structure.city_object import CityObject
from hub.city_model_structure.energy_systems.energy_system import EnergySystem from hub.city_model_structure.energy_systems.energy_system import EnergySystem
from hub.helpers.peak_loads import PeakLoads from hub.helpers.peak_loads import PeakLoads
City = TypeVar('City')
class Building(CityObject): class Building(CityObject):
""" """
Building(CityObject) class Building(CityObject) class
""" """
def __init__(self, name, surfaces, year_of_construction, function, terrains=None): def __init__(self, name, surfaces, year_of_construction, function, terrains=None, city=None):
super().__init__(name, surfaces) super().__init__(name, surfaces)
self._city = city
self._households = None self._households = None
self._basement_heated = None self._basement_heated = None
self._attic_heated = None self._attic_heated = None
@ -40,7 +43,7 @@ class Building(CityObject):
self._roof_type = None self._roof_type = None
self._internal_zones = None self._internal_zones = None
self._shell = None self._shell = None
self._alias = None self._aliases = None
self._type = 'building' self._type = 'building'
self._cold_water_temperature = dict() self._cold_water_temperature = dict()
self._heating = dict() self._heating = dict()
@ -82,7 +85,7 @@ class Building(CityObject):
elif surface.type == cte.INTERIOR_SLAB: elif surface.type == cte.INTERIOR_SLAB:
self._interior_slabs.append(surface) self._interior_slabs.append(surface)
else: else:
logging.error(f'Building {self.name} [alias {self.alias}] has an unexpected surface type {surface.type}.\n') logging.error(f'Building {self.name} [{self.aliases}] has an unexpected surface type {surface.type}.\n')
@property @property
def shell(self) -> Polyhedron: def shell(self) -> Polyhedron:
@ -470,19 +473,38 @@ class Building(CityObject):
return False return False
@property @property
def alias(self): def aliases(self):
""" """
Get the alias name for the building Get the alias name for the building
:return: str :return: str
""" """
return self._alias return self._aliases
@alias.setter def add_alias(self, value):
def alias(self, value):
""" """
Set the alias name for the building Add a new alias for the building
""" """
self._alias = value if self._aliases is None:
self._aliases = [value]
else:
self._aliases.append(value)
if self.city is not None:
self.city.add_building_alias(self, value)
@property
def city(self) -> City:
"""
Get the city containing the building
:return: City
"""
return self._city
@city.setter
def city(self, value):
"""
Set the city containing the building
"""
self._city = value
@property @property
def usages_percentage(self): def usages_percentage(self):

View File

@ -61,6 +61,7 @@ class City:
self._lca_materials = None self._lca_materials = None
self._level_of_detail = LevelOfDetail() self._level_of_detail = LevelOfDetail()
self._city_objects_dictionary = {} self._city_objects_dictionary = {}
self._city_objects_alias_dictionary = {}
self._energy_systems_connection_table = None self._energy_systems_connection_table = None
self._generic_energy_systems = None self._generic_energy_systems = None
@ -189,6 +190,24 @@ class City:
return self.buildings[self._city_objects_dictionary[name]] return self.buildings[self._city_objects_dictionary[name]]
return None return None
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]
def add_city_object(self, new_city_object): def add_city_object(self, new_city_object):
""" """
Add a CityObject to the city Add a CityObject to the city
@ -198,8 +217,15 @@ class City:
if new_city_object.type == 'building': if new_city_object.type == 'building':
if self._buildings is None: if self._buildings is None:
self._buildings = [] self._buildings = []
new_city_object._alias_dictionary = self._city_objects_alias_dictionary
self._buildings.append(new_city_object) self._buildings.append(new_city_object)
self._city_objects_dictionary[new_city_object.name] = len(self._buildings) - 1 self._city_objects_dictionary[new_city_object.name] = len(self._buildings) - 1
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]
elif new_city_object.type == 'energy_system': elif new_city_object.type == 'energy_system':
if self._energy_systems is None: if self._energy_systems is None:
self._energy_systems = [] self._energy_systems = []
@ -222,8 +248,14 @@ class City:
self._buildings.remove(city_object) self._buildings.remove(city_object)
# regenerate hash map # regenerate hash map
self._city_objects_dictionary.clear() self._city_objects_dictionary.clear()
self._city_objects_alias_dictionary.clear()
for i, city_object in enumerate(self._buildings): for i, city_object in enumerate(self._buildings):
self._city_objects_dictionary[city_object.name] = i self._city_objects_dictionary[city_object.name] = i
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]
@property @property
def srs_name(self) -> Union[None, str]: def srs_name(self) -> Union[None, str]:

View File

@ -29,7 +29,7 @@ class Geojson:
def __init__(self, def __init__(self,
path, path,
name_field=None, aliases_field=None,
extrusion_height_field=None, extrusion_height_field=None,
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
@ -42,7 +42,7 @@ class Geojson:
self._max_y = cte.MIN_FLOAT self._max_y = cte.MIN_FLOAT
self._max_z = 0 self._max_z = 0
self._city = None self._city = None
self._name_field = name_field self._aliases_field = aliases_field
self._extrusion_height_field = extrusion_height_field self._extrusion_height_field = extrusion_height_field
self._year_of_construction_field = year_of_construction_field self._year_of_construction_field = year_of_construction_field
self._function_field = function_field self._function_field = function_field
@ -128,17 +128,18 @@ class Geojson:
function = self._function_to_hub[function] function = self._function_to_hub[function]
geometry = feature['geometry'] geometry = feature['geometry']
building_name = '' building_name = ''
building_alias = building_name building_aliases = []
if 'id' in feature: if 'id' in feature:
building_name = feature['id'] building_name = feature['id']
building_alias = building_name if self._aliases_field is not None:
if self._name_field is not None:
building_alias = feature['properties'][self._name_field] for alias_field in self._aliases_field:
building_aliases.append(feature['properties'][alias_field])
if str(geometry['type']).lower() == 'polygon': if str(geometry['type']).lower() == 'polygon':
buildings.append(self._parse_polygon(geometry['coordinates'], buildings.append(self._parse_polygon(geometry['coordinates'],
building_name, building_name,
building_alias, building_aliases,
function, function,
year_of_construction, year_of_construction,
extrusion_height)) extrusion_height))
@ -146,7 +147,7 @@ class Geojson:
elif str(geometry['type']).lower() == 'multipolygon': elif str(geometry['type']).lower() == 'multipolygon':
buildings.append(self._parse_multi_polygon(geometry['coordinates'], buildings.append(self._parse_multi_polygon(geometry['coordinates'],
building_name, building_name,
building_alias, building_aliases,
function, function,
year_of_construction, year_of_construction,
extrusion_height)) extrusion_height))
@ -174,7 +175,7 @@ class Geojson:
transformed_coordinates = f'{transformed_coordinates} {transformed[self._X]} {transformed[self._Y]} 0.0' transformed_coordinates = f'{transformed_coordinates} {transformed[self._X]} {transformed[self._Y]} 0.0'
return transformed_coordinates.lstrip(' ') return transformed_coordinates.lstrip(' ')
def _parse_polygon(self, coordinates, building_name, building_alias, function, year_of_construction, extrusion_height): def _parse_polygon(self, coordinates, building_name, building_aliases, function, year_of_construction, extrusion_height):
surfaces = [] surfaces = []
for polygon_coordinates in coordinates: for polygon_coordinates in coordinates:
points = igh.points_from_string( points = igh.points_from_string(
@ -210,7 +211,8 @@ class Geojson:
if len(surfaces) > 1: if len(surfaces) > 1:
raise ValueError('too many surfaces!!!!') raise ValueError('too many surfaces!!!!')
building = Building(f'{building_name}', surfaces, year_of_construction, function) building = Building(f'{building_name}', surfaces, year_of_construction, function)
building.alias = building_alias for alias in building_aliases:
building.add_alias(alias)
if extrusion_height == 0: if extrusion_height == 0:
return building return building
else: else:
@ -244,11 +246,12 @@ class Geojson:
wall = Surface(polygon, polygon) wall = Surface(polygon, polygon)
surfaces.append(wall) surfaces.append(wall)
building = Building(f'{building_name}', surfaces, year_of_construction, function) building = Building(f'{building_name}', surfaces, year_of_construction, function)
building.alias = building_alias for alias in building_aliases:
building.add_alias(alias)
building.volume = volume building.volume = volume
return building return building
def _parse_multi_polygon(self, polygons_coordinates, building_name, building_alias, function, year_of_construction, extrusion_height): def _parse_multi_polygon(self, polygons_coordinates, building_name, building_aliases, function, year_of_construction, extrusion_height):
surfaces = [] surfaces = []
for coordinates in polygons_coordinates: for coordinates in polygons_coordinates:
for polygon_coordinates in coordinates: for polygon_coordinates in coordinates:
@ -282,7 +285,8 @@ class Geojson:
polygon.area = igh.ground_area(coordinates) polygon.area = igh.ground_area(coordinates)
surfaces[-1] = Surface(polygon, polygon) surfaces[-1] = Surface(polygon, polygon)
building = Building(f'{building_name}', surfaces, year_of_construction, function) building = Building(f'{building_name}', surfaces, year_of_construction, function)
building.alias = building_alias for alias in building_aliases:
building.add_alias(alias)
if extrusion_height == 0: if extrusion_height == 0:
return building return building
else: else:
@ -316,6 +320,7 @@ class Geojson:
wall = Surface(polygon, polygon) wall = Surface(polygon, polygon)
surfaces.append(wall) surfaces.append(wall)
building = Building(f'{building_name}', surfaces, year_of_construction, function) building = Building(f'{building_name}', surfaces, year_of_construction, function)
building.alias = building_alias for alias in building_aliases:
building.add_alias(alias)
building.volume = volume building.volume = volume
return building return building

View File

@ -22,7 +22,7 @@ class GeometryFactory:
def __init__(self, file_type, def __init__(self, file_type,
path=None, path=None,
data_frame=None, data_frame=None,
name_field=None, aliases_field=None,
height_field=None, height_field=None,
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
@ -31,7 +31,7 @@ class GeometryFactory:
validate_import_export_type(GeometryFactory, file_type) validate_import_export_type(GeometryFactory, file_type)
self._path = path self._path = path
self._data_frame = data_frame self._data_frame = data_frame
self._name_field = name_field self._aliases_field = aliases_field
self._height_field = height_field self._height_field = height_field
self._year_of_construction_field = year_of_construction_field self._year_of_construction_field = year_of_construction_field
self._function_field = function_field self._function_field = function_field
@ -74,7 +74,7 @@ class GeometryFactory:
:return: City :return: City
""" """
return Geojson(self._path, return Geojson(self._path,
self._name_field, self._aliases_field,
self._height_field, self._height_field,
self._year_of_construction_field, self._year_of_construction_field,
self._function_field, self._function_field,
@ -95,16 +95,3 @@ class GeometryFactory:
:return: City :return: City
""" """
return getattr(self, self._file_type, lambda: None) return getattr(self, self._file_type, lambda: None)
@property
def city_debug(self) -> City:
"""
Enrich the city given to the class using the class given handler
:return: City
"""
return Geojson(self._path,
self._name_field,
self._height_field,
self._year_of_construction_field,
self._function_field,
self._function_to_hub).city

View File

@ -22,7 +22,7 @@ class CityObject(Models):
id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True) id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False) city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
name = Column(String, nullable=False) name = Column(String, nullable=False)
alias = Column(String, nullable=True) aliases = Column(String, nullable=True)
type = Column(String, nullable=False) type = Column(String, nullable=False)
year_of_construction = Column(Integer, nullable=True) year_of_construction = Column(Integer, nullable=True)
function = Column(String, nullable=True) function = Column(String, nullable=True)
@ -39,7 +39,7 @@ class CityObject(Models):
def __init__(self, city_id, building: Building): def __init__(self, city_id, building: Building):
self.city_id = city_id self.city_id = city_id
self.name = building.name self.name = building.name
self.alias = building.alias self.aliases = building.aliases
self.type = building.type self.type = building.type
self.year_of_construction = building.year_of_construction self.year_of_construction = building.year_of_construction
self.function = building.function self.function = building.function

View File

@ -104,7 +104,7 @@ class TestConstructionFactory(TestCase):
self.assertIsNone(building.households, 'building households is not none') self.assertIsNone(building.households, 'building households is not none')
self.assertFalse(building.is_conditioned, 'building is conditioned') self.assertFalse(building.is_conditioned, 'building is conditioned')
self.assertIsNotNone(building.shell, 'building shell is none') self.assertIsNotNone(building.shell, 'building shell is none')
self.assertIsNone(building.alias, 'building alias is not none') self.assertIsNone(building.aliases, 'building alias is not none')
def _check_thermal_zones(self, internal_zone): def _check_thermal_zones(self, internal_zone):
for thermal_zone in internal_zone.thermal_zones: for thermal_zone in internal_zone.thermal_zones:

View File

@ -193,7 +193,6 @@ TestDBFactory
control.user_id) control.user_id)
city_objects_id = [] city_objects_id = []
for building in control.city.buildings: for building in control.city.buildings:
_building = control.database.building_info(building.name, city_id) _building = control.database.building_info(building.name, city_id)
if cte.MONTH not in building.cooling: if cte.MONTH not in building.cooling:
print(f'building {building.name} not calculated') print(f'building {building.name} not calculated')
@ -213,12 +212,12 @@ TestDBFactory
yearly_appliances_electrical_demand = building.appliances_electrical_demand[cte.YEAR][cte.INSEL_MEB] yearly_appliances_electrical_demand = building.appliances_electrical_demand[cte.YEAR][cte.INSEL_MEB]
monthly_domestic_hot_water_heat_demand = building.domestic_hot_water_heat_demand[cte.MONTH][cte.INSEL_MEB] monthly_domestic_hot_water_heat_demand = building.domestic_hot_water_heat_demand[cte.MONTH][cte.INSEL_MEB]
yearly_domestic_hot_water_heat_demand = building.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB] yearly_domestic_hot_water_heat_demand = building.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB]
monthly_heating_consumption = building.heating_consumption[cte.MONTH][cte.INSEL_MEB] monthly_heating_consumption = building.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB] # building.heating_consumption[cte.MONTH][cte.INSEL_MEB]
yearly_heating_consumption = building.heating_consumption[cte.YEAR][cte.INSEL_MEB] yearly_heating_consumption = building.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB] #building.heating_consumption[cte.YEAR][cte.INSEL_MEB]
monthly_cooling_consumption = building.cooling_consumption[cte.MONTH][cte.INSEL_MEB] monthly_cooling_consumption = building.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB] #building.cooling_consumption[cte.MONTH][cte.INSEL_MEB]
yearly_cooling_consumption = building.cooling_consumption[cte.YEAR][cte.INSEL_MEB] yearly_cooling_consumption = building.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB] #building.cooling_consumption[cte.YEAR][cte.INSEL_MEB]
monthly_domestic_hot_water_consumption = building.domestic_hot_water_consumption[cte.MONTH][cte.INSEL_MEB] monthly_domestic_hot_water_consumption = building.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB] #building.domestic_hot_water_consumption[cte.MONTH][cte.INSEL_MEB]
yearly_domestic_hot_water_consumption = building._domestic_hot_water_consumption[cte.YEAR][cte.INSEL_MEB] yearly_domestic_hot_water_consumption = building.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB] #building._domestic_hot_water_consumption[cte.YEAR][cte.INSEL_MEB]
db_building_id = _building.id db_building_id = _building.id
city_objects_id.append(db_building_id) city_objects_id.append(db_building_id)
control.database.add_simulation_results( control.database.add_simulation_results(
@ -245,7 +244,6 @@ TestDBFactory
{"monthly_domestic_hot_water_consumption": monthly_domestic_hot_water_consumption.to_json()}, {"monthly_domestic_hot_water_consumption": monthly_domestic_hot_water_consumption.to_json()},
{"yearly_domestic_hot_water_consumption": yearly_domestic_hot_water_consumption.to_json()} {"yearly_domestic_hot_water_consumption": yearly_domestic_hot_water_consumption.to_json()}
]}), city_object_id=db_building_id) ]}), city_object_id=db_building_id)
self.assertEqual(1, len(city_objects_id), 'wrong number of results') self.assertEqual(1, len(city_objects_id), 'wrong number of results')
self.assertIsNotNone(city_objects_id[0], 'city_object_id is None') self.assertIsNotNone(city_objects_id[0], 'city_object_id is None')
for _id in city_objects_id: for _id in city_objects_id:

View File

@ -139,10 +139,13 @@ class TestGeometryFactory(TestCase):
path=file, path=file,
height_field='building_height', height_field='building_height',
year_of_construction_field='ANNEE_CONS', year_of_construction_field='ANNEE_CONS',
name_field='ID_UEV', aliases_field=['ID_UEV', 'CIVIQUE_DE', 'NOM_RUE'],
function_field='CODE_UTILI', function_field='CODE_UTILI',
function_to_hub=MontrealFunctionToHubFunction().dictionary).city function_to_hub=MontrealFunctionToHubFunction().dictionary).city
hub.exports.exports_factory.ExportsFactory('obj', city, self._output_path).export() hub.exports.exports_factory.ExportsFactory('obj', city, self._output_path).export()
for building in city.building_alias('01040584'):
self.assertEqual('0', building.name, 'Wrong building name when looking for alias')
self.assertEqual(14, len(city.building_alias('rue Sherbrooke Ouest (MTL+MTO+WMT)')))
self.assertEqual(1964, len(city.buildings), 'wrong number of buildings') self.assertEqual(1964, len(city.buildings), 'wrong number of buildings')
def test_map_neighbours(self): def test_map_neighbours(self):