Quality improvements

This commit is contained in:
Guille Gutierrez 2021-08-26 13:27:43 -04:00
parent 58066a45cc
commit 312a9c34be
20 changed files with 224 additions and 180 deletions

View File

@ -554,7 +554,7 @@ class Polygon:
def _order_points(edges_list): def _order_points(edges_list):
# todo: not sure that this method works for any case -> RECHECK # todo: not sure that this method works for any case -> RECHECK
points = edges_list[0] points = edges_list[0]
for j in range(0, len(points)): for _ in range(0, len(points)):
for i in range(1, len(edges_list)): for i in range(1, len(edges_list)):
point_1 = edges_list[i][0] point_1 = edges_list[i][0]
point_2 = points[len(points)-1] point_2 = points[len(points)-1]

View File

@ -5,14 +5,11 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc
""" """
from pathlib import Path from pathlib import Path
from os.path import exists
from exports.formats.stl import Stl
from exports.formats.obj import Obj
from exports.formats.energy_ade import EnergyAde from exports.formats.energy_ade import EnergyAde
from exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
from exports.formats.idf import Idf from exports.formats.idf import Idf
from exports.formats.obj import Obj
from exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
from exports.formats.stl import Stl
class ExportsFactory: class ExportsFactory:
@ -73,7 +70,7 @@ class ExportsFactory:
idf_data_path = (Path(__file__).parent / './formats/idf_files/').resolve() idf_data_path = (Path(__file__).parent / './formats/idf_files/').resolve()
# todo: create a get epw file function based on the city # todo: create a get epw file function based on the city
weather_path = (Path(__file__).parent / '../data/weather/epw/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve() weather_path = (Path(__file__).parent / '../data/weather/epw/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve()
Idf(self._city, self._path, (idf_data_path / f'Minimal.idf'), (idf_data_path / f'Energy+.idd'), weather_path) return Idf(self._city, self._path, (idf_data_path / 'Minimal.idf'), (idf_data_path / 'Energy+.idd'), weather_path)
@property @property
def _sra(self): def _sra(self):
@ -85,10 +82,3 @@ class ExportsFactory:
:return: None :return: None
""" """
return getattr(self, self._export_type, lambda: None) return getattr(self, self._export_type, lambda: None)
def _debug_export(self):
"""
Export the city model structure to the given export type
:return: None
"""
self._idf()

View File

@ -3,47 +3,50 @@ ExportsFactory export a city into several formats
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
import xmltodict
import uuid import uuid
import datetime import datetime
from pathlib import Path from pathlib import Path
import xmltodict
import helpers.constants as cte import helpers.constants as cte
class EnergyAde: class EnergyAde:
"""
Export the city to citygml + energy ade
"""
def __init__(self, city, path): def __init__(self, city, path):
self._city = city self._city = city
self._path = path self._path = path
self._surface_members = None self._surface_members = None
self._export() self._export()
def _export(self): def _export(self):
energy_ade = { energy_ade = {
'core:CityModel': { 'core:CityModel': {
'@xmlns:brid':'http://www.opengis.net/citygml/bridge/2.0', '@xmlns:brid': 'http://www.opengis.net/citygml/bridge/2.0',
'@xmlns:tran':'http://www.opengis.net/citygml/transportation/2.0', '@xmlns:tran': 'http://www.opengis.net/citygml/transportation/2.0',
'@xmlns:frn':'http://www.opengis.net/citygml/cityfurniture/2.0', '@xmlns:frn': 'http://www.opengis.net/citygml/cityfurniture/2.0',
'@xmlns:wtr':'http://www.opengis.net/citygml/waterbody/2.0', '@xmlns:wtr': 'http://www.opengis.net/citygml/waterbody/2.0',
'@xmlns:sch':'http://www.ascc.net/xml/schematron', '@xmlns:sch': 'http://www.ascc.net/xml/schematron',
'@xmlns:veg':'http://www.opengis.net/citygml/vegetation/2.0', '@xmlns:veg': 'http://www.opengis.net/citygml/vegetation/2.0',
'@xmlns:xlink':'http://www.w3.org/1999/xlink', '@xmlns:xlink': 'http://www.w3.org/1999/xlink',
'@xmlns:tun':'http://www.opengis.net/citygml/tunnel/2.0', '@xmlns:tun': 'http://www.opengis.net/citygml/tunnel/2.0',
'@xmlns:tex':'http://www.opengis.net/citygml/texturedsurface/2.0', '@xmlns:tex': 'http://www.opengis.net/citygml/texturedsurface/2.0',
'@xmlns:gml':'http://www.opengis.net/gml', '@xmlns:gml': 'http://www.opengis.net/gml',
'@xmlns:genobj':'http://www.opengis.net/citygml/generics/2.0', '@xmlns:genobj': 'http://www.opengis.net/citygml/generics/2.0',
'@xmlns:dem':'http://www.opengis.net/citygml/relief/2.0', '@xmlns:dem': 'http://www.opengis.net/citygml/relief/2.0',
'@xmlns:app':'http://www.opengis.net/citygml/appearance/2.0', '@xmlns:app': 'http://www.opengis.net/citygml/appearance/2.0',
'@xmlns:luse':'http://www.opengis.net/citygml/landuse/2.0', '@xmlns:luse': 'http://www.opengis.net/citygml/landuse/2.0',
'@xmlns:xAL':'urn:oasis:names:tc:ciq:xsdschema:xAL:2.0', '@xmlns:xAL': 'urn:oasis:names:tc:ciq:xsdschema:xAL:2.0',
'@xmlns:xsi':'http://www.w3.org/2001/XMLSchema-instance', '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'@xmlns:smil20lang':'http://www.w3.org/2001/SMIL20/Language', '@xmlns:smil20lang': 'http://www.w3.org/2001/SMIL20/Language',
'@xmlns:pbase':'http://www.opengis.net/citygml/profiles/base/2.0', '@xmlns:pbase': 'http://www.opengis.net/citygml/profiles/base/2.0',
'@xmlns:smil20': 'http://www.w3.org/2001/SMIL20/', '@xmlns:smil20': 'http://www.w3.org/2001/SMIL20/',
'@xmlns:bldg':'http://www.opengis.net/citygml/building/2.0', '@xmlns:bldg': 'http://www.opengis.net/citygml/building/2.0',
'@xmlns:energy': "http://www.sig3d.org/citygml/2.0/energy/1.0", '@xmlns:energy': "http://www.sig3d.org/citygml/2.0/energy/1.0",
'@xmlns:core':'http://www.opengis.net/citygml/2.0', '@xmlns:core': 'http://www.opengis.net/citygml/2.0',
'@xmlns:grp':'http://www.opengis.net/citygml/cityobjectgroup/2.0', '@xmlns:grp': 'http://www.opengis.net/citygml/cityobjectgroup/2.0',
'gml:boundedBy': { 'gml:boundedBy': {
'gml:Envelope': { 'gml:Envelope': {
'@srsName': self._city.srs_name, '@srsName': self._city.srs_name,
@ -68,7 +71,7 @@ class EnergyAde:
building_dic = self._building_geometry(building, building_dic, self._city) building_dic = self._building_geometry(building, building_dic, self._city)
building_dic['bldg:Building']['energy:volume'] = { building_dic['bldg:Building']['energy:volume'] = {
'energy:VolumeType': { 'energy:VolumeType': {
'energy:type':'grossVolume', 'energy:type': 'grossVolume',
'energy:value': { 'energy:value': {
'@uom': 'm3', '@uom': 'm3',
'energy:value': building.volume 'energy:value': building.volume
@ -79,22 +82,22 @@ class EnergyAde:
'gml:Point': { 'gml:Point': {
'@srsName': self._city.srs_name, '@srsName': self._city.srs_name,
'@gml:id': f'GML_{uuid.uuid4()}', '@gml:id': f'GML_{uuid.uuid4()}',
'gml:Pos': f'{" ".join(map(str,building.centroid))}' 'gml:Pos': f'{" ".join(map(str, building.centroid))}'
} }
} }
building_dic['bldg:Building']['energy:thermalZone'] = self._thermal_zones(building,self._city) building_dic['bldg:Building']['energy:thermalZone'] = self._thermal_zones(building, self._city)
buildings.append(building_dic) buildings.append(building_dic)
energy_ade['core:CityModel']['core:cityObjectMember'] = buildings energy_ade['core:CityModel']['core:cityObjectMember'] = buildings
file_name = self._city.name + '_ade.gml' file_name = self._city.name + '_ade.gml'
file_path = Path(self._path / file_name).resolve() file_path = Path(self._path / file_name).resolve()
with open(file_path, 'w' ) as file: with open(file_path, 'w') as file:
file.write(xmltodict.unparse(energy_ade,pretty=True, short_empty_elements=True)) file.write(xmltodict.unparse(energy_ade, pretty=True, short_empty_elements=True))
@staticmethod @staticmethod
def _measures(building, building_dic): def _measures(building, building_dic):
#todo: this method is only for year and insel need to be generalized # todo: this method is only for year and insel need to be generalized
measures = [] measures = []
measure = EnergyAde._measure(building.heating, cte.YEAR, 'Energy demand heating', 'INSEL') measure = EnergyAde._measure(building.heating, cte.YEAR, 'Energy demand heating', 'INSEL')
if measure is not None: if measure is not None:
@ -127,8 +130,8 @@ class EnergyAde:
measure = { measure = {
'@name': name, '@name': name,
'genobj:value': { 'genobj:value': {
'@uom': 'kWh', '@uom': 'kWh',
'#text': ' '.join([str(e/1000) for e in measure_dict[key_value][source]]) '#text': ' '.join([str(e / 1000) for e in measure_dict[key_value][source]])
} }
} }
return measure return measure
@ -162,7 +165,6 @@ class EnergyAde:
} }
return demand return demand
def _building_geometry(self, building, building_dic, city): def _building_geometry(self, building, building_dic, city):
building_dic['bldg:Building']['bldg:function'] = building.function building_dic['bldg:Building']['bldg:function'] = building.function
@ -183,7 +185,6 @@ class EnergyAde:
raise NotImplementedError('Only lod 1 and 2 can be exported') raise NotImplementedError('Only lod 1 and 2 can be exported')
return building_dic return building_dic
def _lod1(self, building, building_dic, city): def _lod1(self, building, building_dic, city):
raise NotImplementedError('Only lod 1 and 2 can be exported') raise NotImplementedError('Only lod 1 and 2 can be exported')
@ -191,13 +192,13 @@ class EnergyAde:
self._surface_members = [] self._surface_members = []
boundaries = [{ boundaries = [{
'gml:Envelope': { 'gml:Envelope': {
'@srsName': city.srs_name, '@srsName': city.srs_name,
'@srsDimension': 3, '@srsDimension': 3,
'gml:lowerCorner': ' '.join([str(e) for e in city.lower_corner]), 'gml:lowerCorner': ' '.join([str(e) for e in city.lower_corner]),
'gml:upperCorner': ' '.join([str(e) for e in city.upper_corner]) 'gml:upperCorner': ' '.join([str(e) for e in city.upper_corner])
}}] }}]
for surface in building.surfaces: for surface in building.surfaces:
surface_member = { '@xlink:href': f'#PolyId{surface.name}'} surface_member = {'@xlink:href': f'#PolyId{surface.name}'}
self._surface_members.append(surface_member) self._surface_members.append(surface_member)
if surface.type == 'Wall': if surface.type == 'Wall':
surface_type = 'bldg:WallSurface' surface_type = 'bldg:WallSurface'
@ -206,7 +207,7 @@ class EnergyAde:
else: else:
surface_type = 'bldg:RoofSurface' surface_type = 'bldg:RoofSurface'
surface_dic = { surface_dic = {
surface_type: { surface_type: {
'@gml:id': f'GML_{uuid.uuid4()}', '@gml:id': f'GML_{uuid.uuid4()}',
'gml:name': f'{surface.name} ({surface.type})', 'gml:name': f'{surface.name} ({surface.type})',
'gml:boundedBy': { 'gml:boundedBy': {
@ -272,7 +273,7 @@ class EnergyAde:
'gml:name': f'Thermal zone {index} in {building.name} building', 'gml:name': f'Thermal zone {index} in {building.name} building',
'energy:contains': [], 'energy:contains': [],
'energy:floorArea': { 'energy:floorArea': {
'energy:FloorArea' : { 'energy:FloorArea': {
'energy:type': 'grossFloorArea', 'energy:type': 'grossFloorArea',
'energy:value': { 'energy:value': {
'@uom': 'm2', '@uom': 'm2',
@ -285,7 +286,7 @@ class EnergyAde:
'energy:type': 'grossVolume', 'energy:type': 'grossVolume',
'energy:value': { 'energy:value': {
'@uom': 'm3', '@uom': 'm3',
#todo: for now we have just one thermal zone, therefore is the building volume, this need to be changed # todo: for now we have just one thermal zone, therefore is the building volume, this need to be changed
'#text': f'{building.volume}' '#text': f'{building.volume}'
} }
} }
@ -370,11 +371,8 @@ class EnergyAde:
'energy:construction': f'#GML_{opening_construction}', 'energy:construction': f'#GML_{opening_construction}',
'energy:surfaceGeometry': { 'energy:surfaceGeometry': {
} }
} }
} }
thermal_boundaries.append(thermal_boundary_dic) thermal_boundaries.append(thermal_boundary_dic)
return thermal_boundaries return thermal_boundaries

View File

@ -4,10 +4,12 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Soroush Samareh Abolhassani - soroush.samarehabolhassani@mail.concordia.ca Copyright © 2020 Project Author Soroush Samareh Abolhassani - soroush.samarehabolhassani@mail.concordia.ca
""" """
from geomeppy import IDF from geomeppy import IDF
from pathlib import Path
class Idf: class Idf:
"""
Export city to IDF
"""
_THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT' _THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT'
_IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM' _IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM'
_SURFACE = 'BUILDINGSURFACE:DETAILED' _SURFACE = 'BUILDINGSURFACE:DETAILED'
@ -185,15 +187,15 @@ class Idf:
Template_Thermostat_Name=thermostat.Name) Template_Thermostat_Name=thermostat.Name)
def _add_occupancy(self, usage_zone): def _add_occupancy(self, usage_zone):
self._idf.newidfobject(self._PEOPLE, self._idf.newidfobject(self._PEOPLE,
Name=f'{usage_zone.id}_occupancy', Name=f'{usage_zone.id}_occupancy',
Zone_or_ZoneList_Name=usage_zone.id, Zone_or_ZoneList_Name=usage_zone.id,
Number_of_People_Schedule_Name=f'Occupancy schedules {usage_zone.usage}', Number_of_People_Schedule_Name=f'Occupancy schedules {usage_zone.usage}',
Number_of_People_Calculation_Method="People", Number_of_People_Calculation_Method="People",
Number_of_People=500, # todo: get people from where? Number_of_People=500, # todo: get people from where?
Fraction_Radiant=0.3, # todo: howto get this from InternalGains Fraction_Radiant=0.3, # todo: howto get this from InternalGains
Activity_Level_Schedule_Name='occupant schedules' Activity_Level_Schedule_Name='occupant schedules'
) )
def _add_equipment(self, usage_zone): def _add_equipment(self, usage_zone):
self._idf.newidfobject(self._ELECTRIC_EQUIPMENT, self._idf.newidfobject(self._ELECTRIC_EQUIPMENT,
@ -269,10 +271,8 @@ class Idf:
for boundary in thermal_zone.thermal_boundaries: for boundary in thermal_zone.thermal_boundaries:
idf_surface_type = self.idf_surfaces[boundary.surface.type] idf_surface_type = self.idf_surfaces[boundary.surface.type]
for usage_zone in thermal_zone.usage_zones: for usage_zone in thermal_zone.usage_zones:
surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.surface.name}', surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.surface.name}',
Surface_Type=idf_surface_type, Zone_Name=usage_zone.id, Surface_Type=idf_surface_type, Zone_Name=usage_zone.id,
Construction_Name=boundary.construction_name) Construction_Name=boundary.construction_name)
coordinates = self._matrix_to_list(boundary.surface.solid_polygon.coordinates) coordinates = self._matrix_to_list(boundary.surface.solid_polygon.coordinates)
surface.setcoords(coordinates) surface.setcoords(coordinates)

View File

@ -4,18 +4,23 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from exports.formats.triangular import Triangular
from pathlib import Path from pathlib import Path
from imports.geometry_factory import GeometryFactory
import trimesh.exchange.obj import trimesh.exchange.obj
from exports.formats.triangular import Triangular
from imports.geometry_factory import GeometryFactory
class Obj(Triangular): class Obj(Triangular):
"""
Export to obj format
"""
def __init__(self, city, path): def __init__(self, city, path):
super().__init__(city, path, 'obj') super().__init__(city, path, 'obj')
def to_ground_points(self): def to_ground_points(self):
"""
Move closer to the origin
"""
file_name_in = self._city.name + '.' + self._triangular_format file_name_in = self._city.name + '.' + self._triangular_format
file_name_out = self._city.name + '_ground.' + self._triangular_format file_name_out = self._city.name + '_ground.' + self._triangular_format
file_path_in = (Path(self._path).resolve() / file_name_in).resolve() file_path_in = (Path(self._path).resolve() / file_name_in).resolve()

View File

@ -3,12 +3,13 @@ Simplified Radiosity Algorithm
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guillermo.GutierrezMorote@concordia.ca Copyright © 2020 Project Author Guillermo.GutierrezMorote@concordia.ca
""" """
from pathlib import Path
import xmltodict import xmltodict
class SimplifiedRadiosityAlgorithm: class SimplifiedRadiosityAlgorithm:
"""
Export to SRA format
"""
def __init__(self, city, file_name, begin_month=1, begin_day=1, end_month=12, end_day=31): def __init__(self, city, file_name, begin_month=1, begin_day=1, end_month=12, end_day=31):
self._file_name = file_name self._file_name = file_name
self._begin_month = begin_month self._begin_month = begin_month
@ -83,7 +84,5 @@ class SimplifiedRadiosityAlgorithm:
} }
} }
} }
with open(self._file_name, "w") as file: with open(self._file_name, "w") as file:
file.write(xmltodict.unparse(sra, pretty=True, short_empty_elements=True)) file.write(xmltodict.unparse(sra, pretty=True, short_empty_elements=True))
return

View File

@ -8,5 +8,8 @@ from exports.formats.triangular import Triangular
class Stl(Triangular): class Stl(Triangular):
"""
Export to STL
"""
def __init__(self, city, path): def __init__(self, city, path):
super().__init__(city, path, 'stl', write_mode='wb') super().__init__(city, path, 'stl', write_mode='wb')

View File

@ -8,6 +8,9 @@ from trimesh import Trimesh
class Triangular: class Triangular:
"""
Superclass to export to triangular format (STL or OBJ)
"""
def __init__(self, city, path, triangular_format, write_mode='w'): def __init__(self, city, path, triangular_format, write_mode='w'):
self._city = city self._city = city
self._path = path self._path = path

View File

@ -1,3 +1,10 @@
"""
Constant module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
# universal constants # universal constants
KELVIN = 273.15 KELVIN = 273.15
@ -48,4 +55,3 @@ RETAIL = 'retail'
HALL = 'hall' HALL = 'hall'
RESTAURANT = 'restaurant' RESTAURANT = 'restaurant'
EDUCATION = 'education' EDUCATION = 'education'

View File

@ -10,65 +10,90 @@ from imports.schedules_factory import SchedulesFactory
class EnrichCity: class EnrichCity:
"""
Enrich city
"""
def __init__(self, city): def __init__(self, city):
self._city = city self._city = city
self._enriched_city = None self._enriched_city = None
self._errors = [] self._errors = []
@property @property
def errors(self): def errors(self) -> [str]:
"""
Error list
"""
return self._errors return self._errors
def enriched_city(self, construction_format=None, usage_format=None, schedules_format=None): def enriched_city(self, construction_format=None, usage_format=None, schedules_format=None):
"""
Enrich the city with the given formats
:return: City
"""
if self._enriched_city is None: if self._enriched_city is None:
self._errors = [] self._errors = []
print('original:', len(self._city.buildings)) print('original:', len(self._city.buildings))
if construction_format is not None: if construction_format is not None:
# todo: in construction factory, when adding the values to the thermal zones, self._enriched_city = self._construction(construction_format)
# these are created using the just read storeys_above_ground -> review where to assign this value!! if len(self._errors) != 0:
ConstructionFactory(construction_format, self._city).enrich()
for building in self._city.buildings:
# infiltration_rate_system_off is a mandatory parameter.
# If it is not returned, extract the building from the calculation list
if building.thermal_zones[0].infiltration_rate_system_off is None:
self._city.remove_city_object(building)
if self._city.buildings is None:
self._errors.append('no archetype found per construction')
self._enriched_city = self._city
return self._enriched_city return self._enriched_city
print('enriched with construction:', len(self._city.buildings))
if usage_format is not None: if usage_format is not None:
UsageFactory(usage_format, self._city).enrich() self._enriched_city = self._usage(usage_format)
for building in self._city.buildings: if len(self._errors) != 0:
# At least one thermal zone must be created.
# If it is not created, extract the building from the calculation list
if len(building.usage_zones) <= 0:
self._city.remove_city_object(building)
if self._city.buildings is None:
self._errors.append('no archetype found per usage')
self._enriched_city = self._city
return self._enriched_city return self._enriched_city
print('enriched with usage:', len(self._city.buildings))
if schedules_format is not None: if schedules_format is not None:
SchedulesFactory(schedules_format, self._city).enrich() self._enriched_city = self._schedules(schedules_format)
for building in self._city.buildings: if len(self._errors) != 0:
counter_schedules = 0
for usage_zone in building.usage_zones:
# At least one schedule must be created at each thermal zone.
# If it is not created, extract the building from the calculation list
if len(usage_zone.schedules) > 0:
counter_schedules += 1
if counter_schedules < len(building.usage_zones):
self._city.remove_city_object(building)
if self._city.buildings is None:
self._errors.append('no archetype found per usage')
self._enriched_city = self._city
return self._enriched_city return self._enriched_city
print('enriched with occupancy:', len(self._city.buildings)) self._enriched_city = self._city
self._enriched_city = self._city
return self._enriched_city return self._enriched_city
def _construction(self, construction_format):
# todo: in construction factory, when adding the values to the thermal zones,
# these are created using the just read storeys_above_ground -> review where to assign this value!!
ConstructionFactory(construction_format, self._city).enrich()
for building in self._city.buildings:
# infiltration_rate_system_off is a mandatory parameter.
# If it is not returned, extract the building from the calculation list
if building.thermal_zones[0].infiltration_rate_system_off is None:
self._city.remove_city_object(building)
if self._city.buildings is None:
self._errors.append('no archetype found per construction')
self._enriched_city = self._city
return self._enriched_city
print('enriched with construction:', len(self._city.buildings))
return self._city
def _usage(self, usage_format):
UsageFactory(usage_format, self._city).enrich()
for building in self._city.buildings:
# At least one thermal zone must be created.
# If it is not created, extract the building from the calculation list
if len(building.usage_zones) <= 0:
self._city.remove_city_object(building)
if self._city.buildings is None:
self._errors.append('no archetype found per usage')
self._enriched_city = self._city
return self._enriched_city
print('enriched with usage:', len(self._city.buildings))
return self._city
def _schedules(self, schedules_format):
SchedulesFactory(schedules_format, self._city).enrich()
for building in self._city.buildings:
counter_schedules = 0
for usage_zone in building.usage_zones:
# At least one schedule must be created at each thermal zone.
# If it is not created, extract the building from the calculation list
if len(usage_zone.schedules) > 0:
counter_schedules += 1
if counter_schedules < len(building.usage_zones):
self._city.remove_city_object(building)
if self._city.buildings is None:
self._errors.append('no archetype found per usage')
self._enriched_city = self._city
return self._enriched_city
print('enriched with occupancy:', len(self._city.buildings))
return self._city

View File

@ -9,10 +9,10 @@ import numpy as np
import requests import requests
from trimesh import Trimesh from trimesh import Trimesh
from trimesh import intersections from trimesh import intersections
from helpers.configuration_helper import ConfigurationHelper
from city_model_structure.attributes.polygon import Polygon from city_model_structure.attributes.polygon import Polygon
from city_model_structure.attributes.polyhedron import Polyhedron from city_model_structure.attributes.polyhedron import Polyhedron
from helpers.location import Location from helpers.location import Location
from helpers.configuration_helper import ConfigurationHelper
class GeometryHelper: class GeometryHelper:
@ -47,7 +47,6 @@ class GeometryHelper:
delta = math.fabs(a1 - a2) delta = math.fabs(a1 - a2)
return delta <= self._area_delta return delta <= self._area_delta
def is_almost_same_surface(self, s1, s2): def is_almost_same_surface(self, s1, s2):
""" """
Compare two surfaces and decides if they are almost equal (quadratic error under delta) Compare two surfaces and decides if they are almost equal (quadratic error under delta)
@ -82,11 +81,13 @@ class GeometryHelper:
if minimum_distance > self._delta or s1.intersect(s2) is None: if minimum_distance > self._delta or s1.intersect(s2) is None:
return False return False
else: return True
return True
@staticmethod @staticmethod
def segment_list_to_trimesh(lines) -> Trimesh: def segment_list_to_trimesh(lines) -> Trimesh:
"""
Transform a list of segments into a Trimesh
"""
line_points = [lines[0][0], lines[0][1]] line_points = [lines[0][0], lines[0][1]]
lines.remove(lines[0]) lines.remove(lines[0])
while len(lines) > 1: while len(lines) > 1:
@ -97,7 +98,7 @@ class GeometryHelper:
line_points.append(line[1]) line_points.append(line[1])
lines.pop(i - 1) lines.pop(i - 1)
break break
elif GeometryHelper.distance_between_points(line[1], line_points[len(line_points) - 1]) < 1e-8: if GeometryHelper.distance_between_points(line[1], line_points[len(line_points) - 1]) < 1e-8:
line_points.append(line[0]) line_points.append(line[0])
lines.pop(i - 1) lines.pop(i - 1)
break break
@ -161,22 +162,24 @@ class GeometryHelper:
return [trimesh_1, trimesh_2] return [trimesh_1, trimesh_2]
@staticmethod @staticmethod
def get_location(latitude, longitude): def get_location(latitude, longitude) -> Location:
"""
Get Location from latitude and longitude
"""
url = 'https://nominatim.openstreetmap.org/reverse?lat={latitude}&lon={longitude}&format=json' url = 'https://nominatim.openstreetmap.org/reverse?lat={latitude}&lon={longitude}&format=json'
response = requests.get(url.format(latitude=latitude, longitude=longitude)) response = requests.get(url.format(latitude=latitude, longitude=longitude))
if response.status_code != 200: if response.status_code != 200:
# This means something went wrong. # This means something went wrong.
raise Exception('GET /tasks/ {}'.format(response.status_code)) raise Exception('GET /tasks/ {}'.format(response.status_code))
else:
response = response.json() response = response.json()
# todo: this is wrong, remove in the future city = 'Unknown'
city = 'new_york_city' country = 'ca'
country = 'us' if 'city' in response['address']:
if 'city' in response['address']: city = response['address']['city']
city = response['address']['city'] if 'country_code' in response['address']:
if 'country_code' in response['address']: country = response['address']['country_code']
country = response['address']['country_code'] return Location(country, city)
return Location(country, city)
@staticmethod @staticmethod
def distance_between_points(vertex1, vertex2): def distance_between_points(vertex1, vertex2):

View File

@ -1,12 +1,29 @@
"""
Location module
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
class Location: class Location:
"""
Location
"""
def __init__(self, country, city): def __init__(self, country, city):
self._country = country self._country = country
self._city = city self._city = city
@property @property
def city(self): def city(self):
"""
City name
"""
return self._city return self._city
@property @property
def country(self): def country(self):
return self._country """
Country code
"""
return self._country

View File

@ -3,9 +3,9 @@ monthly_to_hourly_demand module
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
import calendar as cal
import pandas as pd import pandas as pd
from city_model_structure.building_demand.occupants import Occupants from city_model_structure.building_demand.occupants import Occupants
import calendar as cal
import helpers.constants as cte import helpers.constants as cte
@ -44,7 +44,7 @@ class MonthlyToHourlyDemand:
for month in range(1, 13): for month in range(1, 13):
temp_grad_month = 0 temp_grad_month = 0
month_range = cal.monthrange(2015, month)[1] month_range = cal.monthrange(2015, month)[1]
for day in range(1, month_range+1): for _ in range(1, month_range+1):
external_temp_med = 0 external_temp_med = 0
for hour in range(0, 24): for hour in range(0, 24):
external_temp_med += external_temp[key][i]/24 external_temp_med += external_temp[key][i]/24
@ -66,10 +66,8 @@ class MonthlyToHourlyDemand:
temp_grad_month += temp_grad_day[i] temp_grad_month += temp_grad_day[i]
i += 1 i += 1
for day in range(1, month_range + 1): for _ in range(1, month_range + 1):
for hour in range(0, 24): for hour in range(0, 24):
# monthly_demand = self._building.heating[cte.MONTH]['INSEL'][month-1] or maybe:
# monthly_demand = self._building.heating[cte.MONTH].INSEL[month-1]
monthly_demand = self._building.heating[cte.MONTH][month-1] monthly_demand = self._building.heating[cte.MONTH][month-1]
if monthly_demand == 'NaN': if monthly_demand == 'NaN':
monthly_demand = 0 monthly_demand = 0
@ -104,7 +102,7 @@ class MonthlyToHourlyDemand:
for month in range(1, 13): for month in range(1, 13):
temp_grad_month = 0 temp_grad_month = 0
month_range = cal.monthrange(2015, month)[1] month_range = cal.monthrange(2015, month)[1]
for day in range(1, month_range[1] + 1): for _ in range(1, month_range[1] + 1):
for hour in range(0, 24): for hour in range(0, 24):
if external_temp[key][i] > temp_set and cooling_schedule[month - 1] == 1: if external_temp[key][i] > temp_set and cooling_schedule[month - 1] == 1:
if occupancy[hour] > 0: if occupancy[hour] > 0:
@ -123,7 +121,7 @@ class MonthlyToHourlyDemand:
temp_grad_month += temp_grad_day[i] temp_grad_month += temp_grad_day[i]
i += 1 i += 1
for day in range(1, month_range[1] + 1): for _ in range(1, month_range[1] + 1):
for hour in range(0, 24): for hour in range(0, 24):
# monthly_demand = self._building.heating[cte.MONTH]['INSEL'][month-1] # monthly_demand = self._building.heating[cte.MONTH]['INSEL'][month-1]
monthly_demand = self._building.cooling[cte.MONTH][month - 1] monthly_demand = self._building.cooling[cte.MONTH][month - 1]

View File

@ -4,11 +4,8 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
""" """
import sys import sys
from imports.construction.nrel_physics_interface import NrelPhysicsInterface
from imports.construction.helpers.construction_helper import ConstructionHelper from imports.construction.helpers.construction_helper import ConstructionHelper
from city_model_structure.building_demand.layer import Layer from imports.construction.nrel_physics_interface import NrelPhysicsInterface
from city_model_structure.building_demand.material import Material
class CaPhysicsParameters(NrelPhysicsInterface): class CaPhysicsParameters(NrelPhysicsInterface):

View File

@ -8,6 +8,9 @@ from helpers import constants as cte
class ConstructionHelper: class ConstructionHelper:
"""
Construction helper
"""
# NREL # NREL
function_to_nrel = { function_to_nrel = {
cte.RESIDENTIAL: 'residential', cte.RESIDENTIAL: 'residential',

View File

@ -16,6 +16,7 @@ class NrelPhysicsInterface:
""" """
NrelPhysicsInterface abstract class NrelPhysicsInterface abstract class
""" """
def __init__(self, base_path, constructions_file='us_constructions.xml', def __init__(self, base_path, constructions_file='us_constructions.xml',
archetypes_file='us_archetypes.xml'): archetypes_file='us_archetypes.xml'):
self._building_archetypes = [] self._building_archetypes = []
@ -39,7 +40,7 @@ class NrelPhysicsInterface:
if units != 'm': if units != 'm':
raise Exception(f'average storey height units = {units}, expected meters') raise Exception(f'average storey height units = {units}, expected meters')
storeys_above_ground = archetype['number_of_storeys']['#text'] storeys_above_ground = archetype['number_of_storeys']['#text']
effective_thermal_capacity = float(archetype['thermal_capacity']['#text'])*1000 effective_thermal_capacity = float(archetype['thermal_capacity']['#text']) * 1000
units = archetype['thermal_capacity']['@units'] units = archetype['thermal_capacity']['@units']
if units != 'kJ/K m2': if units != 'kJ/K m2':
raise Exception(f'thermal capacity units = {units}, expected kJ/K m2') raise Exception(f'thermal capacity units = {units}, expected kJ/K m2')
@ -75,12 +76,12 @@ class NrelPhysicsInterface:
visible_absorptance = material_lib['visible_absorptance']['#text'] visible_absorptance = material_lib['visible_absorptance']['#text']
no_mass = 'no_mass' in material_lib no_mass = 'no_mass' in material_lib
if no_mass: if no_mass:
thermal_resistance = material_lib['thermal_resistance']['#text'] thermal_resistance = material_lib['thermal_resistance']['#text']
units = material_lib['thermal_resistance']['@units'] units = material_lib['thermal_resistance']['@units']
if units != 'm2 K/W': if units != 'm2 K/W':
raise Exception(f'thermal resistance units = {units}, expected m2 K/W') raise Exception(f'thermal resistance units = {units}, expected m2 K/W')
layer = nla(name, solar_absorptance, thermal_absorptance, visible_absorptance, no_mass=no_mass, layer = nla(name, solar_absorptance, thermal_absorptance, visible_absorptance, no_mass=no_mass,
thermal_resistance=thermal_resistance) thermal_resistance=thermal_resistance)
else: else:
thickness = current_layer['thickness']['#text'] thickness = current_layer['thickness']['#text']
units = current_layer['thickness']['@units'] units = current_layer['thickness']['@units']
@ -119,9 +120,9 @@ class NrelPhysicsInterface:
raise Exception(f'thickness units = {units}, expected m') raise Exception(f'thickness units = {units}, expected m')
g_value = w_lib['solar_transmittance_at_normal_incidence']['#text'] g_value = w_lib['solar_transmittance_at_normal_incidence']['#text']
back_side_solar_transmittance_at_normal_incidence = \ back_side_solar_transmittance_at_normal_incidence = \
w_lib['back_side_solar_transmittance_at_normal_incidence']['#text'] w_lib['back_side_solar_transmittance_at_normal_incidence']['#text']
front_side_solar_transmittance_at_normal_incidence = \ front_side_solar_transmittance_at_normal_incidence = \
w_lib['front_side_solar_transmittance_at_normal_incidence']['#text'] w_lib['front_side_solar_transmittance_at_normal_incidence']['#text']
thermal_opening = ntoa(conductivity=conductivity, frame_ratio=frame_ratio, g_value=g_value, thermal_opening = ntoa(conductivity=conductivity, frame_ratio=frame_ratio, g_value=g_value,
thickness=thickness, back_side_solar_transmittance_at_normal_incidence= thickness=thickness, back_side_solar_transmittance_at_normal_incidence=
back_side_solar_transmittance_at_normal_incidence, back_side_solar_transmittance_at_normal_incidence,
@ -177,4 +178,7 @@ class NrelPhysicsInterface:
raise Exception('Construction type not found') raise Exception('Construction type not found')
def enrich_buildings(self): def enrich_buildings(self):
"""
Raise not implemented error
"""
raise NotImplementedError raise NotImplementedError

View File

@ -3,9 +3,9 @@ ConstructionFactory (before PhysicsFactory) retrieve the specific construction m
SPDX - License - Identifier: LGPL - 3.0 - or -later SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
from pathlib import Path
from imports.construction.us_physics_parameters import UsPhysicsParameters from imports.construction.us_physics_parameters import UsPhysicsParameters
from imports.construction.ca_physics_parameters import CaPhysicsParameters from imports.construction.ca_physics_parameters import CaPhysicsParameters
from pathlib import Path
class ConstructionFactory: class ConstructionFactory:
@ -23,9 +23,6 @@ class ConstructionFactory:
def _nrcan(self): def _nrcan(self):
CaPhysicsParameters(self._city, self._base_path).enrich_buildings() CaPhysicsParameters(self._city, self._base_path).enrich_buildings()
def _other_construction_library_format(self):
raise NotImplementedError
def enrich(self): def enrich(self):
""" """
Enrich the city with the construction information Enrich the city with the construction information

View File

@ -20,13 +20,10 @@ class UsageFactory:
self._base_path = base_path self._base_path = base_path
def _hft(self): def _hft(self):
HftUsageParameters(self._city, self._base_path).enrich_buildings() return HftUsageParameters(self._city, self._base_path).enrich_buildings()
def _ca(self): def _ca(self):
CaUsageParameters(self._city, self._base_path).enrich_buildings() return CaUsageParameters(self._city, self._base_path).enrich_buildings()
def _other_usage_library_format(self):
raise Exception('Not implemented')
def enrich(self): def enrich(self):
""" """

View File

@ -19,23 +19,16 @@ class WeatherFactory:
self._base_path = base_path self._base_path = base_path
self._file_name = file_name self._file_name = file_name
def _tmy3(self):
raise Exception('Not implemented')
def _tm2(self):
# Meteonorm (https://meteonorm.com/en/product/typical-years)
raise Exception('Not implemented')
def _epw(self): def _epw(self):
# EnergyPlus Weather # EnergyPlus Weather
# to download files: https://energyplus.net/weather # to download files: https://energyplus.net/weather
# description of the format: https://energyplus.net/sites/default/files/pdfs_v8.3.0/AuxiliaryPrograms.pdf # description of the format: https://energyplus.net/sites/default/files/pdfs_v8.3.0/AuxiliaryPrograms.pdf
_path = Path(self._base_path / 'epw').resolve() _path = Path(self._base_path / 'epw').resolve()
EpwWeatherParameters(self._city, _path, self._file_name) return EpwWeatherParameters(self._city, _path, self._file_name)
def _xls(self): def _xls(self):
name = 'ISO_52016_1_BESTEST_ClimData_2016.08.24' name = 'ISO_52016_1_BESTEST_ClimData_2016.08.24'
XlsWeatherParameters(self._city, self._base_path, name) return XlsWeatherParameters(self._city, self._base_path, name)
def enrich(self): def enrich(self):
""" """

View File

@ -52,3 +52,9 @@ class TestSchedulesFactory(TestCase):
city = self._get_citygml(file) city = self._get_citygml(file)
occupancy_handler = 'doe_idf' occupancy_handler = 'doe_idf'
SchedulesFactory(occupancy_handler, city).enrich() SchedulesFactory(occupancy_handler, city).enrich()
for building in city.buildings:
for usage_zone in building.usage_zones:
for schedule in usage_zone.schedules:
print(schedule)
print(usage_zone.schedules[schedule])