forked from s_ranjbar/city_retrofit
Merge remote-tracking branch 'origin/main' into final_energy_system_model
# Conflicts: # .gitignore # hub/city_model_structure/building.py # hub/exports/building_energy/idf.py # hub/imports/geometry_factory.py
This commit is contained in:
commit
674393970c
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -1 +1,12 @@
|
||||||
.idea
|
!.gitignore
|
||||||
|
**/venv/
|
||||||
|
.idea/
|
||||||
|
/development_tests/
|
||||||
|
/data/energy_systems/heat_pumps/*.csv
|
||||||
|
/data/energy_systems/heat_pumps/*.insel
|
||||||
|
.DS_Store
|
||||||
|
**/.env
|
||||||
|
**/hub/logs/
|
||||||
|
**/__pycache__/
|
||||||
|
**/.idea/
|
||||||
|
cerc_hub.egg-info
|
||||||
|
|
3
cerc_hub.egg-info/.gitignore
vendored
3
cerc_hub.egg-info/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
# Except this file
|
|
||||||
*
|
|
||||||
!.gitignore
|
|
|
@ -188,7 +188,7 @@ class EilatCatalog(Catalog):
|
||||||
schedules_key = {}
|
schedules_key = {}
|
||||||
for j in range(0, number_usage_types):
|
for j in range(0, number_usage_types):
|
||||||
usage_parameters = _extracted_data.iloc[j]
|
usage_parameters = _extracted_data.iloc[j]
|
||||||
usage_type = usage_parameters[0]
|
usage_type = usage_parameters.iloc[0]
|
||||||
lighting_data[usage_type] = usage_parameters[1:6].values.tolist()
|
lighting_data[usage_type] = usage_parameters[1:6].values.tolist()
|
||||||
plug_loads_data[usage_type] = usage_parameters[8:13].values.tolist()
|
plug_loads_data[usage_type] = usage_parameters[8:13].values.tolist()
|
||||||
occupancy_data[usage_type] = usage_parameters[17:20].values.tolist()
|
occupancy_data[usage_type] = usage_parameters[17:20].values.tolist()
|
||||||
|
|
|
@ -8,6 +8,8 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
|
||||||
import hub.helpers.constants as cte
|
import hub.helpers.constants as cte
|
||||||
|
@ -28,12 +30,11 @@ class NrcanCatalog(Catalog):
|
||||||
Nrcan catalog class
|
Nrcan catalog class
|
||||||
"""
|
"""
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
path = str(path / 'nrcan.xml')
|
self._schedules_path = Path(path / 'nrcan_schedules.json').resolve()
|
||||||
|
self._space_types_path = Path(path / 'nrcan_space_types.json').resolve()
|
||||||
|
self._space_compliance_path = Path(path / 'nrcan_space_compliance_2015.json').resolve()
|
||||||
self._content = None
|
self._content = None
|
||||||
self._schedules = {}
|
self._schedules = {}
|
||||||
with open(path, 'r', encoding='utf-8') as xml:
|
|
||||||
self._metadata = xmltodict.parse(xml.read())
|
|
||||||
self._base_url = self._metadata['nrcan']['@base_url']
|
|
||||||
self._load_schedules()
|
self._load_schedules()
|
||||||
self._content = Content(self._load_archetypes())
|
self._content = Content(self._load_archetypes())
|
||||||
|
|
||||||
|
@ -55,11 +56,9 @@ class NrcanCatalog(Catalog):
|
||||||
return Schedule(hub_type, raw['values'], data_type, time_step, time_range, day_types)
|
return Schedule(hub_type, raw['values'], data_type, time_step, time_range, day_types)
|
||||||
|
|
||||||
def _load_schedules(self):
|
def _load_schedules(self):
|
||||||
usage = self._metadata['nrcan']
|
|
||||||
url = f'{self._base_url}{usage["schedules"]}'
|
|
||||||
_schedule_types = []
|
_schedule_types = []
|
||||||
with urllib.request.urlopen(url) as json_file:
|
with open(self._schedules_path, 'r') as f:
|
||||||
schedules_type = json.load(json_file)
|
schedules_type = json.load(f)
|
||||||
for schedule_type in schedules_type['tables']['schedules']['table']:
|
for schedule_type in schedules_type['tables']['schedules']['table']:
|
||||||
schedule = NrcanCatalog._extract_schedule(schedule_type)
|
schedule = NrcanCatalog._extract_schedule(schedule_type)
|
||||||
if schedule_type['name'] not in _schedule_types:
|
if schedule_type['name'] not in _schedule_types:
|
||||||
|
@ -80,14 +79,11 @@ class NrcanCatalog(Catalog):
|
||||||
|
|
||||||
def _load_archetypes(self):
|
def _load_archetypes(self):
|
||||||
usages = []
|
usages = []
|
||||||
name = self._metadata['nrcan']
|
with open(self._space_types_path, 'r') as f:
|
||||||
url_1 = f'{self._base_url}{name["space_types"]}'
|
space_types = json.load(f)['tables']['space_types']['table']
|
||||||
url_2 = f'{self._base_url}{name["space_types_compliance"]}'
|
|
||||||
with urllib.request.urlopen(url_1) as json_file:
|
|
||||||
space_types = json.load(json_file)['tables']['space_types']['table']
|
|
||||||
space_types = [st for st in space_types if st['space_type'] == 'WholeBuilding']
|
space_types = [st for st in space_types if st['space_type'] == 'WholeBuilding']
|
||||||
with urllib.request.urlopen(url_2) as json_file:
|
with open(self._space_compliance_path, 'r') as f:
|
||||||
space_types_compliance = json.load(json_file)['tables']['space_compliance']['table']
|
space_types_compliance = json.load(f)['tables']['space_compliance']['table']
|
||||||
space_types_compliance = [st for st in space_types_compliance if st['space_type'] == 'WholeBuilding']
|
space_types_compliance = [st for st in space_types_compliance if st['space_type'] == 'WholeBuilding']
|
||||||
space_types_dictionary = {}
|
space_types_dictionary = {}
|
||||||
for space_type in space_types_compliance:
|
for space_type in space_types_compliance:
|
||||||
|
|
|
@ -747,8 +747,13 @@ class Building(CityObject):
|
||||||
|
|
||||||
for key, item in self._distribution_systems_electrical_consumption.items():
|
for key, item in self._distribution_systems_electrical_consumption.items():
|
||||||
for i in range(0, len(item)):
|
for i in range(0, len(item)):
|
||||||
self._distribution_systems_electrical_consumption[key][i] += _peak_load * _consumption_fix_flow \
|
_working_hours_value = _working_hours[key]
|
||||||
* _working_hours[key] * cte.WATTS_HOUR_TO_JULES
|
if len(item) == 12:
|
||||||
|
_working_hours_value = _working_hours[key][i]
|
||||||
|
self._distribution_systems_electrical_consumption[key][i] += (
|
||||||
|
_peak_load * _consumption_fix_flow * _working_hours_value * cte.WATTS_HOUR_TO_JULES
|
||||||
|
)
|
||||||
|
|
||||||
return self._distribution_systems_electrical_consumption
|
return self._distribution_systems_electrical_consumption
|
||||||
|
|
||||||
def _calculate_consumption(self, consumption_type, demand):
|
def _calculate_consumption(self, consumption_type, demand):
|
||||||
|
@ -836,3 +841,17 @@ class Building(CityObject):
|
||||||
"""
|
"""
|
||||||
self._heating_consumption_disaggregated = value
|
self._heating_consumption_disaggregated = value
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lower_corner(self):
|
||||||
|
"""
|
||||||
|
Get building lower corner.
|
||||||
|
"""
|
||||||
|
return [self._min_x, self._min_y, self._min_z]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def upper_corner(self):
|
||||||
|
"""
|
||||||
|
Get building upper corner.
|
||||||
|
"""
|
||||||
|
return [self._max_x, self._max_y, self._max_z]
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Layer:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._thickness = None
|
self._thickness = None
|
||||||
self._id = None
|
self._id = None
|
||||||
self._name = None
|
self._material_name = None
|
||||||
self._conductivity = None
|
self._conductivity = None
|
||||||
self._specific_heat = None
|
self._specific_heat = None
|
||||||
self._density = None
|
self._density = None
|
||||||
|
@ -54,22 +54,20 @@ class Layer:
|
||||||
self._thickness = float(value)
|
self._thickness = float(value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def material_name(self):
|
||||||
"""
|
"""
|
||||||
Get material name
|
Get material name
|
||||||
:return: str
|
:return: str
|
||||||
"""
|
"""
|
||||||
# todo: this should be named material_name instead
|
return self._material_name
|
||||||
return self._name
|
|
||||||
|
|
||||||
@name.setter
|
@material_name.setter
|
||||||
def name(self, value):
|
def material_name(self, value):
|
||||||
"""
|
"""
|
||||||
Set material name
|
Set material name
|
||||||
:param value: string
|
:param value: string
|
||||||
"""
|
"""
|
||||||
# todo: this should be named material_name instead
|
self._material_name = str(value)
|
||||||
self._name = str(value)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def conductivity(self) -> Union[None, float]:
|
def conductivity(self) -> Union[None, float]:
|
||||||
|
|
|
@ -18,6 +18,7 @@ from hub.city_model_structure.attributes.point import Point
|
||||||
from hub.city_model_structure.greenery.vegetation import Vegetation
|
from hub.city_model_structure.greenery.vegetation import Vegetation
|
||||||
from hub.city_model_structure.building_demand.thermal_boundary import ThermalBoundary
|
from hub.city_model_structure.building_demand.thermal_boundary import ThermalBoundary
|
||||||
import hub.helpers.constants as cte
|
import hub.helpers.constants as cte
|
||||||
|
from hub.helpers.configuration_helper import ConfigurationHelper
|
||||||
|
|
||||||
|
|
||||||
class Surface:
|
class Surface:
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<nrcan base_url="https://raw.githubusercontent.com/NREL/openstudio-standards/master/lib/openstudio-standards/standards/necb/">
|
|
||||||
<space_types>NECB2015/data/space_types.json</space_types>
|
|
||||||
<space_types_compliance>NECB2015/qaqc/qaqc_data/space_compliance_2015.json</space_types_compliance>>
|
|
||||||
<schedules>NECB2015/data/schedules.json</schedules>
|
|
||||||
</nrcan>
|
|
8685
hub/data/usage/nrcan_schedules.json
Normal file
8685
hub/data/usage/nrcan_schedules.json
Normal file
File diff suppressed because it is too large
Load Diff
3211
hub/data/usage/nrcan_space_compliance_2015.json
Normal file
3211
hub/data/usage/nrcan_space_compliance_2015.json
Normal file
File diff suppressed because it is too large
Load Diff
26170
hub/data/usage/nrcan_space_types.json
Normal file
26170
hub/data/usage/nrcan_space_types.json
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -12,6 +12,7 @@ from geomeppy import IDF
|
||||||
import hub.helpers.constants as cte
|
import hub.helpers.constants as cte
|
||||||
from hub.city_model_structure.attributes.schedule import Schedule
|
from hub.city_model_structure.attributes.schedule import Schedule
|
||||||
from hub.city_model_structure.building_demand.thermal_zone import ThermalZone
|
from hub.city_model_structure.building_demand.thermal_zone import ThermalZone
|
||||||
|
from hub.helpers.configuration_helper import ConfigurationHelper
|
||||||
|
|
||||||
|
|
||||||
class Idf:
|
class Idf:
|
||||||
|
@ -53,9 +54,16 @@ class Idf:
|
||||||
_SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY'
|
_SIZING_PERIODS = 'SIZINGPERIOD:DESIGNDAY'
|
||||||
_LOCATION = 'SITE:LOCATION'
|
_LOCATION = 'SITE:LOCATION'
|
||||||
_SIMPLE = 'Simple'
|
_SIMPLE = 'Simple'
|
||||||
|
_EQUIPMENT_CONNECTIONS = 'ZONEHVAC:EQUIPMENTCONNECTIONS'
|
||||||
|
_NODE_LIST = 'NODELIST'
|
||||||
|
_BASEBOARD = 'ZONEHVAC:BASEBOARD:CONVECTIVE:ELECTRIC'
|
||||||
|
_AIR_TERMINAL_NO_REHEAT = 'AIRTERMINAL:SINGLEDUCT:CONSTANTVOLUME:NOREHEAT'
|
||||||
|
_AIR_DISTRIBUTION = 'ZONEHVAC:AIRDISTRIBUTIONUNIT'
|
||||||
|
_EQUIPMENT_LIST = 'ZONEHVAC:EQUIPMENTLIST'
|
||||||
|
_SIZING_ZONE = 'SIZING:ZONE'
|
||||||
|
_DESIGN_SPECIFICATION_OUTDOOR_AIR = 'DESIGNSPECIFICATION:OUTDOORAIR'
|
||||||
|
|
||||||
idf_surfaces = {
|
idf_surfaces = {
|
||||||
# todo: make an enum for all the surface types
|
|
||||||
cte.WALL: 'wall',
|
cte.WALL: 'wall',
|
||||||
cte.GROUND: 'floor',
|
cte.GROUND: 'floor',
|
||||||
cte.ROOF: 'roof'
|
cte.ROOF: 'roof'
|
||||||
|
@ -117,7 +125,8 @@ class Idf:
|
||||||
if levels_of_detail.construction is None:
|
if levels_of_detail.construction is None:
|
||||||
raise AttributeError('Level of detail of construction not assigned')
|
raise AttributeError('Level of detail of construction not assigned')
|
||||||
if levels_of_detail.construction < 2:
|
if levels_of_detail.construction < 2:
|
||||||
raise AttributeError(f'Level of detail of construction = {levels_of_detail.construction}. Required minimum level 2')
|
raise AttributeError(
|
||||||
|
f'Level of detail of construction = {levels_of_detail.construction}. Required minimum level 2')
|
||||||
if levels_of_detail.usage is None:
|
if levels_of_detail.usage is None:
|
||||||
raise AttributeError('Level of detail of usage not assigned')
|
raise AttributeError('Level of detail of usage not assigned')
|
||||||
if levels_of_detail.usage < 2:
|
if levels_of_detail.usage < 2:
|
||||||
|
@ -148,20 +157,20 @@ class Idf:
|
||||||
|
|
||||||
def _add_material(self, layer):
|
def _add_material(self, layer):
|
||||||
for material in self._idf.idfobjects[self._MATERIAL]:
|
for material in self._idf.idfobjects[self._MATERIAL]:
|
||||||
if material.Name == layer.name:
|
if material.Name == layer.material_name:
|
||||||
return
|
return
|
||||||
for material in self._idf.idfobjects[self._MATERIAL_NOMASS]:
|
for material in self._idf.idfobjects[self._MATERIAL_NOMASS]:
|
||||||
if material.Name == layer.name:
|
if material.Name == layer.material_name:
|
||||||
return
|
return
|
||||||
if layer.no_mass:
|
if layer.no_mass:
|
||||||
self._idf.newidfobject(self._MATERIAL_NOMASS,
|
self._idf.newidfobject(self._MATERIAL_NOMASS,
|
||||||
Name=layer.name,
|
Name=layer.material_name,
|
||||||
Roughness=self._ROUGHNESS,
|
Roughness=self._ROUGHNESS,
|
||||||
Thermal_Resistance=layer.thermal_resistance
|
Thermal_Resistance=layer.thermal_resistance
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._idf.newidfobject(self._MATERIAL,
|
self._idf.newidfobject(self._MATERIAL,
|
||||||
Name=layer.name,
|
Name=layer.material_name,
|
||||||
Roughness=self._ROUGHNESS,
|
Roughness=self._ROUGHNESS,
|
||||||
Thickness=layer.thickness,
|
Thickness=layer.thickness,
|
||||||
Conductivity=layer.conductivity,
|
Conductivity=layer.conductivity,
|
||||||
|
@ -276,11 +285,11 @@ class Idf:
|
||||||
self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs)
|
self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs)
|
||||||
|
|
||||||
def _write_schedules_file(self, usage, schedule):
|
def _write_schedules_file(self, usage, schedule):
|
||||||
file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.dat').resolve())
|
file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.csv').resolve())
|
||||||
with open(file_name, 'w', encoding='utf8') as file:
|
with open(file_name, 'w', encoding='utf8') as file:
|
||||||
for value in schedule.values:
|
for value in schedule.values:
|
||||||
file.write(f'{str(value)},\n')
|
file.write(f'{str(value)},\n')
|
||||||
return file_name
|
return Path(file_name).name
|
||||||
|
|
||||||
def _add_file_schedule(self, usage, schedule, file_name):
|
def _add_file_schedule(self, usage, schedule, file_name):
|
||||||
_schedule = self._idf.newidfobject(self._FILE_SCHEDULE, Name=f'{schedule.type} schedules {usage}')
|
_schedule = self._idf.newidfobject(self._FILE_SCHEDULE, Name=f'{schedule.type} schedules {usage}')
|
||||||
|
@ -338,17 +347,17 @@ class Idf:
|
||||||
_kwargs = {'Name': vegetation_name,
|
_kwargs = {'Name': vegetation_name,
|
||||||
'Outside_Layer': thermal_boundary.parent_surface.vegetation.name}
|
'Outside_Layer': thermal_boundary.parent_surface.vegetation.name}
|
||||||
for i in range(0, len(layers) - 1):
|
for i in range(0, len(layers) - 1):
|
||||||
_kwargs[f'Layer_{i + 2}'] = layers[i].name
|
_kwargs[f'Layer_{i + 2}'] = layers[i].material_name
|
||||||
else:
|
else:
|
||||||
_kwargs = {'Name': thermal_boundary.construction_name, 'Outside_Layer': layers[0].name}
|
_kwargs = {'Name': thermal_boundary.construction_name, 'Outside_Layer': layers[0].material_name}
|
||||||
for i in range(1, len(layers) - 1):
|
for i in range(1, len(layers) - 1):
|
||||||
_kwargs[f'Layer_{i + 1}'] = layers[i].name
|
_kwargs[f'Layer_{i + 1}'] = layers[i].material_name
|
||||||
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
self._idf.newidfobject(self._CONSTRUCTION, **_kwargs)
|
||||||
|
|
||||||
def _add_window_construction_and_material(self, thermal_opening):
|
def _add_window_construction_and_material(self, thermal_opening):
|
||||||
for window_material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]:
|
for window_material in self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]:
|
||||||
if window_material['UFactor'] == thermal_opening.overall_u_value and \
|
if window_material['UFactor'] == thermal_opening.overall_u_value and \
|
||||||
window_material['Solar_Heat_Gain_Coefficient'] == thermal_opening.g_value:
|
window_material['Solar_Heat_Gain_Coefficient'] == thermal_opening.g_value:
|
||||||
return
|
return
|
||||||
|
|
||||||
order = str(len(self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]) + 1)
|
order = str(len(self._idf.idfobjects[self._WINDOW_MATERIAL_SIMPLE]) + 1)
|
||||||
|
@ -381,29 +390,93 @@ class Idf:
|
||||||
)
|
)
|
||||||
|
|
||||||
def _add_heating_system(self, thermal_zone, zone_name):
|
def _add_heating_system(self, thermal_zone, zone_name):
|
||||||
for air_system in self._idf.idfobjects[self._IDEAL_LOAD_AIR_SYSTEM]:
|
for air_system in self._idf.idfobjects[self._EQUIPMENT_CONNECTIONS]:
|
||||||
if air_system.Zone_Name == zone_name:
|
if air_system.Zone_Name == zone_name:
|
||||||
return
|
return
|
||||||
thermostat = self._add_thermostat(thermal_zone)
|
thermostat = self._add_thermostat(thermal_zone)
|
||||||
self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM,
|
self._idf.newidfobject(self._EQUIPMENT_CONNECTIONS,
|
||||||
Zone_Name=zone_name,
|
Zone_Name=zone_name,
|
||||||
System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}',
|
Zone_Conditioning_Equipment_List_Name=f'{zone_name} Equipment List',
|
||||||
Heating_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}',
|
Zone_Air_Inlet_Node_or_NodeList_Name=f'{zone_name} Inlet Node List',
|
||||||
Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage_name}',
|
Zone_Air_Node_Name=f'Node 1',
|
||||||
Template_Thermostat_Name=thermostat.Name)
|
Zone_Return_Air_Node_or_NodeList_Name=f'{zone_name} Return Node List')
|
||||||
|
|
||||||
|
def _add_nodelist_system(self, thermal_zone, zone_name):
|
||||||
|
self._idf.newidfobject(self._NODE_LIST,
|
||||||
|
Name=f'{zone_name} Inlet Node List',
|
||||||
|
Node_1_Name='Node 2')
|
||||||
|
self._idf.newidfobject(self._NODE_LIST,
|
||||||
|
Name=f'{zone_name} Return Node List',
|
||||||
|
Node_1_Name='Node 3')
|
||||||
|
|
||||||
|
def _add_baseboard_system(self, thermal_zone, zone_name):
|
||||||
|
for baseboard in self._idf.idfobjects[self._BASEBOARD]:
|
||||||
|
if baseboard.Zone_Name == zone_name:
|
||||||
|
return
|
||||||
|
self._idf.newidfobject(self._BASEBOARD, Name=f'Elec Baseboard',Availability_Schedule_Name='HVAC AVAIL')
|
||||||
|
|
||||||
|
def _add_air_terminal_system(self, thermal_zone, zone_name):
|
||||||
|
"""for air_terminal in self._idf.idfobjects[self._AIR_TERMINAL_NO_REHEAT]:
|
||||||
|
if air_terminal.Zone_Name == zone_name:
|
||||||
|
return"""
|
||||||
|
self._idf.newidfobject(self._AIR_TERMINAL_NO_REHEAT, Name=f'Diffuser',
|
||||||
|
Availability_Schedule_Name='HVAC AVAIL',
|
||||||
|
Air_Inlet_Node_Name='Node 4',
|
||||||
|
Air_Outlet_Node_Name='Node 2',
|
||||||
|
Maximum_Air_Flow_Rate='AutoSize')
|
||||||
|
|
||||||
|
def _add_air_distribution_system(self, thermal_zone, zone_name):
|
||||||
|
for air_distribution in self._idf.idfobjects[self._AIR_DISTRIBUTION]:
|
||||||
|
if air_distribution.Zone_Name == zone_name:
|
||||||
|
return
|
||||||
|
self._idf.newidfobject(self._AIR_DISTRIBUTION,
|
||||||
|
Name='ADU Diffuser',
|
||||||
|
Air_Distribution_Unit_Outlet_Node_Name='Node 2',
|
||||||
|
Air_Terminal_Object_Type='AirTerminal:SingleDuct:ConstantVolume:NoReheat',
|
||||||
|
Air_Terminal_Name='Diffuser')
|
||||||
|
|
||||||
|
def _add_equipment_list_system(self, thermal_zone, zone_name):
|
||||||
|
for air_distribution in self._idf.idfobjects[self._EQUIPMENT_LIST]:
|
||||||
|
if air_distribution.Zone_Name == zone_name:
|
||||||
|
return
|
||||||
|
self._idf.newidfobject(self._EQUIPMENT_LIST,
|
||||||
|
Name=f'{zone_name} Equipment List',
|
||||||
|
Load_Distribution_Scheme='SequentialLoad',
|
||||||
|
Zone_Equipment_1_Object_Type='ZoneHVAC:Baseboard:Convective:Electric',
|
||||||
|
Zone_Equipment_1_Name='Elec Baseboard',
|
||||||
|
Zone_Equipment_1_Cooling_Sequence='1',
|
||||||
|
Zone_Equipment_1_Heating_or_NoLoad_Sequence='1',
|
||||||
|
Zone_Equipment_2_Object_Type='ZoneHVAC:AirDistributionUnit',
|
||||||
|
Zone_Equipment_2_Name='ADU Diffuser',
|
||||||
|
Zone_Equipment_2_Cooling_Sequence='2',
|
||||||
|
Zone_Equipment_2_Heating_or_NoLoad_Sequence='2'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _add_sizing_zone(self, thermal_zone, zone_name):
|
||||||
|
koa=self._idf.newidfobject(self._SIZING_ZONE,
|
||||||
|
Zone_or_ZoneList_Name=f'{zone_name}',
|
||||||
|
Zone_Cooling_Design_Supply_Air_Humidity_Ratio='0.0085',
|
||||||
|
Zone_Heating_Design_Supply_Air_Humidity_Ratio='0.008'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _add_outdoor_air_design_specification(self, thermal_zone, zone_name):
|
||||||
|
self._idf.newidfobject(self._DESIGN_SPECIFICATION_OUTDOOR_AIR,
|
||||||
|
Name='MidriseApartment Apartment Ventilation',
|
||||||
|
Outdoor_Air_Method='Sum',
|
||||||
|
Outdoor_Air_Flow_per_Person='0.0169901079552')
|
||||||
|
|
||||||
def _add_occupancy(self, thermal_zone, zone_name):
|
def _add_occupancy(self, thermal_zone, zone_name):
|
||||||
number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area
|
number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area
|
||||||
fraction_radiant = 0
|
fraction_radiant = 0
|
||||||
total_sensible = (
|
total_sensible = (
|
||||||
thermal_zone.occupancy.sensible_radiative_internal_gain + thermal_zone.occupancy.sensible_convective_internal_gain
|
thermal_zone.occupancy.sensible_radiative_internal_gain + thermal_zone.occupancy.sensible_convective_internal_gain
|
||||||
)
|
)
|
||||||
if total_sensible != 0:
|
if total_sensible != 0:
|
||||||
fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / total_sensible
|
fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / total_sensible
|
||||||
|
|
||||||
self._idf.newidfobject(self._PEOPLE,
|
self._idf.newidfobject(self._PEOPLE,
|
||||||
Name=f'{zone_name}_occupancy',
|
Name=f'{zone_name}_occupancy',
|
||||||
Zone_or_ZoneList_Name=zone_name,
|
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
|
||||||
Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage_name}',
|
Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage_name}',
|
||||||
Number_of_People_Calculation_Method="People",
|
Number_of_People_Calculation_Method="People",
|
||||||
Number_of_People=number_of_people,
|
Number_of_People=number_of_people,
|
||||||
|
@ -420,7 +493,7 @@ class Idf:
|
||||||
|
|
||||||
self._idf.newidfobject(self._LIGHTS,
|
self._idf.newidfobject(self._LIGHTS,
|
||||||
Name=f'{zone_name}_lights',
|
Name=f'{zone_name}_lights',
|
||||||
Zone_or_ZoneList_Name=zone_name,
|
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
|
||||||
Schedule_Name=f'Lighting schedules {thermal_zone.usage_name}',
|
Schedule_Name=f'Lighting schedules {thermal_zone.usage_name}',
|
||||||
Design_Level_Calculation_Method=method,
|
Design_Level_Calculation_Method=method,
|
||||||
Watts_per_Zone_Floor_Area=watts_per_zone_floor_area,
|
Watts_per_Zone_Floor_Area=watts_per_zone_floor_area,
|
||||||
|
@ -439,7 +512,7 @@ class Idf:
|
||||||
self._idf.newidfobject(self._APPLIANCES,
|
self._idf.newidfobject(self._APPLIANCES,
|
||||||
Fuel_Type=fuel_type,
|
Fuel_Type=fuel_type,
|
||||||
Name=f'{zone_name}_appliance',
|
Name=f'{zone_name}_appliance',
|
||||||
Zone_or_ZoneList_Name=zone_name,
|
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
|
||||||
Schedule_Name=f'Appliance schedules {thermal_zone.usage_name}',
|
Schedule_Name=f'Appliance schedules {thermal_zone.usage_name}',
|
||||||
Design_Level_Calculation_Method=method,
|
Design_Level_Calculation_Method=method,
|
||||||
Power_per_Zone_Floor_Area=watts_per_zone_floor_area,
|
Power_per_Zone_Floor_Area=watts_per_zone_floor_area,
|
||||||
|
@ -453,7 +526,7 @@ class Idf:
|
||||||
_infiltration = thermal_zone.infiltration_rate_system_off * cte.HOUR_TO_SECONDS
|
_infiltration = thermal_zone.infiltration_rate_system_off * cte.HOUR_TO_SECONDS
|
||||||
self._idf.newidfobject(self._INFILTRATION,
|
self._idf.newidfobject(self._INFILTRATION,
|
||||||
Name=f'{zone_name}_infiltration',
|
Name=f'{zone_name}_infiltration',
|
||||||
Zone_or_ZoneList_Name=zone_name,
|
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
|
||||||
Schedule_Name=schedule,
|
Schedule_Name=schedule,
|
||||||
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
|
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
|
||||||
Air_Changes_per_Hour=_infiltration
|
Air_Changes_per_Hour=_infiltration
|
||||||
|
@ -464,7 +537,7 @@ class Idf:
|
||||||
_air_change = thermal_zone.mechanical_air_change * cte.HOUR_TO_SECONDS
|
_air_change = thermal_zone.mechanical_air_change * cte.HOUR_TO_SECONDS
|
||||||
self._idf.newidfobject(self._VENTILATION,
|
self._idf.newidfobject(self._VENTILATION,
|
||||||
Name=f'{zone_name}_ventilation',
|
Name=f'{zone_name}_ventilation',
|
||||||
Zone_or_ZoneList_Name=zone_name,
|
Zone_or_ZoneList_or_Space_or_SpaceList_Name=zone_name,
|
||||||
Schedule_Name=schedule,
|
Schedule_Name=schedule,
|
||||||
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
|
Design_Flow_Rate_Calculation_Method='AirChanges/Hour',
|
||||||
Air_Changes_per_Hour=_air_change
|
Air_Changes_per_Hour=_air_change
|
||||||
|
@ -553,12 +626,7 @@ class Idf:
|
||||||
self._add_zone(thermal_zone, building.name)
|
self._add_zone(thermal_zone, building.name)
|
||||||
self._add_heating_system(thermal_zone, building.name)
|
self._add_heating_system(thermal_zone, building.name)
|
||||||
self._add_infiltration(thermal_zone, building.name)
|
self._add_infiltration(thermal_zone, building.name)
|
||||||
self._add_ventilation(thermal_zone, building.name)
|
|
||||||
self._add_occupancy(thermal_zone, building.name)
|
|
||||||
self._add_lighting(thermal_zone, building.name)
|
|
||||||
self._add_appliances(thermal_zone, building.name)
|
|
||||||
self._add_dhw(thermal_zone, building.name)
|
self._add_dhw(thermal_zone, building.name)
|
||||||
# todo: @Guille: if export_type is not surfaces, we don't differentiate between shadows and buildings?
|
|
||||||
if self._export_type == "Surfaces":
|
if self._export_type == "Surfaces":
|
||||||
if building.name in self._target_buildings or building.name in self._adjacent_buildings:
|
if building.name in self._target_buildings or building.name in self._adjacent_buildings:
|
||||||
if building.thermal_zones_from_internal_zones is not None:
|
if building.thermal_zones_from_internal_zones is not None:
|
||||||
|
@ -611,7 +679,6 @@ class Idf:
|
||||||
windows_list.append(window)
|
windows_list.append(window)
|
||||||
for window in windows_list:
|
for window in windows_list:
|
||||||
self._idf.removeidfobject(window)
|
self._idf.removeidfobject(window)
|
||||||
|
|
||||||
self._idf.saveas(str(self._output_file))
|
self._idf.saveas(str(self._output_file))
|
||||||
return self._idf
|
return self._idf
|
||||||
|
|
||||||
|
@ -640,24 +707,26 @@ class Idf:
|
||||||
self._idf.intersect_match()
|
self._idf.intersect_match()
|
||||||
|
|
||||||
def _add_shading(self, building):
|
def _add_shading(self, building):
|
||||||
for surface in building.surfaces:
|
for i, surface in enumerate(building.surfaces):
|
||||||
shading = self._idf.newidfobject(self._SHADING, Name=f'{surface.name}')
|
shading = self._idf.newidfobject(self._SHADING, Name=f'{building.name}_{i}')
|
||||||
coordinates = self._matrix_to_list(surface.solid_polygon.coordinates,
|
coordinates = self._matrix_to_list(surface.solid_polygon.coordinates,
|
||||||
self._city.lower_corner)
|
self._city.lower_corner)
|
||||||
shading.setcoords(coordinates)
|
shading.setcoords(coordinates)
|
||||||
solar_reflectance = surface.short_wave_reflectance
|
solar_reflectance = surface.short_wave_reflectance
|
||||||
|
if solar_reflectance is None:
|
||||||
|
solar_reflectance = ConfigurationHelper().short_wave_reflectance
|
||||||
self._idf.newidfobject(self._SHADING_PROPERTY,
|
self._idf.newidfobject(self._SHADING_PROPERTY,
|
||||||
Shading_Surface_Name=f'{surface.name}',
|
Shading_Surface_Name=f'{building.name}_{i}',
|
||||||
Diffuse_Solar_Reflectance_of_Unglazed_Part_of_Shading_Surface=solar_reflectance,
|
Diffuse_Solar_Reflectance_of_Unglazed_Part_of_Shading_Surface=solar_reflectance,
|
||||||
Fraction_of_Shading_Surface_That_Is_Glazed=0)
|
Fraction_of_Shading_Surface_That_Is_Glazed=0)
|
||||||
|
|
||||||
def _add_pure_geometry(self, building, zone_name):
|
def _add_pure_geometry(self, building, zone_name):
|
||||||
for surface in building.surfaces:
|
for index, surface in enumerate(building.surfaces):
|
||||||
outside_boundary_condition = 'Outdoors'
|
outside_boundary_condition = 'Outdoors'
|
||||||
sun_exposure = 'SunExposed'
|
sun_exposure = 'SunExposed'
|
||||||
wind_exposure = 'WindExposed'
|
wind_exposure = 'WindExposed'
|
||||||
idf_surface_type = self.idf_surfaces[surface.type]
|
idf_surface_type = self.idf_surfaces[surface.type]
|
||||||
_kwargs = {'Name': f'{surface.name}',
|
_kwargs = {'Name': f'Building_{building.name}_surface_{index}',
|
||||||
'Surface_Type': idf_surface_type,
|
'Surface_Type': idf_surface_type,
|
||||||
'Zone_Name': zone_name}
|
'Zone_Name': zone_name}
|
||||||
if surface.type == cte.GROUND:
|
if surface.type == cte.GROUND:
|
||||||
|
@ -666,7 +735,7 @@ class Idf:
|
||||||
wind_exposure = 'NoWind'
|
wind_exposure = 'NoWind'
|
||||||
if surface.percentage_shared is not None and surface.percentage_shared > 0.5:
|
if surface.percentage_shared is not None and surface.percentage_shared > 0.5:
|
||||||
outside_boundary_condition = 'Surface'
|
outside_boundary_condition = 'Surface'
|
||||||
outside_boundary_condition_object = surface.name
|
outside_boundary_condition_object = f'Building_{building.name}_surface_{index}'
|
||||||
sun_exposure = 'NoSun'
|
sun_exposure = 'NoSun'
|
||||||
wind_exposure = 'NoWind'
|
wind_exposure = 'NoWind'
|
||||||
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object
|
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object
|
||||||
|
@ -691,12 +760,12 @@ class Idf:
|
||||||
|
|
||||||
def _add_surfaces(self, building, zone_name):
|
def _add_surfaces(self, building, zone_name):
|
||||||
for thermal_zone in building.thermal_zones_from_internal_zones:
|
for thermal_zone in building.thermal_zones_from_internal_zones:
|
||||||
for boundary in thermal_zone.thermal_boundaries:
|
for index, boundary in enumerate(thermal_zone.thermal_boundaries):
|
||||||
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
|
idf_surface_type = self.idf_surfaces[boundary.parent_surface.type]
|
||||||
outside_boundary_condition = 'Outdoors'
|
outside_boundary_condition = 'Outdoors'
|
||||||
sun_exposure = 'SunExposed'
|
sun_exposure = 'SunExposed'
|
||||||
wind_exposure = 'WindExposed'
|
wind_exposure = 'WindExposed'
|
||||||
_kwargs = {'Name': f'{boundary.parent_surface.name}',
|
_kwargs = {'Name': f'Building_{building.name}_surface_{index}',
|
||||||
'Surface_Type': idf_surface_type,
|
'Surface_Type': idf_surface_type,
|
||||||
'Zone_Name': zone_name}
|
'Zone_Name': zone_name}
|
||||||
if boundary.parent_surface.type == cte.GROUND:
|
if boundary.parent_surface.type == cte.GROUND:
|
||||||
|
@ -705,7 +774,7 @@ class Idf:
|
||||||
wind_exposure = 'NoWind'
|
wind_exposure = 'NoWind'
|
||||||
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5:
|
if boundary.parent_surface.percentage_shared is not None and boundary.parent_surface.percentage_shared > 0.5:
|
||||||
outside_boundary_condition = 'Surface'
|
outside_boundary_condition = 'Surface'
|
||||||
outside_boundary_condition_object = boundary.parent_surface.name
|
outside_boundary_condition_object = f'Building_{building.name}_surface_{index}'
|
||||||
sun_exposure = 'NoSun'
|
sun_exposure = 'NoSun'
|
||||||
wind_exposure = 'NoWind'
|
wind_exposure = 'NoWind'
|
||||||
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object
|
_kwargs['Outside_Boundary_Condition_Object'] = outside_boundary_condition_object
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
103532
hub/exports/building_energy/idf_files/Energy+9.5.idd
Normal file
103532
hub/exports/building_energy/idf_files/Energy+9.5.idd
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -13,7 +13,7 @@
|
||||||
! HVAC: None.
|
! HVAC: None.
|
||||||
!
|
!
|
||||||
|
|
||||||
Version,9.5;
|
Version,24.1;
|
||||||
|
|
||||||
Timestep,4;
|
Timestep,4;
|
||||||
|
|
||||||
|
@ -122,36 +122,30 @@
|
||||||
No, !- Do Zone Sizing Calculation
|
No, !- Do Zone Sizing Calculation
|
||||||
No, !- Do System Sizing Calculation
|
No, !- Do System Sizing Calculation
|
||||||
No, !- Do Plant Sizing Calculation
|
No, !- Do Plant Sizing Calculation
|
||||||
No, !- Run Simulation for Sizing Periods
|
Yes, !- Run Simulation for Sizing Periods
|
||||||
Yes, !- Run Simulation for Weather File Run Periods
|
No, !- Run Simulation for Weather File Run Periods
|
||||||
No, !- Do HVAC Sizing Simulation for Sizing Periods
|
No, !- Do HVAC Sizing Simulation for Sizing Periods
|
||||||
1; !- Maximum Number of HVAC Sizing Simulation Passes
|
1; !- Maximum Number of HVAC Sizing Simulation Passes
|
||||||
|
|
||||||
Output:Table:SummaryReports, AnnualBuildingUtilityPerformanceSummary,
|
Output:VariableDictionary,Regular;
|
||||||
DemandEndUseComponentsSummary,
|
|
||||||
SensibleHeatGainSummary,
|
|
||||||
InputVerificationandResultsSummary,
|
|
||||||
AdaptiveComfortSummary,
|
|
||||||
Standard62.1Summary,
|
|
||||||
ClimaticDataSummary,
|
|
||||||
EquipmentSummary,
|
|
||||||
EnvelopeSummary,
|
|
||||||
LightingSummary,
|
|
||||||
HVACSizingSummary,
|
|
||||||
SystemSummary,
|
|
||||||
ComponentSizingSummary,
|
|
||||||
OutdoorAirSummary,
|
|
||||||
ObjectCountSummary,
|
|
||||||
EndUseEnergyConsumptionOtherFuelsMonthly,
|
|
||||||
PeakEnergyEndUseOtherFuelsMonthly;
|
|
||||||
|
|
||||||
|
Output:Variable,*,Site Outdoor Air Drybulb Temperature,Timestep;
|
||||||
|
|
||||||
OutputControl:Table:Style, CommaAndHTML,JtoKWH;
|
Output:Variable,*,Site Outdoor Air Wetbulb Temperature,Timestep;
|
||||||
|
|
||||||
Output:Meter,DISTRICTHEATING:Facility,hourly;
|
Output:Variable,*,Site Outdoor Air Dewpoint Temperature,Timestep;
|
||||||
Output:Meter,DISTRICTCOOLING:Facility,hourly;
|
|
||||||
Output:Meter,InteriorEquipment:Electricity,hourly;
|
Output:Variable,*,Site Solar Azimuth Angle,Timestep;
|
||||||
Output:Meter,InteriorLights:Electricity,hourly;
|
|
||||||
|
Output:Variable,*,Site Solar Altitude Angle,Timestep;
|
||||||
|
|
||||||
|
Output:Variable,*,Site Direct Solar Radiation Rate per Area,Timestep;
|
||||||
|
|
||||||
|
Output:Variable,*,Site Diffuse Solar Radiation Rate per Area,Timestep;
|
||||||
|
|
||||||
|
OutputControl:Table:Style,
|
||||||
|
HTML; !- Column Separator
|
||||||
|
|
||||||
|
Output:Table:SummaryReports,
|
||||||
|
AllSummary; !- Report 1 Name
|
||||||
|
|
||||||
OutputControl:IlluminanceMap:Style,
|
|
||||||
Comma; !- Column separator
|
|
||||||
|
|
157
hub/exports/building_energy/idf_files/Minimal9.5.idf
Normal file
157
hub/exports/building_energy/idf_files/Minimal9.5.idf
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
! Minimal.idf
|
||||||
|
! Basic file description: This is a minimal configuration necessary to run.
|
||||||
|
! Highlights: Illustrates minimal items necessary to perform run.
|
||||||
|
! BUILDING, SURFACEGEOMETRY, LOCATION and DESIGNDAY (or RUNPERIOD) are the absolute minimal required input objects.
|
||||||
|
! TIME STEP IN HOUR is included so as to not get warning error.
|
||||||
|
! Including two design days, Run Control object and RunPeriod to facilitate use.
|
||||||
|
! Although not incredibly useful, this could be used as a weather/solar calculator.
|
||||||
|
! Simulation Location/Run: Denver is included. Any could be used.
|
||||||
|
! Building: None.
|
||||||
|
!
|
||||||
|
! Internal gains description: None.
|
||||||
|
!
|
||||||
|
! HVAC: None.
|
||||||
|
!
|
||||||
|
|
||||||
|
Version,9.5;
|
||||||
|
|
||||||
|
Timestep,4;
|
||||||
|
|
||||||
|
Building,
|
||||||
|
None, !- Name
|
||||||
|
0.0000000E+00, !- North Axis {deg}
|
||||||
|
Suburbs, !- Terrain
|
||||||
|
0.04, !- Loads Convergence Tolerance Value {W}
|
||||||
|
0.40, !- Temperature Convergence Tolerance Value {deltaC}
|
||||||
|
FullInteriorAndExterior, !- Solar Distribution
|
||||||
|
25, !- Maximum Number of Warmup Days
|
||||||
|
6; !- Minimum Number of Warmup Days
|
||||||
|
|
||||||
|
GlobalGeometryRules,
|
||||||
|
UpperLeftCorner, !- Starting Vertex Position
|
||||||
|
CounterClockWise, !- Vertex Entry Direction
|
||||||
|
World; !- Coordinate System
|
||||||
|
|
||||||
|
Site:Location,
|
||||||
|
DENVER_STAPLETON_CO_USA_WMO_724690, !- Name
|
||||||
|
39.77, !- Latitude {deg}
|
||||||
|
-104.87, !- Longitude {deg}
|
||||||
|
-7.00, !- Time Zone {hr}
|
||||||
|
1611.00; !- Elevation {m}
|
||||||
|
|
||||||
|
! DENVER_STAPLETON_CO_USA Annual Heating Design Conditions Wind Speed=2.3m/s Wind Dir=180
|
||||||
|
! Coldest Month=December
|
||||||
|
! DENVER_STAPLETON_CO_USA Annual Heating 99.6%, MaxDB=-20°C
|
||||||
|
|
||||||
|
SizingPeriod:DesignDay,
|
||||||
|
DENVER_STAPLETON Ann Htg 99.6% Condns DB, !- Name
|
||||||
|
12, !- Month
|
||||||
|
21, !- Day of Month
|
||||||
|
WinterDesignDay, !- Day Type
|
||||||
|
-20, !- Maximum Dry-Bulb Temperature {C}
|
||||||
|
0.0, !- Daily Dry-Bulb Temperature Range {deltaC}
|
||||||
|
, !- Dry-Bulb Temperature Range Modifier Type
|
||||||
|
, !- Dry-Bulb Temperature Range Modifier Day Schedule Name
|
||||||
|
Wetbulb, !- Humidity Condition Type
|
||||||
|
-20, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C}
|
||||||
|
, !- Humidity Condition Day Schedule Name
|
||||||
|
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
|
||||||
|
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
|
||||||
|
, !- Daily Wet-Bulb Temperature Range {deltaC}
|
||||||
|
83411., !- Barometric Pressure {Pa}
|
||||||
|
2.3, !- Wind Speed {m/s}
|
||||||
|
180, !- Wind Direction {deg}
|
||||||
|
No, !- Rain Indicator
|
||||||
|
No, !- Snow Indicator
|
||||||
|
No, !- Daylight Saving Time Indicator
|
||||||
|
ASHRAEClearSky, !- Solar Model Indicator
|
||||||
|
, !- Beam Solar Day Schedule Name
|
||||||
|
, !- Diffuse Solar Day Schedule Name
|
||||||
|
, !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless}
|
||||||
|
, !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless}
|
||||||
|
0.00; !- Sky Clearness
|
||||||
|
|
||||||
|
! DENVER_STAPLETON Annual Cooling Design Conditions Wind Speed=4m/s Wind Dir=120
|
||||||
|
! Hottest Month=July
|
||||||
|
! DENVER_STAPLETON_CO_USA Annual Cooling (DB=>MWB) .4%, MaxDB=34.1°C MWB=15.8°C
|
||||||
|
|
||||||
|
SizingPeriod:DesignDay,
|
||||||
|
DENVER_STAPLETON Ann Clg .4% Condns DB=>MWB, !- Name
|
||||||
|
7, !- Month
|
||||||
|
21, !- Day of Month
|
||||||
|
SummerDesignDay, !- Day Type
|
||||||
|
34.1, !- Maximum Dry-Bulb Temperature {C}
|
||||||
|
15.2, !- Daily Dry-Bulb Temperature Range {deltaC}
|
||||||
|
, !- Dry-Bulb Temperature Range Modifier Type
|
||||||
|
, !- Dry-Bulb Temperature Range Modifier Day Schedule Name
|
||||||
|
Wetbulb, !- Humidity Condition Type
|
||||||
|
15.8, !- Wetbulb or DewPoint at Maximum Dry-Bulb {C}
|
||||||
|
, !- Humidity Condition Day Schedule Name
|
||||||
|
, !- Humidity Ratio at Maximum Dry-Bulb {kgWater/kgDryAir}
|
||||||
|
, !- Enthalpy at Maximum Dry-Bulb {J/kg}
|
||||||
|
, !- Daily Wet-Bulb Temperature Range {deltaC}
|
||||||
|
83411., !- Barometric Pressure {Pa}
|
||||||
|
4, !- Wind Speed {m/s}
|
||||||
|
120, !- Wind Direction {deg}
|
||||||
|
No, !- Rain Indicator
|
||||||
|
No, !- Snow Indicator
|
||||||
|
No, !- Daylight Saving Time Indicator
|
||||||
|
ASHRAEClearSky, !- Solar Model Indicator
|
||||||
|
, !- Beam Solar Day Schedule Name
|
||||||
|
, !- Diffuse Solar Day Schedule Name
|
||||||
|
, !- ASHRAE Clear Sky Optical Depth for Beam Irradiance (taub) {dimensionless}
|
||||||
|
, !- ASHRAE Clear Sky Optical Depth for Diffuse Irradiance (taud) {dimensionless}
|
||||||
|
1.00; !- Sky Clearness
|
||||||
|
|
||||||
|
RunPeriod,
|
||||||
|
Run Period 1, !- Name
|
||||||
|
1, !- Begin Month
|
||||||
|
1, !- Begin Day of Month
|
||||||
|
, !- Begin Year
|
||||||
|
12, !- End Month
|
||||||
|
31, !- End Day of Month
|
||||||
|
, !- End Year
|
||||||
|
Tuesday, !- Day of Week for Start Day
|
||||||
|
Yes, !- Use Weather File Holidays and Special Days
|
||||||
|
Yes, !- Use Weather File Daylight Saving Period
|
||||||
|
No, !- Apply Weekend Holiday Rule
|
||||||
|
Yes, !- Use Weather File Rain Indicators
|
||||||
|
Yes; !- Use Weather File Snow Indicators
|
||||||
|
|
||||||
|
SimulationControl,
|
||||||
|
No, !- Do Zone Sizing Calculation
|
||||||
|
No, !- Do System Sizing Calculation
|
||||||
|
No, !- Do Plant Sizing Calculation
|
||||||
|
No, !- Run Simulation for Sizing Periods
|
||||||
|
Yes, !- Run Simulation for Weather File Run Periods
|
||||||
|
No, !- Do HVAC Sizing Simulation for Sizing Periods
|
||||||
|
1; !- Maximum Number of HVAC Sizing Simulation Passes
|
||||||
|
|
||||||
|
Output:Table:SummaryReports, AnnualBuildingUtilityPerformanceSummary,
|
||||||
|
DemandEndUseComponentsSummary,
|
||||||
|
SensibleHeatGainSummary,
|
||||||
|
InputVerificationandResultsSummary,
|
||||||
|
AdaptiveComfortSummary,
|
||||||
|
Standard62.1Summary,
|
||||||
|
ClimaticDataSummary,
|
||||||
|
EquipmentSummary,
|
||||||
|
EnvelopeSummary,
|
||||||
|
LightingSummary,
|
||||||
|
HVACSizingSummary,
|
||||||
|
SystemSummary,
|
||||||
|
ComponentSizingSummary,
|
||||||
|
OutdoorAirSummary,
|
||||||
|
ObjectCountSummary,
|
||||||
|
EndUseEnergyConsumptionOtherFuelsMonthly,
|
||||||
|
PeakEnergyEndUseOtherFuelsMonthly;
|
||||||
|
|
||||||
|
|
||||||
|
OutputControl:Table:Style, CommaAndHTML,JtoKWH;
|
||||||
|
|
||||||
|
Output:Meter,DISTRICTHEATING:Facility,hourly;
|
||||||
|
Output:Meter,DISTRICTCOOLING:Facility,hourly;
|
||||||
|
Output:Meter,InteriorEquipment:Electricity,hourly;
|
||||||
|
Output:Meter,InteriorLights:Electricity,hourly;
|
||||||
|
|
||||||
|
OutputControl:IlluminanceMap:Style,
|
||||||
|
Comma; !- Column separator
|
|
@ -7,9 +7,12 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from hub.exports.formats.glb import Glb
|
||||||
from hub.exports.formats.obj import Obj
|
from hub.exports.formats.obj import Obj
|
||||||
|
from hub.exports.formats.geojson import Geojson
|
||||||
from hub.exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
|
from hub.exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
|
||||||
from hub.exports.formats.stl import Stl
|
from hub.exports.formats.stl import Stl
|
||||||
|
from hub.exports.formats.cesiumjs_tileset import CesiumjsTileset
|
||||||
from hub.helpers.utils import validate_import_export_type
|
from hub.helpers.utils import validate_import_export_type
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +20,7 @@ class ExportsFactory:
|
||||||
"""
|
"""
|
||||||
Exports factory class
|
Exports factory class
|
||||||
"""
|
"""
|
||||||
def __init__(self, handler, city, path, target_buildings=None, adjacent_buildings=None):
|
def __init__(self, handler, city, path, target_buildings=None, adjacent_buildings=None, base_uri=None):
|
||||||
self._city = city
|
self._city = city
|
||||||
self._handler = '_' + handler.lower()
|
self._handler = '_' + handler.lower()
|
||||||
validate_import_export_type(ExportsFactory, handler)
|
validate_import_export_type(ExportsFactory, handler)
|
||||||
|
@ -26,18 +29,7 @@ class ExportsFactory:
|
||||||
self._path = path
|
self._path = path
|
||||||
self._target_buildings = target_buildings
|
self._target_buildings = target_buildings
|
||||||
self._adjacent_buildings = adjacent_buildings
|
self._adjacent_buildings = adjacent_buildings
|
||||||
|
self._base_uri = base_uri
|
||||||
@property
|
|
||||||
def _citygml(self):
|
|
||||||
"""
|
|
||||||
Export to citygml
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _collada(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _stl(self):
|
def _stl(self):
|
||||||
|
@ -61,9 +53,30 @@ class ExportsFactory:
|
||||||
Export the city to Simplified Radiosity Algorithm xml format
|
Export the city to Simplified Radiosity Algorithm xml format
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
return SimplifiedRadiosityAlgorithm(self._city,
|
return SimplifiedRadiosityAlgorithm(
|
||||||
(self._path / f'{self._city.name}_sra.xml'),
|
self._city, (self._path / f'{self._city.name}_sra.xml'), target_buildings=self._target_buildings
|
||||||
target_buildings=self._target_buildings)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cesiumjs_tileset(self):
|
||||||
|
"""
|
||||||
|
Export the city to a cesiumJs tileset format
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
return CesiumjsTileset(
|
||||||
|
self._city,
|
||||||
|
(self._path / f'{self._city.name}.json'),
|
||||||
|
target_buildings=self._target_buildings,
|
||||||
|
base_uri=self._base_uri
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _glb(self):
|
||||||
|
return Glb(self._city, self._path, target_buildings=self._target_buildings)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _geojson(self):
|
||||||
|
return Geojson(self._city, self._path, target_buildings=self._target_buildings)
|
||||||
|
|
||||||
def export(self):
|
def export(self):
|
||||||
"""
|
"""
|
||||||
|
|
159
hub/exports/formats/cesiumjs_tileset.py
Normal file
159
hub/exports/formats/cesiumjs_tileset.py
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
"""
|
||||||
|
export a city into Cesium tileset format
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
|
||||||
|
import pyproj
|
||||||
|
from pyproj import Transformer
|
||||||
|
|
||||||
|
from hub.helpers.geometry_helper import GeometryHelper
|
||||||
|
|
||||||
|
|
||||||
|
class CesiumjsTileset:
|
||||||
|
def __init__(self, city, file_name, target_buildings=None, base_uri=None):
|
||||||
|
self._city = city
|
||||||
|
self._file_name = file_name
|
||||||
|
self._target_buildings = target_buildings
|
||||||
|
if base_uri is None:
|
||||||
|
base_uri = '.'
|
||||||
|
self._base_uri = base_uri
|
||||||
|
try:
|
||||||
|
srs_name = self._city.srs_name
|
||||||
|
if self._city.srs_name in GeometryHelper.srs_transformations:
|
||||||
|
srs_name = GeometryHelper.srs_transformations[self._city.srs_name]
|
||||||
|
input_reference = pyproj.CRS(srs_name) # Projected coordinate system from input data
|
||||||
|
except pyproj.exceptions.CRSError as err:
|
||||||
|
raise pyproj.exceptions.CRSError from err
|
||||||
|
self._to_gps = Transformer.from_crs(input_reference, pyproj.CRS('EPSG:4326'))
|
||||||
|
city_upper_corner = [
|
||||||
|
self._city.upper_corner[0] - self._city.lower_corner[0],
|
||||||
|
self._city.upper_corner[1] - self._city.lower_corner[1],
|
||||||
|
self._city.upper_corner[2] - self._city.lower_corner[2]
|
||||||
|
]
|
||||||
|
city_lower_corner = [0, 0, 0]
|
||||||
|
self._tile_set = {
|
||||||
|
'asset': {
|
||||||
|
'version': '1.1',
|
||||||
|
"tilesetVersion": "1.2.3"
|
||||||
|
},
|
||||||
|
'position': self._to_gps.transform(self._city.lower_corner[0], self._city.lower_corner[1]),
|
||||||
|
'schema': {
|
||||||
|
'id': "building",
|
||||||
|
'classes': {
|
||||||
|
'building': {
|
||||||
|
"properties": {
|
||||||
|
'name': {
|
||||||
|
'type': 'STRING'
|
||||||
|
},
|
||||||
|
'position': {
|
||||||
|
'type': 'SCALAR',
|
||||||
|
'array': True,
|
||||||
|
'componentType': 'FLOAT32'
|
||||||
|
},
|
||||||
|
'aliases': {
|
||||||
|
'type': 'STRING',
|
||||||
|
'array': True,
|
||||||
|
},
|
||||||
|
'volume': {
|
||||||
|
'type': 'SCALAR',
|
||||||
|
'componentType': 'FLOAT32'
|
||||||
|
},
|
||||||
|
'floor_area': {
|
||||||
|
'type': 'SCALAR',
|
||||||
|
'componentType': 'FLOAT32'
|
||||||
|
},
|
||||||
|
'max_height': {
|
||||||
|
'type': 'SCALAR',
|
||||||
|
'componentType': 'INT32'
|
||||||
|
},
|
||||||
|
'year_of_construction': {
|
||||||
|
'type': 'SCALAR',
|
||||||
|
'componentType': 'INT32'
|
||||||
|
},
|
||||||
|
'function': {
|
||||||
|
'type': 'STRING'
|
||||||
|
},
|
||||||
|
'usages_percentage': {
|
||||||
|
'type': 'STRING'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'geometricError': 500,
|
||||||
|
'root': {
|
||||||
|
'boundingVolume': {
|
||||||
|
'box': CesiumjsTileset._box_values(city_upper_corner, city_lower_corner)
|
||||||
|
},
|
||||||
|
'geometricError': 70,
|
||||||
|
'refine': 'ADD',
|
||||||
|
'children': []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self._export()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _box_values(upper_corner, lower_corner):
|
||||||
|
|
||||||
|
x = (upper_corner[0] - lower_corner[0]) / 2
|
||||||
|
x_center = ((upper_corner[0] - lower_corner[0]) / 2) + lower_corner[0]
|
||||||
|
y = (upper_corner[1] - lower_corner[1]) / 2
|
||||||
|
y_center = ((upper_corner[1] - lower_corner[1]) / 2) + lower_corner[1]
|
||||||
|
z = (upper_corner[2] - lower_corner[2]) / 2
|
||||||
|
return [x_center, y_center, z, x, 0, 0, 0, y, 0, 0, 0, z]
|
||||||
|
|
||||||
|
def _ground_coordinates(self, coordinates):
|
||||||
|
ground_coordinates = []
|
||||||
|
for coordinate in coordinates:
|
||||||
|
ground_coordinates.append(
|
||||||
|
(coordinate[0] - self._city.lower_corner[0], coordinate[1] - self._city.lower_corner[1])
|
||||||
|
)
|
||||||
|
return ground_coordinates
|
||||||
|
|
||||||
|
def _export(self):
|
||||||
|
for building in self._city.buildings:
|
||||||
|
upper_corner = [-math.inf, -math.inf, 0]
|
||||||
|
lower_corner = [math.inf, math.inf, 0]
|
||||||
|
lower_corner_coordinates = lower_corner
|
||||||
|
for surface in building.grounds: # todo: maybe we should add the terrain?
|
||||||
|
coordinates = self._ground_coordinates(surface.solid_polygon.coordinates)
|
||||||
|
lower_corner = [min([c[0] for c in coordinates]), min([c[1] for c in coordinates]), 0]
|
||||||
|
lower_corner_coordinates = [
|
||||||
|
min([c[0] for c in surface.solid_polygon.coordinates]),
|
||||||
|
min([c[1] for c in surface.solid_polygon.coordinates]),
|
||||||
|
0
|
||||||
|
]
|
||||||
|
upper_corner = [max([c[0] for c in coordinates]), max([c[1] for c in coordinates]), building.max_height]
|
||||||
|
|
||||||
|
tile = {
|
||||||
|
'boundingVolume': {
|
||||||
|
'box': CesiumjsTileset._box_values(upper_corner, lower_corner)
|
||||||
|
},
|
||||||
|
'geometricError': 250,
|
||||||
|
'metadata': {
|
||||||
|
'class': 'building',
|
||||||
|
'properties': {
|
||||||
|
'name': building.name,
|
||||||
|
'position': self._to_gps.transform(lower_corner_coordinates[0], lower_corner_coordinates[1]),
|
||||||
|
'aliases': building.aliases,
|
||||||
|
'volume': building.volume,
|
||||||
|
'floor_area': building.floor_area,
|
||||||
|
'max_height': building.max_height,
|
||||||
|
'year_of_construction': building.year_of_construction,
|
||||||
|
'function': building.function,
|
||||||
|
'usages_percentage': building.usages_percentage
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'content': {
|
||||||
|
'uri': f'{self._base_uri}/{building.name}.glb'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self._tile_set['root']['children'].append(tile)
|
||||||
|
|
||||||
|
with open(self._file_name, 'w') as f:
|
||||||
|
json.dump(self._tile_set, f, indent=2)
|
112
hub/exports/formats/geojson.py
Normal file
112
hub/exports/formats/geojson.py
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
"""
|
||||||
|
export a city into Geojson format
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pyproj
|
||||||
|
from pyproj import Transformer
|
||||||
|
|
||||||
|
from hub.helpers.geometry_helper import GeometryHelper
|
||||||
|
|
||||||
|
|
||||||
|
class Geojson:
|
||||||
|
"""
|
||||||
|
Export to geojson format
|
||||||
|
"""
|
||||||
|
def __init__(self, city, path, target_buildings):
|
||||||
|
self._city = city
|
||||||
|
self._file_path = Path(path / f'{self._city.name}.geojson').resolve()
|
||||||
|
try:
|
||||||
|
srs_name = self._city.srs_name
|
||||||
|
if self._city.srs_name in GeometryHelper.srs_transformations:
|
||||||
|
srs_name = GeometryHelper.srs_transformations[self._city.srs_name]
|
||||||
|
input_reference = pyproj.CRS(srs_name) # Projected coordinate system from input data
|
||||||
|
except pyproj.exceptions.CRSError as err:
|
||||||
|
raise pyproj.exceptions.CRSError from err
|
||||||
|
self._to_gps = Transformer.from_crs(input_reference, pyproj.CRS('EPSG:4326'))
|
||||||
|
if target_buildings is None:
|
||||||
|
target_buildings = [b.name for b in self._city.buildings]
|
||||||
|
self._geojson_skeleton = {
|
||||||
|
'type': 'FeatureCollection',
|
||||||
|
'features': []
|
||||||
|
}
|
||||||
|
self._feature_skeleton = {
|
||||||
|
'type': 'Feature',
|
||||||
|
'geometry': {
|
||||||
|
'type': 'Polygon',
|
||||||
|
'coordinates': []
|
||||||
|
},
|
||||||
|
'properties': {}
|
||||||
|
}
|
||||||
|
self._export()
|
||||||
|
|
||||||
|
def _export(self):
|
||||||
|
for building in self._city.buildings:
|
||||||
|
if len(building.grounds) == 1:
|
||||||
|
ground = building.grounds[0]
|
||||||
|
feature = self._polygon(ground)
|
||||||
|
else:
|
||||||
|
feature = self._multipolygon(building.grounds)
|
||||||
|
feature['id'] = building.name
|
||||||
|
feature['properties']['height'] = f'{building.max_height - building.lower_corner[2]}'
|
||||||
|
feature['properties']['function'] = f'{building.function}'
|
||||||
|
feature['properties']['year_of_construction'] = f'{building.year_of_construction}'
|
||||||
|
feature['properties']['aliases'] = building.aliases
|
||||||
|
feature['properties']['elevation'] = f'{building.lower_corner[2]}'
|
||||||
|
self._geojson_skeleton['features'].append(feature)
|
||||||
|
with open(self._file_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self._geojson_skeleton, f, indent=2)
|
||||||
|
|
||||||
|
def _polygon(self, ground):
|
||||||
|
feature = {
|
||||||
|
'type': 'Feature',
|
||||||
|
'geometry': {
|
||||||
|
'type': 'Polygon',
|
||||||
|
'coordinates': []
|
||||||
|
},
|
||||||
|
'properties': {}
|
||||||
|
}
|
||||||
|
ground_coordinates = []
|
||||||
|
for coordinate in ground.solid_polygon.coordinates:
|
||||||
|
gps_coordinate = self._to_gps.transform(coordinate[0], coordinate[1])
|
||||||
|
ground_coordinates.insert(0, [gps_coordinate[1], gps_coordinate[0]])
|
||||||
|
|
||||||
|
first_gps_coordinate = self._to_gps.transform(
|
||||||
|
ground.solid_polygon.coordinates[0][0],
|
||||||
|
ground.solid_polygon.coordinates[0][1]
|
||||||
|
)
|
||||||
|
ground_coordinates.insert(0, [first_gps_coordinate[1], first_gps_coordinate[0]])
|
||||||
|
feature['geometry']['coordinates'].append(ground_coordinates)
|
||||||
|
return feature
|
||||||
|
|
||||||
|
def _multipolygon(self, grounds):
|
||||||
|
feature = {
|
||||||
|
'type': 'Feature',
|
||||||
|
'geometry': {
|
||||||
|
'type': 'MultiPolygon',
|
||||||
|
'coordinates': []
|
||||||
|
},
|
||||||
|
'properties': {}
|
||||||
|
}
|
||||||
|
polygons = []
|
||||||
|
for ground in grounds:
|
||||||
|
ground_coordinates = []
|
||||||
|
for coordinate in ground.solid_polygon.coordinates:
|
||||||
|
gps_coordinate = self._to_gps.transform(coordinate[0], coordinate[1])
|
||||||
|
ground_coordinates.insert(0, [gps_coordinate[1], gps_coordinate[0]])
|
||||||
|
|
||||||
|
first_gps_coordinate = self._to_gps.transform(
|
||||||
|
ground.solid_polygon.coordinates[0][0],
|
||||||
|
ground.solid_polygon.coordinates[0][1]
|
||||||
|
)
|
||||||
|
ground_coordinates.insert(0, [first_gps_coordinate[1], first_gps_coordinate[0]])
|
||||||
|
polygons.append(ground_coordinates)
|
||||||
|
feature['geometry']['coordinates'].append(polygons)
|
||||||
|
return feature
|
||||||
|
|
||||||
|
|
54
hub/exports/formats/glb.py
Normal file
54
hub/exports/formats/glb.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
"""
|
||||||
|
export a city into Glb format
|
||||||
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
|
Copyright © 2022 Concordia CERC group
|
||||||
|
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from hub.city_model_structure.city import City
|
||||||
|
from hub.exports.formats.obj import Obj
|
||||||
|
|
||||||
|
|
||||||
|
class GltExceptionError(Exception):
|
||||||
|
"""
|
||||||
|
Glt execution error
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Glb:
|
||||||
|
"""
|
||||||
|
Glb class
|
||||||
|
"""
|
||||||
|
def __init__(self, city, path, target_buildings=None):
|
||||||
|
self._city = city
|
||||||
|
self._path = path
|
||||||
|
if target_buildings is None:
|
||||||
|
target_buildings = [b.name for b in self._city.buildings]
|
||||||
|
self._target_buildings = target_buildings
|
||||||
|
self._export()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _obj2gltf(self):
|
||||||
|
return shutil.which('obj2gltf')
|
||||||
|
|
||||||
|
def _export(self):
|
||||||
|
try:
|
||||||
|
for building in self._city.buildings:
|
||||||
|
city = City(self._city.lower_corner, self._city.upper_corner, self._city.srs_name)
|
||||||
|
city.add_city_object(building)
|
||||||
|
city.name = building.name
|
||||||
|
Obj(city, self._path)
|
||||||
|
glb = f'{self._path}/{building.name}.glb'
|
||||||
|
subprocess.run([
|
||||||
|
self._obj2gltf,
|
||||||
|
'-i', f'{self._path}/{building.name}.obj',
|
||||||
|
'-b',
|
||||||
|
'-o', f'{glb}'
|
||||||
|
])
|
||||||
|
os.unlink(f'{self._path}/{building.name}.obj')
|
||||||
|
os.unlink(f'{self._path}/{building.name}.mtl')
|
||||||
|
except (subprocess.SubprocessError, subprocess.TimeoutExpired, subprocess.CalledProcessError) as err:
|
||||||
|
raise GltExceptionError from err
|
|
@ -26,7 +26,7 @@ class Obj:
|
||||||
|
|
||||||
def _to_vertex(self, coordinate):
|
def _to_vertex(self, coordinate):
|
||||||
x, y, z = self._ground(coordinate)
|
x, y, z = self._ground(coordinate)
|
||||||
return f'v {x} {z} {y}\n'
|
return f'v {x} {z} -{y}\n' # to match opengl expectations
|
||||||
|
|
||||||
def _to_texture_vertex(self, coordinate):
|
def _to_texture_vertex(self, coordinate):
|
||||||
u, v, _ = self._ground(coordinate)
|
u, v, _ = self._ground(coordinate)
|
||||||
|
@ -54,7 +54,7 @@ class Obj:
|
||||||
with open(mtl_file_path, 'w', encoding='utf-8') as mtl:
|
with open(mtl_file_path, 'w', encoding='utf-8') as mtl:
|
||||||
mtl.write("newmtl cerc_base_material\n")
|
mtl.write("newmtl cerc_base_material\n")
|
||||||
mtl.write("Ka 1.0 1.0 1.0 # Ambient color (white)\n")
|
mtl.write("Ka 1.0 1.0 1.0 # Ambient color (white)\n")
|
||||||
mtl.write("Kd 0.3 0.8 0.3 # Diffuse color (greenish)\n")
|
mtl.write("Kd 0.1 0.3 0.1 # Diffuse color (greenish)\n")
|
||||||
mtl.write("Ks 1.0 1.0 1.0 # Specular color (white)\n")
|
mtl.write("Ks 1.0 1.0 1.0 # Specular color (white)\n")
|
||||||
mtl.write("Ns 400.0 # Specular exponent (defines shininess)\n")
|
mtl.write("Ns 400.0 # Specular exponent (defines shininess)\n")
|
||||||
vertices = {}
|
vertices = {}
|
||||||
|
@ -63,12 +63,13 @@ class Obj:
|
||||||
normal_index = 0
|
normal_index = 0
|
||||||
with open(obj_file_path, 'w', encoding='utf-8') as obj:
|
with open(obj_file_path, 'w', encoding='utf-8') as obj:
|
||||||
obj.write("# cerc-hub export\n")
|
obj.write("# cerc-hub export\n")
|
||||||
obj.write(f'mtllib {mtl_name}')
|
obj.write(f'mtllib {mtl_name}\n')
|
||||||
|
|
||||||
for building in self._city.buildings:
|
for building in self._city.buildings:
|
||||||
obj.write(f'# building {building.name}\n')
|
obj.write(f'# building {building.name}\n')
|
||||||
obj.write(f'g {building.name}\n')
|
obj.write(f'g {building.name}\n')
|
||||||
obj.write('s off\n')
|
obj.write('s off\n')
|
||||||
|
|
||||||
for surface in building.surfaces:
|
for surface in building.surfaces:
|
||||||
obj.write(f'# surface {surface.name}\n')
|
obj.write(f'# surface {surface.name}\n')
|
||||||
face = []
|
face = []
|
||||||
|
@ -77,7 +78,6 @@ class Obj:
|
||||||
textures = []
|
textures = []
|
||||||
for coordinate in surface.perimeter_polygon.coordinates:
|
for coordinate in surface.perimeter_polygon.coordinates:
|
||||||
vertex = self._to_vertex(coordinate)
|
vertex = self._to_vertex(coordinate)
|
||||||
|
|
||||||
if vertex not in vertices:
|
if vertex not in vertices:
|
||||||
vertex_index += 1
|
vertex_index += 1
|
||||||
vertices[vertex] = vertex_index
|
vertices[vertex] = vertex_index
|
||||||
|
@ -86,8 +86,7 @@ class Obj:
|
||||||
textures.append(self._to_texture_vertex(coordinate)) # only append if non-existing
|
textures.append(self._to_texture_vertex(coordinate)) # only append if non-existing
|
||||||
else:
|
else:
|
||||||
current = vertices[vertex]
|
current = vertices[vertex]
|
||||||
|
face.append(f'{current}/{current}/{normal_index}') # insert clockwise
|
||||||
face.insert(0, f'{current}/{current}/{normal_index}') # insert counterclockwise
|
|
||||||
obj.writelines(normal) # add the normal
|
obj.writelines(normal) # add the normal
|
||||||
obj.writelines(textures) # add the texture vertex
|
obj.writelines(textures) # add the texture vertex
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ class EilatPhysicsParameters:
|
||||||
layer.thickness = layer_archetype.thickness
|
layer.thickness = layer_archetype.thickness
|
||||||
total_thickness += layer_archetype.thickness
|
total_thickness += layer_archetype.thickness
|
||||||
archetype_material = layer_archetype.material
|
archetype_material = layer_archetype.material
|
||||||
layer.name = archetype_material.name
|
layer.material_name = archetype_material.name
|
||||||
layer.no_mass = archetype_material.no_mass
|
layer.no_mass = archetype_material.no_mass
|
||||||
if archetype_material.no_mass:
|
if archetype_material.no_mass:
|
||||||
layer.thermal_resistance = archetype_material.thermal_resistance
|
layer.thermal_resistance = archetype_material.thermal_resistance
|
||||||
|
|
|
@ -82,7 +82,7 @@ class NrcanPhysicsParameters:
|
||||||
layer = Layer()
|
layer = Layer()
|
||||||
layer.thickness = layer_archetype.thickness
|
layer.thickness = layer_archetype.thickness
|
||||||
archetype_material = layer_archetype.material
|
archetype_material = layer_archetype.material
|
||||||
layer.name = archetype_material.name
|
layer.material_name = archetype_material.name
|
||||||
layer.no_mass = archetype_material.no_mass
|
layer.no_mass = archetype_material.no_mass
|
||||||
if archetype_material.no_mass:
|
if archetype_material.no_mass:
|
||||||
layer.thermal_resistance = archetype_material.thermal_resistance
|
layer.thermal_resistance = archetype_material.thermal_resistance
|
||||||
|
|
|
@ -85,7 +85,7 @@ class NrelPhysicsParameters:
|
||||||
layer = Layer()
|
layer = Layer()
|
||||||
layer.thickness = layer_archetype.thickness
|
layer.thickness = layer_archetype.thickness
|
||||||
archetype_material = layer_archetype.material
|
archetype_material = layer_archetype.material
|
||||||
layer.name = archetype_material.name
|
layer.material_name = archetype_material.name
|
||||||
layer.no_mass = archetype_material.no_mass
|
layer.no_mass = archetype_material.no_mass
|
||||||
if archetype_material.no_mass:
|
if archetype_material.no_mass:
|
||||||
layer.thermal_resistance = archetype_material.thermal_resistance
|
layer.thermal_resistance = archetype_material.thermal_resistance
|
||||||
|
|
|
@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
Copyright © 2022 Concordia CERC group
|
Copyright © 2022 Concordia CERC group
|
||||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
@ -22,16 +23,17 @@ class CityGml:
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
path,
|
path,
|
||||||
extrusion_height_field=None,
|
|
||||||
year_of_construction_field=None,
|
year_of_construction_field=None,
|
||||||
function_field=None,
|
function_field=None,
|
||||||
function_to_hub=None):
|
function_to_hub=None,
|
||||||
|
hub_crs=None):
|
||||||
self._city = None
|
self._city = None
|
||||||
self._lod = None
|
self._lod = None
|
||||||
self._lod1_tags = ['lod1Solid', 'lod1MultiSurface']
|
self._lod1_tags = ['lod1Solid', 'lod1MultiSurface']
|
||||||
self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve']
|
self._lod2_tags = ['lod2Solid', 'lod2MultiSurface', 'lod2MultiCurve']
|
||||||
self._extrusion_height_field = extrusion_height_field
|
|
||||||
self._function_to_hub = function_to_hub
|
self._function_to_hub = function_to_hub
|
||||||
|
if hub_crs is None:
|
||||||
|
hub_crs = 'EPSG:26911'
|
||||||
if function_field is None:
|
if function_field is None:
|
||||||
function_field = 'function'
|
function_field = 'function'
|
||||||
if year_of_construction_field is None:
|
if year_of_construction_field is None:
|
||||||
|
@ -79,7 +81,8 @@ class CityGml:
|
||||||
self._srs_name = envelope['@srsName']
|
self._srs_name = envelope['@srsName']
|
||||||
else:
|
else:
|
||||||
# If not coordinate system given assuming hub standard
|
# If not coordinate system given assuming hub standard
|
||||||
self._srs_name = "EPSG:26911"
|
logging.warning(f'gml file contains no coordinate system assuming {hub_crs}')
|
||||||
|
self._srs_name = hub_crs
|
||||||
else:
|
else:
|
||||||
# get the boundary from the city objects instead
|
# get the boundary from the city objects instead
|
||||||
for city_object_member in self._gml['CityModel']['cityObjectMember']:
|
for city_object_member in self._gml['CityModel']['cityObjectMember']:
|
||||||
|
|
|
@ -49,6 +49,8 @@ class CityGmlLod2(CityGmlBase):
|
||||||
surface_encoding, surface_subtype = cls._surface_encoding(bounded[surface_type])
|
surface_encoding, surface_subtype = cls._surface_encoding(bounded[surface_type])
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
continue
|
continue
|
||||||
|
if 'surfaceMember' not in bounded[surface_type][surface_encoding][surface_subtype]:
|
||||||
|
continue
|
||||||
for member in bounded[surface_type][surface_encoding][surface_subtype]['surfaceMember']:
|
for member in bounded[surface_type][surface_encoding][surface_subtype]['surfaceMember']:
|
||||||
if 'CompositeSurface' in member:
|
if 'CompositeSurface' in member:
|
||||||
for composite_members in member['CompositeSurface']['surfaceMember']:
|
for composite_members in member['CompositeSurface']['surfaceMember']:
|
||||||
|
|
|
@ -34,9 +34,13 @@ class Geojson:
|
||||||
extrusion_height_field=None,
|
extrusion_height_field=None,
|
||||||
year_of_construction_field=None,
|
year_of_construction_field=None,
|
||||||
function_field=None,
|
function_field=None,
|
||||||
function_to_hub=None):
|
function_to_hub=None,
|
||||||
# todo: destination epsg should change according actual the location
|
hub_crs=None
|
||||||
self._transformer = Transformer.from_crs('epsg:4326', 'epsg:26911')
|
):
|
||||||
|
self._hub_crs = hub_crs
|
||||||
|
if hub_crs is None :
|
||||||
|
self._hub_crs = 'epsg:26911'
|
||||||
|
self._transformer = Transformer.from_crs('epsg:4326', self._hub_crs)
|
||||||
self._min_x = cte.MAX_FLOAT
|
self._min_x = cte.MAX_FLOAT
|
||||||
self._min_y = cte.MAX_FLOAT
|
self._min_y = cte.MAX_FLOAT
|
||||||
self._max_x = cte.MIN_FLOAT
|
self._max_x = cte.MIN_FLOAT
|
||||||
|
@ -155,7 +159,7 @@ class Geojson:
|
||||||
extrusion_height))
|
extrusion_height))
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f'Geojson geometry type [{geometry["type"]}] unknown')
|
raise NotImplementedError(f'Geojson geometry type [{geometry["type"]}] unknown')
|
||||||
self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911')
|
self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], self._hub_crs)
|
||||||
for building in buildings:
|
for building in buildings:
|
||||||
# Do not include "small building-like structures" to buildings
|
# Do not include "small building-like structures" to buildings
|
||||||
if building.floor_area >= 25:
|
if building.floor_area >= 25:
|
||||||
|
@ -166,7 +170,6 @@ class Geojson:
|
||||||
if lod > 0:
|
if lod > 0:
|
||||||
lines_information = GeometryHelper.city_mapping(self._city, plot=False)
|
lines_information = GeometryHelper.city_mapping(self._city, plot=False)
|
||||||
self._store_shared_percentage_to_walls(self._city, lines_information)
|
self._store_shared_percentage_to_walls(self._city, lines_information)
|
||||||
|
|
||||||
return self._city
|
return self._city
|
||||||
|
|
||||||
def _polygon_coordinates_to_3d(self, polygon_coordinates):
|
def _polygon_coordinates_to_3d(self, polygon_coordinates):
|
||||||
|
@ -210,8 +213,6 @@ class Geojson:
|
||||||
polygon = Polygon(coordinates)
|
polygon = Polygon(coordinates)
|
||||||
polygon.area = igh.ground_area(coordinates)
|
polygon.area = igh.ground_area(coordinates)
|
||||||
surfaces[-1] = Surface(polygon, polygon)
|
surfaces[-1] = Surface(polygon, polygon)
|
||||||
if len(surfaces) > 1:
|
|
||||||
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)
|
||||||
for alias in building_aliases:
|
for alias in building_aliases:
|
||||||
building.add_alias(alias)
|
building.add_alias(alias)
|
||||||
|
|
|
@ -17,12 +17,13 @@ class GeometryFactory:
|
||||||
GeometryFactory class
|
GeometryFactory class
|
||||||
"""
|
"""
|
||||||
def __init__(self, file_type,
|
def __init__(self, file_type,
|
||||||
path,
|
path=None,
|
||||||
aliases_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,
|
||||||
function_to_hub=None):
|
function_to_hub=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)
|
||||||
self._path = path
|
self._path = path
|
||||||
|
@ -31,6 +32,7 @@ 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._hub_crs = hub_crs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _citygml(self) -> City:
|
def _citygml(self) -> City:
|
||||||
|
@ -39,10 +41,10 @@ class GeometryFactory:
|
||||||
:return: City
|
:return: City
|
||||||
"""
|
"""
|
||||||
return CityGml(self._path,
|
return CityGml(self._path,
|
||||||
self._height_field,
|
|
||||||
self._year_of_construction_field,
|
self._year_of_construction_field,
|
||||||
self._function_field,
|
self._function_field,
|
||||||
self._function_to_hub).city
|
self._function_to_hub,
|
||||||
|
self._hub_crs).city
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _obj(self) -> City:
|
def _obj(self) -> City:
|
||||||
|
@ -63,7 +65,8 @@ class GeometryFactory:
|
||||||
self._height_field,
|
self._height_field,
|
||||||
self._year_of_construction_field,
|
self._year_of_construction_field,
|
||||||
self._function_field,
|
self._function_field,
|
||||||
self._function_to_hub).city
|
self._function_to_hub,
|
||||||
|
self._hub_crs).city
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def city(self) -> City:
|
def city(self) -> City:
|
||||||
|
|
|
@ -70,7 +70,7 @@ class InselMonthlyEnergyBalance:
|
||||||
total_day += value
|
total_day += value
|
||||||
for day_type in schedule.day_types:
|
for day_type in schedule.day_types:
|
||||||
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
|
total_lighting += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
|
||||||
* lighting_density / cte.WATTS_HOUR_TO_JULES
|
* lighting_density * cte.WATTS_HOUR_TO_JULES
|
||||||
lighting_demand.append(total_lighting * area)
|
lighting_demand.append(total_lighting * area)
|
||||||
|
|
||||||
for schedule in thermal_zone.appliances.schedules:
|
for schedule in thermal_zone.appliances.schedules:
|
||||||
|
@ -79,7 +79,7 @@ class InselMonthlyEnergyBalance:
|
||||||
total_day += value
|
total_day += value
|
||||||
for day_type in schedule.day_types:
|
for day_type in schedule.day_types:
|
||||||
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
|
total_appliances += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] \
|
||||||
* appliances_density / cte.WATTS_HOUR_TO_JULES
|
* appliances_density * cte.WATTS_HOUR_TO_JULES
|
||||||
appliances_demand.append(total_appliances * area)
|
appliances_demand.append(total_appliances * area)
|
||||||
|
|
||||||
for schedule in thermal_zone.domestic_hot_water.schedules:
|
for schedule in thermal_zone.domestic_hot_water.schedules:
|
||||||
|
@ -89,7 +89,7 @@ class InselMonthlyEnergyBalance:
|
||||||
for day_type in schedule.day_types:
|
for day_type in schedule.day_types:
|
||||||
demand = (
|
demand = (
|
||||||
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
|
peak_flow * cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY
|
||||||
* (service_temperature - cold_water[i_month]) / cte.WATTS_HOUR_TO_JULES
|
* (service_temperature - cold_water[i_month]) * cte.WATTS_HOUR_TO_JULES
|
||||||
)
|
)
|
||||||
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] * demand
|
total_dhw_demand += total_day * cte.WEEK_DAYS_A_MONTH[month][day_type] * demand
|
||||||
domestic_hot_water_demand.append(total_dhw_demand * area)
|
domestic_hot_water_demand.append(total_dhw_demand * area)
|
||||||
|
|
|
@ -24,7 +24,8 @@ class Weather:
|
||||||
'DE.01.082': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/DEU/DEU_Stuttgart.107380_IWEC/DEU_Stuttgart.107380_IWEC.epw',
|
'DE.01.082': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/DEU/DEU_Stuttgart.107380_IWEC/DEU_Stuttgart.107380_IWEC.epw',
|
||||||
'US.NY.047': 'https://energyplus-weather.s3.amazonaws.com/north_and_central_america_wmo_region_4/USA/NY/USA_NY_New.York.City-Central.Park.94728_TMY/USA_NY_New.York.City-Central.Park.94728_TMY.epw',
|
'US.NY.047': 'https://energyplus-weather.s3.amazonaws.com/north_and_central_america_wmo_region_4/USA/NY/USA_NY_New.York.City-Central.Park.94728_TMY/USA_NY_New.York.City-Central.Park.94728_TMY.epw',
|
||||||
'CA.10.12': 'https://energyplus-weather.s3.amazonaws.com/north_and_central_america_wmo_region_4/CAN/PQ/CAN_PQ_Quebec.717140_CWEC/CAN_PQ_Quebec.717140_CWEC.epw',
|
'CA.10.12': 'https://energyplus-weather.s3.amazonaws.com/north_and_central_america_wmo_region_4/CAN/PQ/CAN_PQ_Quebec.717140_CWEC/CAN_PQ_Quebec.717140_CWEC.epw',
|
||||||
'IL.01.': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/ISR/ISR_Eilat.401990_MSI/ISR_Eilat.401990_MSI.epw'
|
'IL.01.': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/ISR/ISR_Eilat.401990_MSI/ISR_Eilat.401990_MSI.epw',
|
||||||
|
'ES.07.PM': 'https://energyplus-weather.s3.amazonaws.com/europe_wmo_region_6/ESP/ESP_Palma.083060_SWEC/ESP_Palma.083060_SWEC.epw'
|
||||||
}
|
}
|
||||||
# todo: this dictionary need to be completed, a data science student task?
|
# todo: this dictionary need to be completed, a data science student task?
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,8 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
|
||||||
Copyright © 2022 Concordia CERC group
|
Copyright © 2022 Concordia CERC group
|
||||||
Project CoderPeter Yefi peteryefi@gmail.com
|
Project CoderPeter Yefi peteryefi@gmail.com
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
from hub.persistence.repositories.application import Application
|
from hub.persistence.repositories.application import Application
|
||||||
from hub.persistence.repositories.city import City
|
from hub.persistence.repositories.city import City
|
||||||
from hub.persistence.repositories.city_object import CityObject
|
from hub.persistence.repositories.city_object import CityObject
|
||||||
|
@ -75,10 +73,10 @@ class DBControl:
|
||||||
:
|
:
|
||||||
"""
|
"""
|
||||||
cities = self._city.get_by_user_id_application_id_and_scenario(user_id, application_id, scenario)
|
cities = self._city.get_by_user_id_application_id_and_scenario(user_id, application_id, scenario)
|
||||||
for city in cities:
|
c = [c[0].id for c in cities]
|
||||||
result = self.building_info(name, city[0].id)
|
result = self._city_object.building_in_cities_info(name, c)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
return result
|
return result
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def building_info(self, name, city_id) -> CityObject:
|
def building_info(self, name, city_id) -> CityObject:
|
||||||
|
@ -90,17 +88,27 @@ class DBControl:
|
||||||
"""
|
"""
|
||||||
return self._city_object.get_by_name_or_alias_and_city(name, city_id)
|
return self._city_object.get_by_name_or_alias_and_city(name, city_id)
|
||||||
|
|
||||||
def buildings_info(self, request_values, city_id) -> [CityObject]:
|
def building_info_in_cities(self, name, cities) -> CityObject:
|
||||||
|
"""
|
||||||
|
Retrieve the building info from the database
|
||||||
|
:param name: Building name
|
||||||
|
:param cities: [City ID]
|
||||||
|
:return: CityObject
|
||||||
|
"""
|
||||||
|
return self._city_object.get_by_name_or_alias_in_cities(name, cities)
|
||||||
|
|
||||||
|
def buildings_info(self, user_id, application_id, names_or_aliases) -> [CityObject]:
|
||||||
"""
|
"""
|
||||||
Retrieve the buildings info from the database
|
Retrieve the buildings info from the database
|
||||||
:param request_values: Building names
|
:param user_id: User ID
|
||||||
:param city_id: City ID
|
:param application_id: Application ID
|
||||||
|
:param names_or_aliases: A list of names or alias for the buildings
|
||||||
:return: [CityObject]
|
:return: [CityObject]
|
||||||
"""
|
"""
|
||||||
buildings = []
|
results = self._city_object.get_by_name_or_alias_for_user_app(user_id, application_id, names_or_aliases)
|
||||||
for name in request_values['names']:
|
if results is None:
|
||||||
buildings.append(self.building_info(name, city_id))
|
return []
|
||||||
return buildings
|
return results
|
||||||
|
|
||||||
def results(self, user_id, application_id, request_values, result_names=None) -> Dict:
|
def results(self, user_id, application_id, request_values, result_names=None) -> Dict:
|
||||||
"""
|
"""
|
||||||
|
@ -114,10 +122,7 @@ class DBControl:
|
||||||
result_names = []
|
result_names = []
|
||||||
results = {}
|
results = {}
|
||||||
for scenario in request_values['scenarios']:
|
for scenario in request_values['scenarios']:
|
||||||
print('scenario', scenario, results)
|
|
||||||
for scenario_name in scenario.keys():
|
for scenario_name in scenario.keys():
|
||||||
print('scenario name', scenario_name)
|
|
||||||
|
|
||||||
result_sets = self._city.get_by_user_id_application_id_and_scenario(
|
result_sets = self._city.get_by_user_id_application_id_and_scenario(
|
||||||
user_id,
|
user_id,
|
||||||
application_id,
|
application_id,
|
||||||
|
@ -125,25 +130,21 @@ class DBControl:
|
||||||
)
|
)
|
||||||
if result_sets is None:
|
if result_sets is None:
|
||||||
continue
|
continue
|
||||||
for result_set in result_sets:
|
results[scenario_name] = []
|
||||||
city_id = result_set[0].id
|
city_ids = [r[0].id for r in result_sets]
|
||||||
|
for building_name in scenario[scenario_name]:
|
||||||
|
_building = self._city_object.get_by_name_or_alias_in_cities(building_name, city_ids)
|
||||||
|
if _building is None:
|
||||||
|
continue
|
||||||
|
city_object_id = _building.id
|
||||||
|
_ = self._simulation_results.get_simulation_results_by_city_object_id_and_names(
|
||||||
|
city_object_id,
|
||||||
|
result_names)
|
||||||
|
|
||||||
results[scenario_name] = []
|
for value in _:
|
||||||
for building_name in scenario[scenario_name]:
|
values = value.values
|
||||||
_building = self._city_object.get_by_name_or_alias_and_city(building_name, city_id)
|
values["building"] = building_name
|
||||||
if _building is None:
|
results[scenario_name].append(values)
|
||||||
continue
|
|
||||||
city_object_id = _building.id
|
|
||||||
_ = self._simulation_results.get_simulation_results_by_city_id_city_object_id_and_names(
|
|
||||||
city_id,
|
|
||||||
city_object_id,
|
|
||||||
result_names)
|
|
||||||
|
|
||||||
for value in _:
|
|
||||||
values = json.loads(value.values)
|
|
||||||
values["building"] = building_name
|
|
||||||
results[scenario_name].append(values)
|
|
||||||
print(scenario, results)
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int):
|
def persist_city(self, city: City, pickle_path, scenario, application_id: int, user_id: int):
|
||||||
|
|
|
@ -7,9 +7,10 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence
|
from sqlalchemy import Column, Integer, String, Sequence
|
||||||
from sqlalchemy import DateTime
|
from sqlalchemy import DateTime
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
|
||||||
from hub.persistence.configuration import Models
|
from hub.persistence.configuration import Models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@ class City(Models):
|
||||||
pickle_path = Column(String, nullable=False)
|
pickle_path = Column(String, nullable=False)
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False)
|
||||||
scenario = Column(String, nullable=False)
|
scenario = Column(String, nullable=False)
|
||||||
application_id = Column(Integer, ForeignKey('application.id'), nullable=False)
|
application_id = Column(Integer, ForeignKey('application.id', ondelete='CASCADE'), nullable=False)
|
||||||
user_id = Column(Integer, ForeignKey('user.id'), nullable=True)
|
user_id = Column(Integer, ForeignKey('user.id', ondelete='CASCADE'), nullable=True)
|
||||||
hub_release = Column(String, nullable=False)
|
hub_release = Column(String, nullable=False)
|
||||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
updated = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
|
@ -21,7 +21,7 @@ class CityObject(Models):
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'city_object'
|
__tablename__ = 'city_object'
|
||||||
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', ondelete='CASCADE'), nullable=False)
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False)
|
||||||
aliases = Column(String, nullable=True)
|
aliases = Column(String, nullable=True)
|
||||||
type = Column(String, nullable=False)
|
type = Column(String, nullable=False)
|
||||||
|
|
|
@ -19,8 +19,8 @@ class SimulationResults(Models):
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'simulation_results'
|
__tablename__ = 'simulation_results'
|
||||||
id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True)
|
id = Column(Integer, Sequence('simulation_results_id_seq'), primary_key=True)
|
||||||
city_id = Column(Integer, ForeignKey('city.id'), nullable=True)
|
city_id = Column(Integer, ForeignKey('city.id', ondelete='CASCADE'), nullable=True)
|
||||||
city_object_id = Column(Integer, ForeignKey('city_object.id'), nullable=True)
|
city_object_id = Column(Integer, ForeignKey('city_object.id', ondelete='CASCADE'), nullable=True)
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False)
|
||||||
values = Column(JSONB, nullable=False)
|
values = Column(JSONB, nullable=False)
|
||||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import logging
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
from hub.persistence.repository import Repository
|
from hub.persistence.repository import Repository
|
||||||
from hub.persistence.models import Application as Model
|
from hub.persistence.models import Application as Model
|
||||||
|
@ -48,10 +49,11 @@ class Application(Repository):
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
application = Model(name=name, description=description, application_uuid=application_uuid)
|
application = Model(name=name, description=description, application_uuid=application_uuid)
|
||||||
self.session.add(application)
|
with Session(self.engine) as session:
|
||||||
self.session.commit()
|
session.add(application)
|
||||||
self.session.refresh(application)
|
session.commit()
|
||||||
return application.id
|
session.refresh(application)
|
||||||
|
return application.id
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('An error occurred while creating application %s', err)
|
logging.error('An error occurred while creating application %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -65,10 +67,11 @@ class Application(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.session.query(Model).filter(
|
with Session(self.engine) as session:
|
||||||
Model.application_uuid == application_uuid
|
session.query(Model).filter(
|
||||||
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
|
Model.application_uuid == application_uuid
|
||||||
self.session.commit()
|
).update({'name': name, 'description': description, 'updated': datetime.datetime.utcnow()})
|
||||||
|
session.commit()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while updating application %s', err)
|
logging.error('Error while updating application %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -80,9 +83,10 @@ class Application(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.session.query(Model).filter(Model.application_uuid == application_uuid).delete()
|
with Session(self.engine) as session:
|
||||||
self.session.flush()
|
session.query(Model).filter(Model.application_uuid == application_uuid).delete()
|
||||||
self.session.commit()
|
session.flush()
|
||||||
|
session.commit()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while deleting application %s', err)
|
logging.error('Error while deleting application %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -94,10 +98,11 @@ class Application(Repository):
|
||||||
:return: Application with the provided application_uuid
|
:return: Application with the provided application_uuid
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result_set = self.session.execute(select(Model).where(
|
with Session(self.engine) as session:
|
||||||
Model.application_uuid == application_uuid)
|
result_set = session.execute(select(Model).where(
|
||||||
).first()
|
Model.application_uuid == application_uuid)
|
||||||
return result_set[0]
|
).first()
|
||||||
|
return result_set[0]
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching application by application_uuid %s', err)
|
logging.error('Error while fetching application by application_uuid %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
|
|
@ -54,18 +54,18 @@ class City(Repository):
|
||||||
application_id,
|
application_id,
|
||||||
user_id,
|
user_id,
|
||||||
__version__)
|
__version__)
|
||||||
|
with Session(self.engine) as session:
|
||||||
self.session.add(db_city)
|
session.add(db_city)
|
||||||
self.session.flush()
|
session.flush()
|
||||||
self.session.commit()
|
session.commit()
|
||||||
for building in city.buildings:
|
for building in city.buildings:
|
||||||
db_city_object = CityObject(db_city.id,
|
db_city_object = CityObject(db_city.id,
|
||||||
building)
|
building)
|
||||||
self.session.add(db_city_object)
|
session.add(db_city_object)
|
||||||
self.session.flush()
|
session.flush()
|
||||||
self.session.commit()
|
session.commit()
|
||||||
self.session.refresh(db_city)
|
session.refresh(db_city)
|
||||||
return db_city.id
|
return db_city.id
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('An error occurred while creating a city %s', err)
|
logging.error('An error occurred while creating a city %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -79,8 +79,9 @@ class City(Repository):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
self.session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
|
with Session(self.engine) as session:
|
||||||
self.session.commit()
|
session.query(Model).filter(Model.id == city_id).update({'name': city.name, 'updated': now})
|
||||||
|
session.commit()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while updating city %s', err)
|
logging.error('Error while updating city %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -92,9 +93,10 @@ class City(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.session.query(CityObject).filter(CityObject.city_id == city_id).delete()
|
with Session(self.engine) as session:
|
||||||
self.session.query(Model).filter(Model.id == city_id).delete()
|
session.query(CityObject).filter(CityObject.city_id == city_id).delete()
|
||||||
self.session.commit()
|
session.query(Model).filter(Model.id == city_id).delete()
|
||||||
|
session.commit()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching city %s', err)
|
logging.error('Error while fetching city %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -108,13 +110,12 @@ class City(Repository):
|
||||||
:return: [ModelCity]
|
:return: [ModelCity]
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result_set = self.session.execute(select(Model).where(Model.user_id == user_id,
|
with Session(self.engine) as session:
|
||||||
Model.application_id == application_id,
|
result_set = session.execute(select(Model).where(Model.user_id == user_id,
|
||||||
Model.scenario == scenario
|
Model.application_id == application_id,
|
||||||
)).all()
|
Model.scenario == scenario
|
||||||
self.session.close()
|
)).all()
|
||||||
self.session = Session(self.engine)
|
return result_set
|
||||||
return result_set
|
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching city by name %s', err)
|
logging.error('Error while fetching city by name %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -127,11 +128,11 @@ class City(Repository):
|
||||||
:return: ModelCity
|
:return: ModelCity
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result_set = self.session.execute(
|
with Session(self.engine) as session:
|
||||||
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
|
result_set = session.execute(
|
||||||
)
|
select(Model).where(Model.user_id == user_id, Model.application_id == application_id)
|
||||||
return [r[0] for r in result_set]
|
)
|
||||||
|
return [r[0] for r in result_set]
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching city by name %s', err)
|
logging.error('Error while fetching city by name %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,16 @@ Project Coder Guille Gutierrez Guillermo.GutierrezMorote@concordia.ca
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from hub.city_model_structure.building import Building
|
from hub.city_model_structure.building import Building
|
||||||
from hub.persistence.repository import Repository
|
|
||||||
from hub.persistence.models import CityObject as Model
|
from hub.persistence.models import CityObject as Model
|
||||||
|
from hub.persistence.models import City as CityModel
|
||||||
|
from hub.persistence.repository import Repository
|
||||||
|
|
||||||
|
|
||||||
class CityObject(Repository):
|
class CityObject(Repository):
|
||||||
|
@ -46,10 +48,11 @@ class CityObject(Repository):
|
||||||
try:
|
try:
|
||||||
city_object = Model(city_id=city_id,
|
city_object = Model(city_id=city_id,
|
||||||
building=building)
|
building=building)
|
||||||
self.session.add(city_object)
|
with Session(self.engine) as session:
|
||||||
self.session.flush()
|
session.add(city_object)
|
||||||
self.session.commit()
|
session.flush()
|
||||||
self.session.refresh(city_object)
|
session.commit()
|
||||||
|
session.refresh(city_object)
|
||||||
return city_object.id
|
return city_object.id
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('An error occurred while creating city_object %s', err)
|
logging.error('An error occurred while creating city_object %s', err)
|
||||||
|
@ -68,17 +71,18 @@ class CityObject(Repository):
|
||||||
for usage in internal_zone.usages:
|
for usage in internal_zone.usages:
|
||||||
object_usage = f'{object_usage}{usage.name}_{usage.percentage} '
|
object_usage = f'{object_usage}{usage.name}_{usage.percentage} '
|
||||||
object_usage = object_usage.rstrip()
|
object_usage = object_usage.rstrip()
|
||||||
self.session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
|
with Session(self.engine) as session:
|
||||||
{'name': building.name,
|
session.query(Model).filter(Model.name == building.name, Model.city_id == city_id).update(
|
||||||
'alias': building.alias,
|
{'name': building.name,
|
||||||
'object_type': building.type,
|
'aliases': building.aliases,
|
||||||
'year_of_construction': building.year_of_construction,
|
'object_type': building.type,
|
||||||
'function': building.function,
|
'year_of_construction': building.year_of_construction,
|
||||||
'usage': object_usage,
|
'function': building.function,
|
||||||
'volume': building.volume,
|
'usage': object_usage,
|
||||||
'area': building.floor_area,
|
'volume': building.volume,
|
||||||
'updated': datetime.datetime.utcnow()})
|
'area': building.floor_area,
|
||||||
self.session.commit()
|
'updated': datetime.datetime.utcnow()})
|
||||||
|
session.commit()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while updating city object %s', err)
|
logging.error('Error while updating city object %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -91,30 +95,116 @@ class CityObject(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
|
with Session(self.engine) as session:
|
||||||
self.session.commit()
|
session.query(Model).filter(Model.city_id == city_id, Model.name == name).delete()
|
||||||
|
session.commit()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while deleting application %s', err)
|
logging.error('Error while deleting application %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
def get_by_name_or_alias_and_city(self, name, city_id) -> Model:
|
def building_in_cities_info(self, name, cities):
|
||||||
"""
|
"""
|
||||||
Fetch a city object based on name and city id
|
Fetch a city object based on name and city id
|
||||||
:param name: city object name
|
:param name: city object name
|
||||||
:param city_id: a city identifier
|
:param cities: city identifiers
|
||||||
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
|
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# search by name first
|
# search by name first
|
||||||
city_object = self.session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
|
with Session(self.engine) as session:
|
||||||
if city_object is not None:
|
city_object = session.execute(select(Model).where(
|
||||||
return city_object[0]
|
Model.name == name, Model.city_id.in_(cities))
|
||||||
# name not found, so search by alias instead
|
).first()
|
||||||
city_objects = self.session.execute(
|
if city_object is not None:
|
||||||
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
|
return city_object[0]
|
||||||
).all()
|
# name not found, so search by alias instead
|
||||||
self.session.close()
|
city_objects = session.execute(
|
||||||
self.session = Session(self.engine)
|
select(Model).where(Model.aliases.contains(name), Model.city_id.in_(cities))
|
||||||
|
).all()
|
||||||
|
for city_object in city_objects:
|
||||||
|
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
||||||
|
for alias in aliases:
|
||||||
|
if alias == name:
|
||||||
|
# force the name as the alias
|
||||||
|
city_object[0].name = name
|
||||||
|
return city_object[0]
|
||||||
|
return None
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city object by name and city: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
except IndexError as err:
|
||||||
|
logging.error('Error while fetching city object by name and city, empty result %s', err)
|
||||||
|
raise IndexError from err
|
||||||
|
|
||||||
|
def get_by_name_or_alias_for_user_app(self, user_id, application_id, names) -> Union[Model, None]:
|
||||||
|
"""
|
||||||
|
Fetch city objects belonging to the user and application where the name or alias is in the names list
|
||||||
|
:param user_id: User ID
|
||||||
|
:param application_id: Application ID
|
||||||
|
:param names: a list of building aliases or names
|
||||||
|
:return [CityObject] or None
|
||||||
|
"""
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
cities = session.execute(select(CityModel).where(
|
||||||
|
CityModel.user_id == user_id, CityModel.application_id == application_id
|
||||||
|
)).all()
|
||||||
|
ids = [c[0].id for c in cities]
|
||||||
|
buildings = session.execute(select(Model).where(
|
||||||
|
Model.city_id.in_(ids), Model.name.in_(names)
|
||||||
|
))
|
||||||
|
results = [r[0] for r in buildings]
|
||||||
|
print(ids, buildings)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_by_name_or_alias_and_city(self, name, city_id) -> Union[Model, None]:
|
||||||
|
"""
|
||||||
|
Fetch a city object based on name and city id
|
||||||
|
:param name: city object name
|
||||||
|
:param city_id: a city identifier
|
||||||
|
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# search by name first
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id == city_id)).first()
|
||||||
|
if city_object is not None:
|
||||||
|
return city_object[0]
|
||||||
|
# name not found, so search by alias instead
|
||||||
|
city_objects = session.execute(
|
||||||
|
select(Model).where(Model.aliases.contains(name), Model.city_id == city_id)
|
||||||
|
).all()
|
||||||
|
for city_object in city_objects:
|
||||||
|
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
||||||
|
for alias in aliases:
|
||||||
|
if alias == name:
|
||||||
|
# force the name as the alias
|
||||||
|
city_object[0].name = name
|
||||||
|
return city_object[0]
|
||||||
|
return None
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city object by name and city: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
||||||
|
except IndexError as err:
|
||||||
|
logging.error('Error while fetching city object by name and city, empty result %s', err)
|
||||||
|
raise IndexError from err
|
||||||
|
|
||||||
|
def get_by_name_or_alias_in_cities(self, name, city_ids) -> Model:
|
||||||
|
"""
|
||||||
|
Fetch a city object based on name and city ids
|
||||||
|
:param name: city object name
|
||||||
|
:param city_ids: a list of city identifiers
|
||||||
|
:return: [CityObject] with the provided name or alias belonging to the city with id city_id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# search by name first
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
city_object = session.execute(select(Model).where(Model.name == name, Model.city_id.in_(tuple(city_ids)))).first()
|
||||||
|
if city_object is not None:
|
||||||
|
return city_object[0]
|
||||||
|
# name not found, so search by alias instead
|
||||||
|
city_objects = session.execute(
|
||||||
|
select(Model).where(Model.aliases.contains(name), Model.city_id.in_(tuple(city_ids)))
|
||||||
|
).all()
|
||||||
for city_object in city_objects:
|
for city_object in city_objects:
|
||||||
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
aliases = city_object[0].aliases.replace('{', '').replace('}', '').split(',')
|
||||||
for alias in aliases:
|
for alias in aliases:
|
||||||
|
|
|
@ -10,6 +10,7 @@ import logging
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from hub.persistence.repository import Repository
|
from hub.persistence.repository import Repository
|
||||||
from hub.persistence.models import City
|
from hub.persistence.models import City
|
||||||
|
@ -52,11 +53,12 @@ class SimulationResults(Repository):
|
||||||
values=values,
|
values=values,
|
||||||
city_id=city_id,
|
city_id=city_id,
|
||||||
city_object_id=city_object_id)
|
city_object_id=city_object_id)
|
||||||
self.session.add(simulation_result)
|
with Session(self.engine) as session:
|
||||||
self.session.flush()
|
session.add(simulation_result)
|
||||||
self.session.commit()
|
session.flush()
|
||||||
self.session.refresh(simulation_result)
|
session.commit()
|
||||||
return simulation_result.id
|
session.refresh(simulation_result)
|
||||||
|
return simulation_result.id
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('An error occurred while creating city_object %s', err)
|
logging.error('An error occurred while creating city_object %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -71,22 +73,23 @@ class SimulationResults(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if city_id is not None:
|
with Session(self.engine) as session:
|
||||||
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
|
if city_id is not None:
|
||||||
|
session.query(Model).filter(Model.name == name, Model.city_id == city_id).update(
|
||||||
{
|
{
|
||||||
'values': values,
|
'values': values,
|
||||||
'updated': datetime.datetime.utcnow()
|
'updated': datetime.datetime.utcnow()
|
||||||
})
|
})
|
||||||
self.session.commit()
|
session.commit()
|
||||||
elif city_object_id is not None:
|
elif city_object_id is not None:
|
||||||
self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
|
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).update(
|
||||||
{
|
{
|
||||||
'values': values,
|
'values': values,
|
||||||
'updated': datetime.datetime.utcnow()
|
'updated': datetime.datetime.utcnow()
|
||||||
})
|
})
|
||||||
self.session.commit()
|
session.commit()
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Missing either city_id or city_object_id')
|
raise NotImplementedError('Missing either city_id or city_object_id')
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while updating city object %s', err)
|
logging.error('Error while updating city object %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -100,14 +103,15 @@ class SimulationResults(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if city_id is not None:
|
with Session(self.engine) as session:
|
||||||
self.session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
|
if city_id is not None:
|
||||||
self.session.commit()
|
session.query(Model).filter(Model.name == name, Model.city_id == city_id).delete()
|
||||||
elif city_object_id is not None:
|
session.commit()
|
||||||
self.session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
|
elif city_object_id is not None:
|
||||||
self.session.commit()
|
session.query(Model).filter(Model.name == name, Model.city_object_id == city_object_id).delete()
|
||||||
else:
|
session.commit()
|
||||||
raise NotImplementedError('Missing either city_id or city_object_id')
|
else:
|
||||||
|
raise NotImplementedError('Missing either city_id or city_object_id')
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while deleting application: %s', err)
|
logging.error('Error while deleting application: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -119,7 +123,8 @@ class SimulationResults(Repository):
|
||||||
:return: [City] with the provided city_id
|
:return: [City] with the provided city_id
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.session.execute(select(City).where(City.id == city_id)).first()
|
with Session(self.engine) as session:
|
||||||
|
return session.execute(select(City).where(City.id == city_id)).first()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching city by city_id: %s', err)
|
logging.error('Error while fetching city by city_id: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -131,7 +136,8 @@ class SimulationResults(Repository):
|
||||||
:return: [CityObject] with the provided city_object_id
|
:return: [CityObject] with the provided city_object_id
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.session.execute(select(CityObject).where(CityObject.id == city_object_id)).first()
|
with Session(self.engine) as session:
|
||||||
|
return session.execute(select(CityObject).where(CityObject.id == city_object_id)).first()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching city by city_id: %s', err)
|
logging.error('Error while fetching city by city_id: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -145,18 +151,43 @@ class SimulationResults(Repository):
|
||||||
:return: [SimulationResult]
|
:return: [SimulationResult]
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
result_set = self.session.execute(select(Model).where(or_(
|
with Session(self.engine) as session:
|
||||||
Model.city_id == city_id,
|
result_set = session.execute(select(Model).where(or_(
|
||||||
Model.city_object_id == city_object_id
|
Model.city_id == city_id,
|
||||||
)))
|
Model.city_object_id == city_object_id
|
||||||
results = [r[0] for r in result_set]
|
)))
|
||||||
if not result_names:
|
results = [r[0] for r in result_set]
|
||||||
return results
|
if not result_names:
|
||||||
filtered_results = []
|
return results
|
||||||
for result in results:
|
filtered_results = []
|
||||||
if result.name in result_names:
|
for result in results:
|
||||||
filtered_results.append(result)
|
if result.name in result_names:
|
||||||
return filtered_results
|
filtered_results.append(result)
|
||||||
|
return filtered_results
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching city by city_id: %s', err)
|
logging.error('Error while fetching city by city_id: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
|
||||||
|
def get_simulation_results_by_city_object_id_and_names(self, city_object_id, result_names=None) -> [Model]:
|
||||||
|
"""
|
||||||
|
Fetch the simulation results based in the city_object_id with the given names or all
|
||||||
|
:param city_object_id: the city object id
|
||||||
|
:param result_names: if given filter the results
|
||||||
|
:return: [SimulationResult]
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
result_set = session.execute(select(Model).where(
|
||||||
|
Model.city_object_id == city_object_id
|
||||||
|
))
|
||||||
|
results = [r[0] for r in result_set]
|
||||||
|
if not result_names:
|
||||||
|
return results
|
||||||
|
filtered_results = []
|
||||||
|
for result in results:
|
||||||
|
if result.name in result_names:
|
||||||
|
filtered_results.append(result)
|
||||||
|
return filtered_results
|
||||||
|
except SQLAlchemyError as err:
|
||||||
|
logging.error('Error while fetching city by city_id: %s', err)
|
||||||
|
raise SQLAlchemyError from err
|
|
@ -9,6 +9,7 @@ import logging
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from hub.helpers.auth import Auth
|
from hub.helpers.auth import Auth
|
||||||
from hub.persistence.repository import Repository
|
from hub.persistence.repository import Repository
|
||||||
|
@ -49,10 +50,11 @@ class User(Repository):
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id)
|
user = Model(name=name, password=Auth.hash_password(password), role=role, application_id=application_id)
|
||||||
self.session.add(user)
|
with Session(self.engine) as session:
|
||||||
self.session.flush()
|
session.add(user)
|
||||||
self.session.commit()
|
session.flush()
|
||||||
self.session.refresh(user)
|
session.commit()
|
||||||
|
session.refresh(user)
|
||||||
return user.id
|
return user.id
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('An error occurred while creating user %s', err)
|
logging.error('An error occurred while creating user %s', err)
|
||||||
|
@ -68,13 +70,14 @@ class User(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.session.query(Model).filter(Model.id == user_id).update({
|
with Session(self.engine) as session:
|
||||||
'name': name,
|
session.query(Model).filter(Model.id == user_id).update({
|
||||||
'password': Auth.hash_password(password),
|
'name': name,
|
||||||
'role': role,
|
'password': Auth.hash_password(password),
|
||||||
'updated': datetime.datetime.utcnow()
|
'role': role,
|
||||||
})
|
'updated': datetime.datetime.utcnow()
|
||||||
self.session.commit()
|
})
|
||||||
|
session.commit()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while updating user: %s', err)
|
logging.error('Error while updating user: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -86,8 +89,9 @@ class User(Repository):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.session.query(Model).filter(Model.id == user_id).delete()
|
with Session(self.engine) as session:
|
||||||
self.session.commit()
|
session.query(Model).filter(Model.id == user_id).delete()
|
||||||
|
session.commit()
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching user: %s', err)
|
logging.error('Error while fetching user: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -100,10 +104,12 @@ class User(Repository):
|
||||||
:return: User matching the search criteria or None
|
:return: User matching the search criteria or None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
user = self.session.execute(
|
with Session(self.engine) as session:
|
||||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
user = session.execute(
|
||||||
).first()
|
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||||
return user[0]
|
).first()
|
||||||
|
session.commit()
|
||||||
|
return user[0]
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching user by name and application: %s', err)
|
logging.error('Error while fetching user by name and application: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -120,12 +126,13 @@ class User(Repository):
|
||||||
:return: User
|
:return: User
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
user = self.session.execute(
|
with Session(self.engine) as session:
|
||||||
select(Model).where(Model.name == name, Model.application_id == application_id)
|
user = session.execute(
|
||||||
).first()
|
select(Model).where(Model.name == name, Model.application_id == application_id)
|
||||||
if user:
|
).first()
|
||||||
if Auth.check_password(password, user[0].password):
|
if user:
|
||||||
return user[0]
|
if Auth.check_password(password, user[0].password):
|
||||||
|
return user[0]
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching user by name: %s', err)
|
logging.error('Error while fetching user by name: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
@ -140,10 +147,11 @@ class User(Repository):
|
||||||
:return: User
|
:return: User
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
application = self.session.execute(
|
with Session(self.engine) as session:
|
||||||
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
|
application = session.execute(
|
||||||
).first()
|
select(ApplicationModel).where(ApplicationModel.application_uuid == application_uuid)
|
||||||
return self.get_by_name_application_id_and_password(name, password, application[0].id)
|
).first()
|
||||||
|
return self.get_by_name_application_id_and_password(name, password, application[0].id)
|
||||||
except SQLAlchemyError as err:
|
except SQLAlchemyError as err:
|
||||||
logging.error('Error while fetching user by name: %s', err)
|
logging.error('Error while fetching user by name: %s', err)
|
||||||
raise SQLAlchemyError from err
|
raise SQLAlchemyError from err
|
||||||
|
|
|
@ -6,7 +6,6 @@ Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
from hub.persistence.configuration import Configuration
|
from hub.persistence.configuration import Configuration
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +18,5 @@ class Repository:
|
||||||
try:
|
try:
|
||||||
self.configuration = Configuration(db_name, dotenv_path, app_env)
|
self.configuration = Configuration(db_name, dotenv_path, app_env)
|
||||||
self.engine = create_engine(self.configuration.connection_string)
|
self.engine = create_engine(self.configuration.connection_string)
|
||||||
self.session = Session(self.engine)
|
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
logging.error('Missing value for credentials: %s', err)
|
logging.error('Missing value for credentials: %s', err)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""
|
"""
|
||||||
Hub version number
|
Hub version number
|
||||||
"""
|
"""
|
||||||
__version__ = '0.1.8.11'
|
__version__ = '0.1.8.39'
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -100,6 +100,7 @@ setup(
|
||||||
('hub/data/geolocation', glob.glob('hub/data/geolocation/*.txt')),
|
('hub/data/geolocation', glob.glob('hub/data/geolocation/*.txt')),
|
||||||
('hub/data/greenery', glob.glob('hub/data/greenery/*.xml')),
|
('hub/data/greenery', glob.glob('hub/data/greenery/*.xml')),
|
||||||
('hub/data/usage', glob.glob('hub/data/usage/*.xml')),
|
('hub/data/usage', glob.glob('hub/data/usage/*.xml')),
|
||||||
|
('hub/data/usage', glob.glob('hub/data/usage/*.json')),
|
||||||
('hub/data/usage', glob.glob('hub/data/usage/*.xlsx')),
|
('hub/data/usage', glob.glob('hub/data/usage/*.xlsx')),
|
||||||
('hub/data/weather', glob.glob('hub/data/weather/*.dat')),
|
('hub/data/weather', glob.glob('hub/data/weather/*.dat')),
|
||||||
('hub/data/weather/epw', glob.glob('hub/data/weather/epw/*.epw')),
|
('hub/data/weather/epw', glob.glob('hub/data/weather/epw/*.epw')),
|
||||||
|
|
|
@ -6,7 +6,6 @@ Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
"""
|
"""
|
||||||
import distutils.spawn
|
import distutils.spawn
|
||||||
import glob
|
import glob
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -103,16 +102,16 @@ class Control:
|
||||||
app_env='TEST',
|
app_env='TEST',
|
||||||
dotenv_path=dotenv_path)
|
dotenv_path=dotenv_path)
|
||||||
|
|
||||||
self._application_uuid = '60b7fc1b-f389-4254-9ffd-22a4cf32c7a3'
|
self._application_uuid = 'b9e0ce80-1218-410c-8a64-9d9b7026aad8'
|
||||||
self._application_id = 1
|
self._application_id = 1
|
||||||
self._user_id = 1
|
self._user_id = 1
|
||||||
|
|
||||||
self._application_id = self._database.persist_application(
|
self._application_id = self._database.persist_application(
|
||||||
'City_layers',
|
'test',
|
||||||
'City layers test user',
|
'test',
|
||||||
self.application_uuid
|
self.application_uuid
|
||||||
)
|
)
|
||||||
self._user_id = self._database.create_user('city_layers', self._application_id, 'city_layers', UserRoles.Admin)
|
self._user_id = self._database.create_user('test', self._application_id, 'test', UserRoles.Admin)
|
||||||
|
|
||||||
self._pickle_path = Path('tests_data/pickle_path.bz2').resolve()
|
self._pickle_path = Path('tests_data/pickle_path.bz2').resolve()
|
||||||
|
|
||||||
|
@ -248,36 +247,36 @@ TestDBFactory
|
||||||
for x in building.onsite_electrical_production[cte.MONTH]]
|
for x in building.onsite_electrical_production[cte.MONTH]]
|
||||||
yearly_on_site_electrical_production = [x * cte.WATTS_HOUR_TO_JULES
|
yearly_on_site_electrical_production = [x * cte.WATTS_HOUR_TO_JULES
|
||||||
for x in building.onsite_electrical_production[cte.YEAR]]
|
for x in building.onsite_electrical_production[cte.YEAR]]
|
||||||
results = json.dumps({cte.INSEL_MEB: [
|
results = {cte.INSEL_MEB: {
|
||||||
{'monthly_cooling_peak_load': monthly_cooling_peak_load},
|
'monthly_cooling_peak_load': monthly_cooling_peak_load,
|
||||||
{'yearly_cooling_peak_load': yearly_cooling_peak_load},
|
'yearly_cooling_peak_load': yearly_cooling_peak_load,
|
||||||
{'monthly_heating_peak_load': monthly_heating_peak_load},
|
'monthly_heating_peak_load': monthly_heating_peak_load,
|
||||||
{'yearly_heating_peak_load': yearly_heating_peak_load},
|
'yearly_heating_peak_load': yearly_heating_peak_load,
|
||||||
{'monthly_lighting_peak_load': monthly_lighting_peak_load},
|
'monthly_lighting_peak_load': monthly_lighting_peak_load,
|
||||||
{'yearly_lighting_peak_load': yearly_lighting_peak_load},
|
'yearly_lighting_peak_load': yearly_lighting_peak_load,
|
||||||
{'monthly_appliances_peak_load': monthly_appliances_peak_load},
|
'monthly_appliances_peak_load': monthly_appliances_peak_load,
|
||||||
{'yearly_appliances_peak_load': yearly_appliances_peak_load},
|
'yearly_appliances_peak_load': yearly_appliances_peak_load,
|
||||||
{'monthly_cooling_demand': monthly_cooling_demand},
|
'monthly_cooling_demand': monthly_cooling_demand,
|
||||||
{'yearly_cooling_demand': yearly_cooling_demand},
|
'yearly_cooling_demand': yearly_cooling_demand,
|
||||||
{'monthly_heating_demand': monthly_heating_demand},
|
'monthly_heating_demand': monthly_heating_demand,
|
||||||
{'yearly_heating_demand': yearly_heating_demand},
|
'yearly_heating_demand': yearly_heating_demand,
|
||||||
{'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand},
|
'monthly_lighting_electrical_demand': monthly_lighting_electrical_demand,
|
||||||
{'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand},
|
'yearly_lighting_electrical_demand': yearly_lighting_electrical_demand,
|
||||||
{'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand},
|
'monthly_appliances_electrical_demand': monthly_appliances_electrical_demand,
|
||||||
{'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand},
|
'yearly_appliances_electrical_demand': yearly_appliances_electrical_demand,
|
||||||
{'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand},
|
'monthly_domestic_hot_water_heat_demand': monthly_domestic_hot_water_heat_demand,
|
||||||
{'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand},
|
'yearly_domestic_hot_water_heat_demand': yearly_domestic_hot_water_heat_demand,
|
||||||
{'monthly_heating_consumption': monthly_heating_consumption},
|
'monthly_heating_consumption': monthly_heating_consumption,
|
||||||
{'yearly_heating_consumption': yearly_heating_consumption},
|
'yearly_heating_consumption': yearly_heating_consumption,
|
||||||
{'monthly_cooling_consumption': monthly_cooling_consumption},
|
'monthly_cooling_consumption': monthly_cooling_consumption,
|
||||||
{'yearly_cooling_consumption': yearly_cooling_consumption},
|
'yearly_cooling_consumption': yearly_cooling_consumption,
|
||||||
{'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption},
|
'monthly_domestic_hot_water_consumption': monthly_domestic_hot_water_consumption,
|
||||||
{'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption},
|
'yearly_domestic_hot_water_consumption': yearly_domestic_hot_water_consumption,
|
||||||
{'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption},
|
'monthly_distribution_systems_electrical_consumption': monthly_distribution_systems_electrical_consumption,
|
||||||
{'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption},
|
'yearly_distribution_systems_electrical_consumption': yearly_distribution_systems_electrical_consumption,
|
||||||
{'monthly_on_site_electrical_production': monthly_on_site_electrical_production},
|
'monthly_on_site_electrical_production': monthly_on_site_electrical_production,
|
||||||
{'yearly_on_site_electrical_production': yearly_on_site_electrical_production}
|
'yearly_on_site_electrical_production': yearly_on_site_electrical_production
|
||||||
]})
|
}}
|
||||||
|
|
||||||
db_building_id = _building.id
|
db_building_id = _building.id
|
||||||
city_objects_id.append(db_building_id)
|
city_objects_id.append(db_building_id)
|
||||||
|
@ -286,6 +285,8 @@ TestDBFactory
|
||||||
results, city_object_id=db_building_id)
|
results, city_object_id=db_building_id)
|
||||||
self.assertEqual(17, len(city_objects_id), 'wrong number of results')
|
self.assertEqual(17, 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')
|
||||||
|
results = control.database.results(control.user_id, control.application_id, request_values)
|
||||||
|
|
||||||
for _id in city_objects_id:
|
for _id in city_objects_id:
|
||||||
control.database.delete_results_by_name('insel meb', city_object_id=_id)
|
control.database.delete_results_by_name('insel meb', city_object_id=_id)
|
||||||
control.database.delete_city(city_id)
|
control.database.delete_city(city_id)
|
||||||
|
|
|
@ -5,11 +5,8 @@ Copyright © 2022 Concordia CERC group
|
||||||
Project Coder Peter Yefi peteryefi@gmail.com
|
Project Coder Peter Yefi peteryefi@gmail.com
|
||||||
"""
|
"""
|
||||||
import distutils.spawn
|
import distutils.spawn
|
||||||
import glob
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
@ -18,19 +15,7 @@ import sqlalchemy.exc
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.exc import ProgrammingError
|
from sqlalchemy.exc import ProgrammingError
|
||||||
|
|
||||||
import hub.helpers.constants as cte
|
|
||||||
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
|
|
||||||
from hub.exports.exports_factory import ExportsFactory
|
|
||||||
from hub.helpers.data.montreal_function_to_hub_function import MontrealFunctionToHubFunction
|
|
||||||
from hub.imports.construction_factory import ConstructionFactory
|
|
||||||
from hub.imports.energy_systems_factory import EnergySystemsFactory
|
|
||||||
from hub.imports.geometry_factory import GeometryFactory
|
|
||||||
from hub.imports.results_factory import ResultFactory
|
|
||||||
from hub.imports.usage_factory import UsageFactory
|
|
||||||
from hub.imports.weather_factory import WeatherFactory
|
|
||||||
from hub.persistence.db_control import DBControl
|
from hub.persistence.db_control import DBControl
|
||||||
from hub.persistence.models import City, Application, CityObject, SimulationResults
|
|
||||||
from hub.persistence.models import User, UserRoles
|
|
||||||
from hub.persistence.repository import Repository
|
from hub.persistence.repository import Repository
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,24 +114,12 @@ TestDBFactory
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@unittest.skipIf(control.skip_test, control.skip_reason)
|
@unittest.skipIf(control.skip_test, control.skip_reason)
|
||||||
def test_retrieve_results(self):
|
def test_buildings_info(self):
|
||||||
request_values = {
|
request_values = {
|
||||||
"scenarios": [
|
"buildings": [
|
||||||
{
|
"01002777", "01002773", "01036804"
|
||||||
"current status": ["01002777", "01002773", "01036804"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"skin retrofit": ["01002777", "01002773", "01036804"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"system retrofit and pv": ["01002777", "01002773", "01036804"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"skin and system retrofit with pv": ["01002777", "01002773", "01036804"]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
results = control.database.results(control.user_id, control.application_id, request_values)
|
results = control.database.buildings_info(control.user_id, control.application_id, request_values)
|
||||||
|
self.assertEqual(12, len(results), 'wrong number of results')
|
||||||
print(results)
|
print(results)
|
||||||
|
|
|
@ -5,19 +5,20 @@ Copyright © 2022 Concordia CERC group
|
||||||
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
||||||
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
import logging.handlers
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from hub.imports.geometry_factory import GeometryFactory
|
|
||||||
from hub.helpers.dictionaries import Dictionaries
|
|
||||||
from hub.imports.construction_factory import ConstructionFactory
|
|
||||||
from hub.imports.usage_factory import UsageFactory
|
|
||||||
from hub.imports.weather_factory import WeatherFactory
|
|
||||||
from hub.exports.exports_factory import ExportsFactory
|
|
||||||
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
|
|
||||||
import hub.helpers.constants as cte
|
import hub.helpers.constants as cte
|
||||||
from hub.city_model_structure.city import City
|
from hub.city_model_structure.city import City
|
||||||
|
from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory
|
||||||
|
from hub.exports.exports_factory import ExportsFactory
|
||||||
|
from hub.helpers.dictionaries import Dictionaries
|
||||||
|
from hub.imports.construction_factory import ConstructionFactory
|
||||||
|
from hub.imports.geometry_factory import GeometryFactory
|
||||||
|
from hub.imports.usage_factory import UsageFactory
|
||||||
|
from hub.imports.weather_factory import WeatherFactory
|
||||||
|
|
||||||
|
|
||||||
class TestExports(TestCase):
|
class TestExports(TestCase):
|
||||||
|
@ -66,7 +67,7 @@ class TestExports(TestCase):
|
||||||
|
|
||||||
def _export(self, export_type, from_pickle=False):
|
def _export(self, export_type, from_pickle=False):
|
||||||
self._complete_city = self._get_complete_city(from_pickle)
|
self._complete_city = self._get_complete_city(from_pickle)
|
||||||
ExportsFactory(export_type, self._complete_city, self._output_path).export()
|
ExportsFactory(export_type, self._complete_city, self._output_path, base_uri='../glb').export()
|
||||||
|
|
||||||
def _export_building_energy(self, export_type, from_pickle=False):
|
def _export_building_energy(self, export_type, from_pickle=False):
|
||||||
self._complete_city = self._get_complete_city(from_pickle)
|
self._complete_city = self._get_complete_city(from_pickle)
|
||||||
|
@ -78,6 +79,39 @@ class TestExports(TestCase):
|
||||||
"""
|
"""
|
||||||
self._export('obj', False)
|
self._export('obj', False)
|
||||||
|
|
||||||
|
def test_cesiumjs_tileset_export(self):
|
||||||
|
"""
|
||||||
|
export to cesiumjs tileset
|
||||||
|
"""
|
||||||
|
self._export('cesiumjs_tileset', False)
|
||||||
|
tileset = Path(self._output_path / f'{self._city.name}.json')
|
||||||
|
self.assertTrue(tileset.exists())
|
||||||
|
with open(tileset, 'r') as f:
|
||||||
|
json_tileset = json.load(f)
|
||||||
|
self.assertEqual(1, len(json_tileset['root']['children']), "Wrong number of children")
|
||||||
|
|
||||||
|
def test_glb_export(self):
|
||||||
|
"""
|
||||||
|
export to glb format
|
||||||
|
"""
|
||||||
|
self._export('glb', False)
|
||||||
|
for building in self._city.buildings:
|
||||||
|
glb_file = Path(self._output_path / f'{building.name}.glb')
|
||||||
|
self.assertTrue(glb_file.exists(), f'{building.name} Building glb wasn\'t correctly generated')
|
||||||
|
|
||||||
|
def test_geojson_export(self):
|
||||||
|
self._export('geojson', False)
|
||||||
|
geojson_file = Path(self._output_path / f'{self._city.name}.geojson')
|
||||||
|
self.assertTrue(geojson_file.exists(), f'{geojson_file} doesn\'t exists')
|
||||||
|
with open(geojson_file, 'r') as f:
|
||||||
|
geojson = json.load(f)
|
||||||
|
self.assertEqual(1, len(geojson['features']), 'Wrong number of buildings')
|
||||||
|
geometry = geojson['features'][0]['geometry']
|
||||||
|
self.assertEqual('Polygon', geometry['type'], 'Wrong geometry type')
|
||||||
|
self.assertEqual(1, len(geometry['coordinates']), 'Wrong polygon structure')
|
||||||
|
self.assertEqual(11, len(geometry['coordinates'][0]), 'Wrong number of vertices')
|
||||||
|
os.unlink(geojson_file) # todo: this test need to cover a multipolygon example too
|
||||||
|
|
||||||
def test_energy_ade_export(self):
|
def test_energy_ade_export(self):
|
||||||
"""
|
"""
|
||||||
export to energy ADE
|
export to energy ADE
|
||||||
|
@ -109,9 +143,7 @@ class TestExports(TestCase):
|
||||||
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
|
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
|
||||||
UsageFactory('nrcan', city).enrich()
|
UsageFactory('nrcan', city).enrich()
|
||||||
WeatherFactory('epw', city).enrich()
|
WeatherFactory('epw', city).enrich()
|
||||||
print(self._output_path)
|
|
||||||
try:
|
try:
|
||||||
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
|
EnergyBuildingsExportsFactory('idf', city, self._output_path).export()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
|
self.fail("Idf ExportsFactory raised ExceptionType unexpectedly!")
|
||||||
|
|
||||||
|
|
142
texttest
Normal file
142
texttest
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
ZoneControl:Thermostat,
|
||||||
|
Room_180_7ad8616b Thermostat, !- Name
|
||||||
|
Room_180_7ad8616b, !- Zone or ZoneList Name
|
||||||
|
Room_180_7ad8616b Thermostat Schedule, !- Control Type Schedule Name
|
||||||
|
ThermostatSetpoint:DualSetpoint, !- Control 1 Object Type
|
||||||
|
LargeOffice Building_Setpoint 26, !- Control 1 Name
|
||||||
|
, !- Control 2 Object Type
|
||||||
|
, !- Control 2 Name
|
||||||
|
, !- Control 3 Object Type
|
||||||
|
, !- Control 3 Name
|
||||||
|
, !- Control 4 Object Type
|
||||||
|
, !- Control 4 Name
|
||||||
|
0; !- Temperature Difference Between Cutout And Setpoint {deltaC}
|
||||||
|
|
||||||
|
Schedule:Compact,
|
||||||
|
Room_180_7ad8616b Thermostat Schedule, !- Name
|
||||||
|
Room_180_7ad8616b Thermostat Schedule Type Limits, !- Schedule Type Limits Name
|
||||||
|
Through: 12/31, !- Field 1
|
||||||
|
For: AllDays, !- Field 2
|
||||||
|
Until: 24:00, !- Field 3
|
||||||
|
4; !- Field 4
|
||||||
|
|
||||||
|
ScheduleTypeLimits,
|
||||||
|
Room_180_7ad8616b Thermostat Schedule Type Limits, !- Name
|
||||||
|
0, !- Lower Limit Value {BasedOnField A3}
|
||||||
|
4, !- Upper Limit Value {BasedOnField A3}
|
||||||
|
DISCRETE; !- Numeric Type
|
||||||
|
|
||||||
|
ThermostatSetpoint:DualSetpoint,
|
||||||
|
LargeOffice Building_Setpoint 26, !- Name
|
||||||
|
LargeOffice Building_Setpoint_HtgSetp Schedule, !- Heating Setpoint Temperature Schedule Name
|
||||||
|
LargeOffice Building_Setpoint_ClgSetp Schedule; !- Cooling Setpoint Temperature Schedule Name
|
||||||
|
|
||||||
|
ZoneHVAC:EquipmentConnections,
|
||||||
|
Room_180_7ad8616b, !- Zone Name
|
||||||
|
Room_180_7ad8616b Equipment List, !- Zone Conditioning Equipment List Name
|
||||||
|
Room_180_7ad8616b Inlet Node List, !- Zone Air Inlet Node or NodeList Name
|
||||||
|
, !- Zone Air Exhaust Node or NodeList Name
|
||||||
|
Node 27, !- Zone Air Node Name
|
||||||
|
Room_180_7ad8616b Return Node List; !- Zone Return Air Node or NodeList Name
|
||||||
|
|
||||||
|
NodeList,
|
||||||
|
Room_180_7ad8616b Inlet Node List, !- Name
|
||||||
|
Node 305; !- Node Name 1
|
||||||
|
|
||||||
|
NodeList,
|
||||||
|
Room_180_7ad8616b Return Node List, !- Name
|
||||||
|
Node 308; !- Node Name 1
|
||||||
|
|
||||||
|
ZoneHVAC:Baseboard:Convective:Electric,
|
||||||
|
Elec Baseboard 1, !- Name
|
||||||
|
Always On Discrete hvac_library, !- Availability Schedule Name
|
||||||
|
, !- Heating Design Capacity Method
|
||||||
|
Autosize, !- Heating Design Capacity {W}
|
||||||
|
, !- Heating Design Capacity Per Floor Area {W/m2}
|
||||||
|
, !- Fraction of Autosized Heating Design Capacity
|
||||||
|
1; !- Efficiency
|
||||||
|
|
||||||
|
AirTerminal:SingleDuct:ConstantVolume:NoReheat,
|
||||||
|
Diffuser 21, !- Name
|
||||||
|
Always On Discrete hvac_library, !- Availability Schedule Name
|
||||||
|
Node 307, !- Air Inlet Node Name
|
||||||
|
Node 305, !- Air Outlet Node Name
|
||||||
|
AutoSize; !- Maximum Air Flow Rate {m3/s}
|
||||||
|
|
||||||
|
ZoneHVAC:AirDistributionUnit,
|
||||||
|
ADU Diffuser 21, !- Name
|
||||||
|
Node 305, !- Air Distribution Unit Outlet Node Name
|
||||||
|
AirTerminal:SingleDuct:ConstantVolume:NoReheat, !- Air Terminal Object Type
|
||||||
|
Diffuser 21; !- Air Terminal Name
|
||||||
|
|
||||||
|
ZoneHVAC:EquipmentList,
|
||||||
|
Room_180_7ad8616b Equipment List, !- Name
|
||||||
|
SequentialLoad, !- Load Distribution Scheme
|
||||||
|
ZoneHVAC:Baseboard:Convective:Electric, !- Zone Equipment Object Type 1
|
||||||
|
Elec Baseboard 1, !- Zone Equipment Name 1
|
||||||
|
1, !- Zone Equipment Cooling Sequence 1
|
||||||
|
1, !- Zone Equipment Heating or No-Load Sequence 1
|
||||||
|
, !- Zone Equipment Sequential Cooling Fraction Schedule Name 1
|
||||||
|
, !- Zone Equipment Sequential Heating Fraction Schedule Name 1
|
||||||
|
ZoneHVAC:AirDistributionUnit, !- Zone Equipment Object Type 2
|
||||||
|
ADU Diffuser 21, !- Zone Equipment Name 2
|
||||||
|
2, !- Zone Equipment Cooling Sequence 2
|
||||||
|
2, !- Zone Equipment Heating or No-Load Sequence 2
|
||||||
|
, !- Zone Equipment Sequential Cooling Fraction Schedule Name 2
|
||||||
|
; !- Zone Equipment Sequential Heating Fraction Schedule Name 2
|
||||||
|
|
||||||
|
Sizing:Zone,
|
||||||
|
Room_180_7ad8616b, !- Zone or ZoneList Name
|
||||||
|
SupplyAirTemperature, !- Zone Cooling Design Supply Air Temperature Input Method
|
||||||
|
14, !- Zone Cooling Design Supply Air Temperature {C}
|
||||||
|
11.11, !- Zone Cooling Design Supply Air Temperature Difference {deltaC}
|
||||||
|
SupplyAirTemperature, !- Zone Heating Design Supply Air Temperature Input Method
|
||||||
|
40, !- Zone Heating Design Supply Air Temperature {C}
|
||||||
|
11.11, !- Zone Heating Design Supply Air Temperature Difference {deltaC}
|
||||||
|
0.0085, !- Zone Cooling Design Supply Air Humidity Ratio {kgWater/kgDryAir}
|
||||||
|
0.008, !- Zone Heating Design Supply Air Humidity Ratio {kgWater/kgDryAir}
|
||||||
|
Room_180_7ad8616b DSOA Space List, !- Design Specification Outdoor Air Object Name
|
||||||
|
, !- Zone Heating Sizing Factor
|
||||||
|
, !- Zone Cooling Sizing Factor
|
||||||
|
DesignDay, !- Cooling Design Air Flow Method
|
||||||
|
0, !- Cooling Design Air Flow Rate {m3/s}
|
||||||
|
0.000762, !- Cooling Minimum Air Flow per Zone Floor Area {m3/s-m2}
|
||||||
|
0, !- Cooling Minimum Air Flow {m3/s}
|
||||||
|
0, !- Cooling Minimum Air Flow Fraction
|
||||||
|
DesignDay, !- Heating Design Air Flow Method
|
||||||
|
0, !- Heating Design Air Flow Rate {m3/s}
|
||||||
|
0.002032, !- Heating Maximum Air Flow per Zone Floor Area {m3/s-m2}
|
||||||
|
0.1415762, !- Heating Maximum Air Flow {m3/s}
|
||||||
|
0.3, !- Heating Maximum Air Flow Fraction
|
||||||
|
, !- Design Specification Zone Air Distribution Object Name
|
||||||
|
No, !- Account for Dedicated Outdoor Air System
|
||||||
|
, !- Dedicated Outdoor Air System Control Strategy
|
||||||
|
, !- Dedicated Outdoor Air Low Setpoint Temperature for Design {C}
|
||||||
|
, !- Dedicated Outdoor Air High Setpoint Temperature for Design {C}
|
||||||
|
Sensible Load Only No Latent Load, !- Zone Load Sizing Method
|
||||||
|
HumidityRatioDifference, !- Zone Latent Cooling Design Supply Air Humidity Ratio Input Method
|
||||||
|
, !- Zone Dehumidification Design Supply Air Humidity Ratio {kgWater/kgDryAir}
|
||||||
|
0.005, !- Zone Cooling Design Supply Air Humidity Ratio Difference {kgWater/kgDryAir}
|
||||||
|
HumidityRatioDifference, !- Zone Latent Heating Design Supply Air Humidity Ratio Input Method
|
||||||
|
, !- Zone Humidification Design Supply Air Humidity Ratio {kgWater/kgDryAir}
|
||||||
|
0.005; !- Zone Humidification Design Supply Air Humidity Ratio Difference {kgWater/kgDryAir}
|
||||||
|
|
||||||
|
DesignSpecification:OutdoorAir:SpaceList,
|
||||||
|
Room_180_7ad8616b DSOA Space List, !- Name
|
||||||
|
Room_180_7ad8616b_Space, !- Space Name 1
|
||||||
|
MidriseApartment Apartment Ventilation; !- Space Design Specification Outdoor Air Object Name 1
|
||||||
|
|
||||||
|
Zone,
|
||||||
|
Room_181_3a411b5d, !- Name
|
||||||
|
, !- Direction of Relative North {deg}
|
||||||
|
0, !- X Origin {m}
|
||||||
|
0, !- Y Origin {m}
|
||||||
|
0, !- Z Origin {m}
|
||||||
|
, !- Type
|
||||||
|
1, !- Multiplier
|
||||||
|
4, !- Ceiling Height {m}
|
||||||
|
291.62935408288, !- Volume {m3}
|
||||||
|
, !- Floor Area {m2}
|
||||||
|
, !- Zone Inside Convection Algorithm
|
||||||
|
, !- Zone Outside Convection Algorithm
|
||||||
|
Yes; !- Part of Total Floor Area
|
Loading…
Reference in New Issue
Block a user