Update multi-usage methods to work with usage geojson input and parsers

This commit is contained in:
Vagrant 2024-11-24 06:48:35 +00:00
parent 3db3acd3c6
commit 1449298a25
11 changed files with 153 additions and 100 deletions

View File

@ -36,6 +36,7 @@ class Building(CityObject):
self._terrains = terrains self._terrains = terrains
self._year_of_construction = year_of_construction self._year_of_construction = year_of_construction
self._function = function self._function = function
self._usages = None
self._average_storey_height = None self._average_storey_height = None
self._storeys_above_ground = None self._storeys_above_ground = None
self._floor_area = None self._floor_area = None
@ -256,7 +257,17 @@ class Building(CityObject):
:param value: str :param value: str
""" """
if value is not None: if value is not None:
self._function = str(value) self._function = value
@property
def usages(self) -> Union[None, list]:
"""
Get building function
:return: None or str
"""
if self._usages is None and self._function is not None:
self._usages = [{'usage': self._function, 'ratio': 1 }]
return self._usages
@property @property
def average_storey_height(self) -> Union[None, float]: def average_storey_height(self) -> Union[None, float]:

View File

View File

@ -0,0 +1,31 @@
class ListUsageToHub:
"""
Eilat function to hub function class
"""
def __init__(self, function_dictionary=None):
self._function_dictionary = function_dictionary
def _apply_function_dictionary(self, usages):
function_dictionary = self._function_dictionary
if function_dictionary is not None:
for usage in usages:
if usage['usage'] in function_dictionary:
usage['usage'] = function_dictionary[usage['usage']]
return usages
def parse(self, usages) -> list[dict]:
"""
Get the dictionary
:return: {}
"""
usages = [{"usage": str(i["usage"]), "ratio": float(i["ratio"])} for i in usages]
usages = self._apply_function_dictionary(usages)
return usages

View File

@ -0,0 +1,18 @@
class StringUsageToHub:
"""
Eilat function to hub function class
"""
def parse(self, usages) -> list[dict]:
"""
Get the dictionary
:return: {}
"""
parsed_usages = []
for usage in usages.split('_'):
usage_dict = {"usage": str(usage.split('-')[0]), "ratio": float(usage.split('-')[1])}
parsed_usages.append(usage_dict)
return usages

View File

@ -0,0 +1,31 @@
"""
Dictionaries module saves all transformations of functions and usages to access the catalogs
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2023 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
from hub.helpers.parsers.list_usage_to_hub import ListUsageToHub
from hub.helpers.parsers.string_usage_to_hub import StringUsageToHub
class UseageParsers:
"""
Dictionaries class
"""
@staticmethod
def string_usage_to_hub() -> object:
"""
Hub usage to HfT usage, transformation dictionary
:return: dict
"""
return StringUsageToHub().parse
@staticmethod
def list_usage_to_hub(function_dictionary=None) -> object:
"""
Hub usage to HfT usage, transformation dictionary
:return: dict
"""
return ListUsageToHub(function_dictionary).parse

View File

@ -33,21 +33,10 @@ class NrcanPhysicsParameters:
city = self._city city = self._city
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
for building in city.buildings: for building in city.buildings:
main_function = None if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
functions = building.function.split('_') logging.error('Building %s has an unknown building function %s', building.name, building.function)
if len(functions) > 1:
maximum_percentage = 0
for function in functions:
percentage_and_function = function.split('-')
if float(percentage_and_function[0]) > maximum_percentage:
maximum_percentage = float(percentage_and_function[0])
main_function = percentage_and_function[-1]
else:
main_function = functions[-1]
if main_function not in Dictionaries().hub_function_to_nrcan_construction_function:
logging.error('Building %s has an unknown building function %s', building.name, main_function)
continue continue
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function] function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
try: try:
archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone) archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone)

View File

@ -35,6 +35,8 @@ class Geojson:
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
function_to_hub=None, function_to_hub=None,
usages_field=None,
usages_to_hub=None,
hub_crs=None hub_crs=None
): ):
self._hub_crs = hub_crs self._hub_crs = hub_crs
@ -52,6 +54,8 @@ class Geojson:
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
self._function_to_hub = function_to_hub self._function_to_hub = function_to_hub
self._usages_field = usages_field
self._usages_to_hub = usages_to_hub
with open(path, 'r', encoding='utf8') as json_file: with open(path, 'r', encoding='utf8') as json_file:
self._geojson = json.loads(json_file.read()) self._geojson = json.loads(json_file.read())
@ -117,41 +121,30 @@ class Geojson:
lod = 0 lod = 0
for feature in self._geojson['features']: for feature in self._geojson['features']:
extrusion_height = 0 extrusion_height = 0
if self._extrusion_height_field is not None: if self._extrusion_height_field is not None:
extrusion_height = float(feature['properties'][self._extrusion_height_field]) extrusion_height = float(feature['properties'][self._extrusion_height_field])
lod = 1 lod = 1
self._max_z = max(self._max_z, extrusion_height) self._max_z = max(self._max_z, extrusion_height)
year_of_construction = None year_of_construction = None
if self._year_of_construction_field is not None: if self._year_of_construction_field is not None:
year_of_construction = int(feature['properties'][self._year_of_construction_field]) year_of_construction = int(feature['properties'][self._year_of_construction_field])
function = None function = None
if self._function_field is not None: if self._function_field is not None:
function = str(feature['properties'][self._function_field]) function = str(feature['properties'][self._function_field])
if function == 'Mixed use' or function == 'mixed use':
function_parts = []
if 'usages' in feature['properties']:
usages = feature['properties']['usages']
for usage in usages:
if self._function_to_hub is not None and usage['usage'] in self._function_to_hub:
function_parts.append(f"{usage['percentage']}-{self._function_to_hub[usage['usage']]}")
else:
function_parts.append(f"{usage['percentage']}-{usage['usage']}")
else:
for key, value in feature['properties'].items():
if key.startswith("mixed_type_") and not key.endswith("_percentage"):
type_key = key
percentage_key = f"{key}_percentage"
if percentage_key in feature['properties']:
if self._function_to_hub is not None and feature['properties'][type_key] in self._function_to_hub:
usage_function = self._function_to_hub[feature['properties'][type_key]]
function_parts.append(f"{feature['properties'][percentage_key]}-{usage_function}")
else:
function_parts.append(f"{feature['properties'][percentage_key]}-{feature['properties'][type_key]}")
function = "_".join(function_parts)
if self._function_to_hub is not None: if self._function_to_hub is not None:
# use the transformation dictionary to retrieve the proper function
if function in self._function_to_hub: if function in self._function_to_hub:
function = self._function_to_hub[function] function = self._function_to_hub[function]
usages = None
if self._usages_field is not None:
if self._usages_field in feature['properties']:
usages = feature['properties'][self._usages_field]
if self._usages_to_hub is not None:
usages = self._usages_to_hub(usages)
geometry = feature['geometry'] geometry = feature['geometry']
building_aliases = [] building_aliases = []
if 'id' in feature: if 'id' in feature:

View File

@ -23,6 +23,8 @@ class GeometryFactory:
year_of_construction_field=None, year_of_construction_field=None,
function_field=None, function_field=None,
function_to_hub=None, function_to_hub=None,
usages_field=None,
usages_to_hub=None,
hub_crs=None): hub_crs=None):
self._file_type = '_' + file_type.lower() self._file_type = '_' + file_type.lower()
validate_import_export_type(GeometryFactory, file_type) validate_import_export_type(GeometryFactory, file_type)
@ -32,6 +34,8 @@ class GeometryFactory:
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
self._function_to_hub = function_to_hub self._function_to_hub = function_to_hub
self._usages_field = usages_field
self._usages_to_hub = usages_to_hub
self._hub_crs = hub_crs self._hub_crs = hub_crs
@property @property
@ -66,6 +70,8 @@ class GeometryFactory:
self._year_of_construction_field, self._year_of_construction_field,
self._function_field, self._function_field,
self._function_to_hub, self._function_to_hub,
self._usages_field,
self._usages_to_hub,
self._hub_crs).city self._hub_crs).city
@property @property

View File

@ -38,38 +38,36 @@ class ComnetUsageParameters:
city = self._city city = self._city
comnet_catalog = UsageCatalogFactory('comnet').catalog comnet_catalog = UsageCatalogFactory('comnet').catalog
for building in city.buildings: for building in city.buildings:
usages = []
comnet_archetype_usages = [] comnet_archetype_usages = []
building_functions = building.function.split('_') usages = building.usages
for function in building_functions:
usages.append(function.split('-'))
for usage in usages: for usage in usages:
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]] comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage['usage']]
try: try:
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name) comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
comnet_archetype_usages.append(comnet_archetype_usage) comnet_archetype_usages.append(comnet_archetype_usage)
except KeyError: except KeyError:
logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name) logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name)
continue continue
for (i, internal_zone) in enumerate(building.internal_zones): for (i, internal_zone) in enumerate(building.internal_zones):
internal_zone_usages = [] internal_zone_usages = []
if len(building.internal_zones) > 1: if len(building.internal_zones) > 1:
volume_per_area = 0 volume_per_area = 0
if internal_zone.area is None: if internal_zone.area is None:
logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
if internal_zone.volume is None: if internal_zone.volume is None:
logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
if internal_zone.area <= 0: if internal_zone.area <= 0:
logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
volume_per_area += internal_zone.volume / internal_zone.area volume_per_area += internal_zone.volume / internal_zone.area
usage = Usage() usage = Usage()
usage.name = usages[i][-1] usage.name = usages[i]['usage']
self._assign_values(usage, comnet_archetype_usages[i], volume_per_area, building.cold_water_temperature) self._assign_values(usage, comnet_archetype_usages[i], volume_per_area, building.cold_water_temperature)
usage.percentage = 1 usage.percentage = 1
self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[i]) self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[i])
@ -82,13 +80,11 @@ class ComnetUsageParameters:
'ground', building.name, usages, building.year_of_construction) 'ground', building.name, usages, building.year_of_construction)
storeys_above_ground = self.average_storey_height_calculator(self._city, building) storeys_above_ground = self.average_storey_height_calculator(self._city, building)
volume_per_area = building.volume / building.floor_area / storeys_above_ground volume_per_area = building.volume / building.floor_area / storeys_above_ground
for (j, mixed_usage) in enumerate(usages): for j, usage_type in enumerate(usages):
usage = Usage() usage = Usage()
usage.name = mixed_usage[-1] usage.name = usage_type['usage']
if len(usages) > 1: usage.percentage = float(usage_type['ratio'])
usage.percentage = float(mixed_usage[0]) / 100
else:
usage.percentage = 1
self._assign_values(usage, comnet_archetype_usages[j], volume_per_area, building.cold_water_temperature) self._assign_values(usage, comnet_archetype_usages[j], volume_per_area, building.cold_water_temperature)
self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[j]) self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[j])
internal_zone_usages.append(usage) internal_zone_usages.append(usage)
@ -270,20 +266,11 @@ class ComnetUsageParameters:
def average_storey_height_calculator(city, building): def average_storey_height_calculator(city, building):
climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city) climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city)
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
main_function = None
functions = building.function.split('_') if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
if len(functions) > 1: logging.error('Building %s has an unknown building function %s', building.name, building.function)
maximum_percentage = 0
for function in functions: function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
percentage_and_function = function.split('-')
if float(percentage_and_function[0]) > maximum_percentage:
maximum_percentage = float(percentage_and_function[0])
main_function = percentage_and_function[-1]
else:
main_function = functions[-1]
if main_function not in Dictionaries().hub_function_to_nrcan_construction_function:
logging.error('Building %s has an unknown building function %s', building.name, main_function)
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function]
construction_archetype = None construction_archetype = None
average_storey_height = None average_storey_height = None
nrcan_archetypes = nrcan_catalog.entries('archetypes') nrcan_archetypes = nrcan_catalog.entries('archetypes')
@ -298,4 +285,4 @@ class ComnetUsageParameters:
'[%s], building year of construction: %s and climate zone %s', building.name, function, '[%s], building year of construction: %s and climate zone %s', building.name, function,
building.function, building.year_of_construction, climate_zone) building.function, building.year_of_construction, climate_zone)
return average_storey_height return average_storey_height

View File

@ -37,21 +37,18 @@ class NrcanUsageParameters:
nrcan_catalog = UsageCatalogFactory('nrcan').catalog nrcan_catalog = UsageCatalogFactory('nrcan').catalog
comnet_catalog = UsageCatalogFactory('comnet').catalog comnet_catalog = UsageCatalogFactory('comnet').catalog
for building in city.buildings: for building in city.buildings:
usages = []
nrcan_archetype_usages = [] nrcan_archetype_usages = []
comnet_archetype_usages = [] comnet_archetype_usages = []
building_functions = building.function.split('_') usages = building.usages
for function in building_functions:
usages.append(function.split('-'))
for usage in usages: for usage in usages:
usage_name = Dictionaries().hub_usage_to_nrcan_usage[usage[-1]] usage_name = Dictionaries().hub_usage_to_nrcan_usage[usage['usage']]
try: try:
archetype_usage = self._search_archetypes(nrcan_catalog, usage_name) archetype_usage = self._search_archetypes(nrcan_catalog, usage_name)
nrcan_archetype_usages.append(archetype_usage) nrcan_archetype_usages.append(archetype_usage)
except KeyError: except KeyError:
logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name) logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name)
continue continue
comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]] comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage['usage']]
try: try:
comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name) comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name)
comnet_archetype_usages.append(comnet_archetype_usage) comnet_archetype_usages.append(comnet_archetype_usage)
@ -65,19 +62,19 @@ class NrcanUsageParameters:
volume_per_area = 0 volume_per_area = 0
if internal_zone.area is None: if internal_zone.area is None:
logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
if internal_zone.volume is None: if internal_zone.volume is None:
logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
if internal_zone.area <= 0: if internal_zone.area <= 0:
logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s', logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s',
building.name, usages[i][-1]) building.name, usages[i]['usage'])
continue continue
volume_per_area += internal_zone.volume / internal_zone.area volume_per_area += internal_zone.volume / internal_zone.area
usage = Usage() usage = Usage()
usage.name = usages[i][-1] usage.name = usages[i]['usage']
self._assign_values(usage, nrcan_archetype_usages[i], volume_per_area, building.cold_water_temperature) self._assign_values(usage, nrcan_archetype_usages[i], volume_per_area, building.cold_water_temperature)
self._assign_comnet_extra_values(usage, comnet_archetype_usages[i], nrcan_archetype_usages[i].occupancy.occupancy_density) self._assign_comnet_extra_values(usage, comnet_archetype_usages[i], nrcan_archetype_usages[i].occupancy.occupancy_density)
usage.percentage = 1 usage.percentage = 1
@ -86,19 +83,17 @@ class NrcanUsageParameters:
else: else:
storeys_above_ground = building.storeys_above_ground storeys_above_ground = building.storeys_above_ground
if storeys_above_ground is None: if storeys_above_ground is None:
logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for usage %s. ' logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for function %s. '
'NRCAN construction data for the year %s is used to calculated number of storeys above ' 'NRCAN construction data for the year %s is used to calculated number of storeys above '
'ground', building.name, usages, building.year_of_construction) 'ground', building.name, building.function, building.year_of_construction)
storeys_above_ground = self.average_storey_height_calculator(self._city, building) storeys_above_ground = self.average_storey_height_calculator(self._city, building)
continue continue
volume_per_area = building.volume / building.floor_area / storeys_above_ground volume_per_area = building.volume / building.floor_area / storeys_above_ground
for (j, mixed_usage) in enumerate(usages): for j, usage_type in enumerate(usages):
usage = Usage() usage = Usage()
usage.name = mixed_usage[-1] usage.name = usage_type['usage']
if len(usages) > 1: usage.percentage = float(usage_type['ratio'])
usage.percentage = float(mixed_usage[0]) / 100
else:
usage.percentage = 1
self._assign_values(usage, nrcan_archetype_usages[j], volume_per_area, building.cold_water_temperature) self._assign_values(usage, nrcan_archetype_usages[j], volume_per_area, building.cold_water_temperature)
self._assign_comnet_extra_values(usage, comnet_archetype_usages[j], nrcan_archetype_usages[j].occupancy.occupancy_density) self._assign_comnet_extra_values(usage, comnet_archetype_usages[j], nrcan_archetype_usages[j].occupancy.occupancy_density)
self._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[j]) self._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[j])
@ -227,20 +222,11 @@ class NrcanUsageParameters:
def average_storey_height_calculator(city, building): def average_storey_height_calculator(city, building):
climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city) climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.climate_reference_city)
nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog
main_function = None
functions = building.function.split('_') if building.function not in Dictionaries().hub_function_to_nrcan_construction_function:
if len(functions) > 1: logging.error('Building %s has an unknown building function %s', building.name, building.function)
maximum_percentage = 0
for function in functions: function = Dictionaries().hub_function_to_nrcan_construction_function[building.function]
percentage_and_function = function.split('-')
if float(percentage_and_function[0]) > maximum_percentage:
maximum_percentage = float(percentage_and_function[0])
main_function = percentage_and_function[-1]
else:
main_function = functions[-1]
if main_function not in Dictionaries().hub_function_to_nrcan_construction_function:
logging.error('Building %s has an unknown building function %s', building.name, main_function)
function = Dictionaries().hub_function_to_nrcan_construction_function[main_function]
construction_archetype = None construction_archetype = None
average_storey_height = None average_storey_height = None
nrcan_archetypes = nrcan_catalog.entries('archetypes') nrcan_archetypes = nrcan_catalog.entries('archetypes')

View File

@ -65,6 +65,7 @@ setup(
'hub.helpers', 'hub.helpers',
'hub.helpers.peak_calculation', 'hub.helpers.peak_calculation',
'hub.helpers.data', 'hub.helpers.data',
'hub.helpers.parsers',
'hub.imports', 'hub.imports',
'hub.imports.construction', 'hub.imports.construction',
'hub.imports.construction.helpers', 'hub.imports.construction.helpers',
@ -109,4 +110,4 @@ setup(
('hub/exports/building_energy/idf_files', glob.glob('hub/exports/building_energy/idf_files/*.idd')) ('hub/exports/building_energy/idf_files', glob.glob('hub/exports/building_energy/idf_files/*.idd'))
], ],
) )