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:
Guille Gutierrez 2024-02-16 05:50:45 +01:00
commit 674393970c
47 changed files with 156740 additions and 1700 deletions

13
.gitignore vendored
View File

@ -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

View File

@ -1,3 +0,0 @@
# Except this file
*
!.gitignore

View File

@ -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()

View File

@ -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:

View File

@ -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]

View File

@ -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]:

View File

@ -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:

View File

@ -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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View 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

View File

@ -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):
""" """

View 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)

View 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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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']:

View File

@ -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']:

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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?

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -1,4 +1,4 @@
""" """
Hub version number Hub version number
""" """
__version__ = '0.1.8.11' __version__ = '0.1.8.39'

View File

@ -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')),

View File

@ -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)

View File

@ -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)

View File

@ -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
View 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