Merge branch 'retrofit_project' into systems_catalog

This commit is contained in:
Pilar Monsalvete 2023-05-29 12:18:09 -04:00
commit a431ee9d1e
12 changed files with 125 additions and 66 deletions

View File

@ -7,7 +7,7 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
"""
import logging
from typing import List, Union
from typing import List, Union, TypeVar
import numpy as np
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.helpers.peak_loads import PeakLoads
City = TypeVar('City')
class Building(CityObject):
"""
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)
self._city = city
self._households = None
self._basement_heated = None
self._attic_heated = None
@ -40,7 +43,7 @@ class Building(CityObject):
self._roof_type = None
self._internal_zones = None
self._shell = None
self._alias = None
self._aliases = None
self._type = 'building'
self._cold_water_temperature = dict()
self._heating = dict()
@ -83,7 +86,7 @@ class Building(CityObject):
elif surface.type == cte.INTERIOR_SLAB:
self._interior_slabs.append(surface)
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
def shell(self) -> Polyhedron:
@ -471,19 +474,38 @@ class Building(CityObject):
return False
@property
def alias(self):
def aliases(self):
"""
Get the alias name for the building
:return: str
"""
return self._alias
return self._aliases
@alias.setter
def alias(self, value):
def add_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
def usages_percentage(self):

View File

@ -8,6 +8,7 @@ Code contributors: Peter Yefi peteryefi@gmail.com
from __future__ import annotations
import bz2
import logging
import sys
import pickle
import math
@ -60,6 +61,7 @@ class City:
self._lca_materials = None
self._level_of_detail = LevelOfDetail()
self._city_objects_dictionary = {}
self._city_objects_alias_dictionary = {}
self._energy_systems_connection_table = None
self._generic_energy_systems = None
@ -70,10 +72,9 @@ class City:
if self._srs_name in GeometryHelper.srs_transformations.keys():
self._srs_name = GeometryHelper.srs_transformations[self._srs_name]
input_reference = pyproj.CRS(self.srs_name) # Projected coordinate system from input data
except pyproj.exceptions.CRSError:
sys.stderr.write('Invalid projection reference system, please check the input data. '
'(e.g. in CityGML files: srs_name)\n')
sys.exit()
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
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])
@ -189,6 +190,24 @@ class City:
return self.buildings[self._city_objects_dictionary[name]]
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):
"""
Add a CityObject to the city
@ -198,8 +217,15 @@ class City:
if new_city_object.type == 'building':
if self._buildings is None:
self._buildings = []
new_city_object._alias_dictionary = self._city_objects_alias_dictionary
self._buildings.append(new_city_object)
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':
if self._energy_systems is None:
self._energy_systems = []
@ -222,8 +248,14 @@ class City:
self._buildings.remove(city_object)
# regenerate hash map
self._city_objects_dictionary.clear()
self._city_objects_alias_dictionary.clear()
for i, city_object in enumerate(self._buildings):
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
def srs_name(self) -> Union[None, str]:

View File

@ -24,12 +24,12 @@ class Geojson:
"""
Geojson class
"""
X = 0
Y = 1
_X = 0
_Y = 1
def __init__(self,
path,
name_field=None,
aliases_field=None,
extrusion_height_field=None,
year_of_construction_field=None,
function_field=None,
@ -42,7 +42,7 @@ class Geojson:
self._max_y = cte.MIN_FLOAT
self._max_z = 0
self._city = None
self._name_field = name_field
self._aliases_field = aliases_field
self._extrusion_height_field = extrusion_height_field
self._year_of_construction_field = year_of_construction_field
self._function_field = function_field
@ -128,14 +128,18 @@ class Geojson:
function = self._function_to_hub[function]
geometry = feature['geometry']
building_name = ''
building_aliases = []
if 'id' in feature:
building_name = feature['id']
if self._name_field is not None:
building_name = feature['properties'][self._name_field]
if self._aliases_field is not None:
for alias_field in self._aliases_field:
building_aliases.append(feature['properties'][alias_field])
if str(geometry['type']).lower() == 'polygon':
buildings.append(self._parse_polygon(geometry['coordinates'],
building_name,
building_aliases,
function,
year_of_construction,
extrusion_height))
@ -143,6 +147,7 @@ class Geojson:
elif str(geometry['type']).lower() == 'multipolygon':
buildings.append(self._parse_multi_polygon(geometry['coordinates'],
building_name,
building_aliases,
function,
year_of_construction,
extrusion_height))
@ -165,12 +170,12 @@ class Geojson:
def _polygon_coordinates_to_3d(self, polygon_coordinates):
transformed_coordinates = ''
for coordinate in polygon_coordinates:
transformed = self._transformer.transform(coordinate[self.Y], coordinate[self.X])
self._save_bounds(transformed[self.X], transformed[self.Y])
transformed_coordinates = f'{transformed_coordinates} {transformed[self.X]} {transformed[self.Y]} 0.0'
transformed = self._transformer.transform(coordinate[self._Y], coordinate[self._X])
self._save_bounds(transformed[self._X], transformed[self._Y])
transformed_coordinates = f'{transformed_coordinates} {transformed[self._X]} {transformed[self._Y]} 0.0'
return transformed_coordinates.lstrip(' ')
def _parse_polygon(self, coordinates, building_name, function, year_of_construction, extrusion_height):
def _parse_polygon(self, coordinates, building_name, building_aliases, function, year_of_construction, extrusion_height):
surfaces = []
for polygon_coordinates in coordinates:
points = igh.points_from_string(
@ -206,6 +211,8 @@ class Geojson:
if len(surfaces) > 1:
raise ValueError('too many surfaces!!!!')
building = Building(f'{building_name}', surfaces, year_of_construction, function)
for alias in building_aliases:
building.add_alias(alias)
if extrusion_height == 0:
return building
else:
@ -239,10 +246,12 @@ class Geojson:
wall = Surface(polygon, polygon)
surfaces.append(wall)
building = Building(f'{building_name}', surfaces, year_of_construction, function)
for alias in building_aliases:
building.add_alias(alias)
building.volume = volume
return building
def _parse_multi_polygon(self, polygons_coordinates, building_name, function, year_of_construction, extrusion_height):
def _parse_multi_polygon(self, polygons_coordinates, building_name, building_aliases, function, year_of_construction, extrusion_height):
surfaces = []
for coordinates in polygons_coordinates:
for polygon_coordinates in coordinates:
@ -276,6 +285,8 @@ class Geojson:
polygon.area = igh.ground_area(coordinates)
surfaces[-1] = Surface(polygon, polygon)
building = Building(f'{building_name}', surfaces, year_of_construction, function)
for alias in building_aliases:
building.add_alias(alias)
if extrusion_height == 0:
return building
else:
@ -309,5 +320,7 @@ class Geojson:
wall = Surface(polygon, polygon)
surfaces.append(wall)
building = Building(f'{building_name}', surfaces, year_of_construction, function)
for alias in building_aliases:
building.add_alias(alias)
building.volume = volume
return building

View File

@ -22,7 +22,7 @@ class GeometryFactory:
def __init__(self, file_type,
path=None,
data_frame=None,
name_field=None,
aliases_field=None,
height_field=None,
year_of_construction_field=None,
function_field=None,
@ -31,7 +31,7 @@ class GeometryFactory:
validate_import_export_type(GeometryFactory, file_type)
self._path = path
self._data_frame = data_frame
self._name_field = name_field
self._aliases_field = aliases_field
self._height_field = height_field
self._year_of_construction_field = year_of_construction_field
self._function_field = function_field
@ -74,7 +74,7 @@ class GeometryFactory:
:return: City
"""
return Geojson(self._path,
self._name_field,
self._aliases_field,
self._height_field,
self._year_of_construction_field,
self._function_field,
@ -95,16 +95,3 @@ class GeometryFactory:
:return: City
"""
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

@ -6,7 +6,7 @@ Project Coder Peter Yefi peteryefi@gmail.com
"""
import logging
from hub.persistence import Repository
from hub.persistence.repository import Repository
from hub.persistence.models import Application
from hub.persistence.models import City
from hub.persistence.models import CityObject

View File

@ -22,7 +22,7 @@ class CityObject(Models):
id = Column(Integer, Sequence('city_object_id_seq'), primary_key=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
name = Column(String, nullable=False)
alias = Column(String, nullable=True)
aliases = Column(String, nullable=True)
type = Column(String, nullable=False)
year_of_construction = Column(Integer, nullable=True)
function = Column(String, nullable=True)
@ -32,6 +32,7 @@ class CityObject(Models):
total_heating_area = Column(Float, nullable=False)
wall_area = Column(Float, nullable=False)
windows_area = Column(Float, nullable=False)
roof_area = Column(Float, nullable=False)
system_name = Column(String, nullable=False)
created = Column(DateTime, default=datetime.datetime.utcnow)
updated = Column(DateTime, default=datetime.datetime.utcnow)
@ -39,13 +40,14 @@ class CityObject(Models):
def __init__(self, city_id, building: Building):
self.city_id = city_id
self.name = building.name
self.alias = building.alias
self.aliases = building.aliases
self.type = building.type
self.year_of_construction = building.year_of_construction
self.function = building.function
self.usage = building.usages_percentage
self.volume = building.volume
self.area = building.floor_area
self.roof_area = sum(roof.solid_polygon.area for roof in building.roofs)
storeys = building.storeys_above_ground
if storeys is None:
storeys = building.max_height / building.average_storey_height

View File

@ -104,7 +104,7 @@ class TestConstructionFactory(TestCase):
self.assertIsNone(building.households, 'building households is not none')
self.assertFalse(building.is_conditioned, 'building is conditioned')
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):
for thermal_zone in internal_zone.thermal_zones:

View File

@ -193,7 +193,6 @@ TestDBFactory
control.user_id)
city_objects_id = []
for building in control.city.buildings:
_building = control.database.building_info(building.name, city_id)
if cte.MONTH not in building.cooling:
print(f'building {building.name} not calculated')
@ -213,25 +212,27 @@ TestDBFactory
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]
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]
yearly_heating_consumption = building.heating_consumption[cte.YEAR][cte.INSEL_MEB]
monthly_cooling_consumption = building.cooling_consumption[cte.MONTH][cte.INSEL_MEB]
yearly_cooling_consumption = building.cooling_consumption[cte.YEAR][cte.INSEL_MEB]
monthly_domestic_hot_water_consumption = 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]
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.domestic_hot_water_heat_demand[cte.YEAR][cte.INSEL_MEB] #building.heating_consumption[cte.YEAR][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.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_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_heat_demand[cte.YEAR][cte.INSEL_MEB] #building._domestic_hot_water_consumption[cte.YEAR][cte.INSEL_MEB]
db_building_id = _building.id
city_objects_id.append(db_building_id)
control.database.add_simulation_results(
cte.INSEL_MEB,
json.dumps({cte.INSEL_MEB: [
{"monthly_cooling": monthly_cooling.to_json()},
{"yearly_cooling": yearly_cooling.to_json()},
{"monthly_heating": monthly_heating.to_json()},
{"yearly_heating": yearly_heating.to_json()},
{"monthly_cooling_peak_load": monthly_cooling_peak_load.to_json()},
{"yearly_cooling_peak_load": yearly_cooling_peak_load.to_json()},
{"monthly_heating_peak_load": monthly_heating_peak_load.to_json()},
{"yearly_heating_peak_load": yearly_heating_peak_load.to_json()},
{"monthly_electrical_peak_load": monthly_electrical_peak_load.to_json()},
{"yearly_electrical_peak_load": yearly_electrical_peak_load.to_json()},
{"monthly_cooling_demand": monthly_cooling.to_json()},
{"yearly_cooling_demand": yearly_cooling.to_json()},
{"monthly_heating_demand": monthly_heating.to_json()},
{"yearly_heating_demand": yearly_heating.to_json()},
{"monthly_lighting_electrical_demand": monthly_lighting_electrical_demand.to_json()},
{"yearly_lighting_electrical_demand": yearly_lighting_electrical_demand.to_json()},
{"monthly_appliances_electrical_demand": monthly_appliances_electrical_demand.to_json()},
@ -245,7 +246,6 @@ TestDBFactory
{"monthly_domestic_hot_water_consumption": monthly_domestic_hot_water_consumption.to_json()},
{"yearly_domestic_hot_water_consumption": yearly_domestic_hot_water_consumption.to_json()}
]}), city_object_id=db_building_id)
self.assertEqual(1, len(city_objects_id), 'wrong number of results')
self.assertIsNotNone(city_objects_id[0], 'city_object_id is None')
for _id in city_objects_id:

View File

@ -139,10 +139,13 @@ class TestGeometryFactory(TestCase):
path=file,
height_field='building_height',
year_of_construction_field='ANNEE_CONS',
name_field='ID_UEV',
aliases_field=['ID_UEV', 'CIVIQUE_DE', 'NOM_RUE'],
function_field='CODE_UTILI',
function_to_hub=MontrealFunctionToHubFunction().dictionary).city
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')
def test_map_neighbours(self):

View File

@ -90,22 +90,22 @@ class TestResultsImport(TestCase):
self.assertIsNotNone(building.heating_peak_load)
self.assertIsNotNone(building.cooling_peak_load)
pd.testing.assert_series_equal(
building.heating_peak_load[cte.YEAR]['heating peak loads'],
building.heating_peak_load[cte.YEAR][cte.HEATING_PEAK_LOAD],
expected_yearly['expected'],
check_names=False
)
pd.testing.assert_series_equal(
building.cooling_peak_load[cte.YEAR]['cooling peak loads'],
building.cooling_peak_load[cte.YEAR][cte.COOLING_PEAK_LOAD],
expected_yearly['expected'],
check_names=False
)
pd.testing.assert_series_equal(
building.heating_peak_load[cte.MONTH]['heating peak loads'],
building.heating_peak_load[cte.MONTH][cte.HEATING_PEAK_LOAD],
expected_monthly['expected'],
check_names=False
)
pd.testing.assert_series_equal(
building.cooling_peak_load[cte.MONTH]['cooling peak loads'],
building.cooling_peak_load[cte.MONTH][cte.COOLING_PEAK_LOAD],
expected_monthly['expected'],
check_names=False
)

View File

@ -97,9 +97,9 @@ class TestSystemsFactory(TestCase):
copy.deepcopy(_generic_building_energy_system.generation_system)
)
if cte.HEATING in _building_energy_equipment.demand_types:
_building_generation_system.heat_power = building.heating_peak_load[cte.YEAR]['heating peak loads'][0]
_building_generation_system.heat_power = building.heating_peak_load[cte.YEAR][cte.HEATING_PEAK_LOAD][0]
if cte.COOLING in _building_energy_equipment.demand_types:
_building_generation_system.cooling_power = building.cooling_peak_load[cte.YEAR]['cooling peak loads'][0]
_building_generation_system.cooling_power = building.cooling_peak_load[cte.YEAR][cte.COOLING_PEAK_LOAD][0]
_building_energy_equipment.generation_system = _building_generation_system
_building_energy_equipment.distribution_system = _building_distribution_system
_building_energy_equipment.emission_system = _building_emission_system

View File

@ -61,8 +61,8 @@ setup(
'hub.exports.energy_systems',
'hub.exports.formats',
'hub.helpers',
'hub.helpers.peak_calculation',
'hub.helpers.data',
'hub.hub_logger',
'hub.imports',
'hub.imports.construction',
'hub.imports.construction.helpers',