""" ExportsFactory export a city into several formats SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ import xmltodict import uuid import datetime from pathlib import Path class EnergyAde: def __init__(self, city, path): self._city = city self._path = path self._export() def _export(self): energy_ade = { 'core:CityModel': { '@xmlns:brid':'http://www.opengis.net/citygml/bridge/2.0', '@xmlns:tran':'http://www.opengis.net/citygml/transportation/2.0', '@xmlns:frn':'http://www.opengis.net/citygml/cityfurniture/2.0', '@xmlns:wtr':'http://www.opengis.net/citygml/waterbody/2.0', '@xmlns:sch':'http://www.ascc.net/xml/schematron', '@xmlns:veg':'http://www.opengis.net/citygml/vegetation/2.0', '@xmlns:xlink':'http://www.w3.org/1999/xlink', '@xmlns:tun':'http://www.opengis.net/citygml/tunnel/2.0', '@xmlns:tex':'http://www.opengis.net/citygml/texturedsurface/2.0', '@xmlns:gml':'http://www.opengis.net/gml', '@xmlns:genobj':'http://www.opengis.net/citygml/generics/2.0', '@xmlns:dem':'http://www.opengis.net/citygml/relief/2.0', '@xmlns:app':'http://www.opengis.net/citygml/appearance/2.0', '@xmlns:luse':'http://www.opengis.net/citygml/landuse/2.0', '@xmlns:xAL':'urn:oasis:names:tc:ciq:xsdschema:xAL:2.0', '@xmlns:xsi':'http://www.w3.org/2001/XMLSchema-instance', '@xmlns:smil20lang':'http://www.w3.org/2001/SMIL20/Language', '@xmlns:pbase':'http://www.opengis.net/citygml/profiles/base/2.0', '@xmlns:smil20': 'http://www.w3.org/2001/SMIL20/', '@xmlns:bldg':'http://www.opengis.net/citygml/building/2.0', '@xmlns:energy': "http://www.sig3d.org/citygml/2.0/energy/1.0", '@xmlns:core':'http://www.opengis.net/citygml/2.0', '@xmlns:grp':'http://www.opengis.net/citygml/cityobjectgroup/2.0', 'gml:boundedBy': { 'gml:Envelope': { '@srsName': self._city.srs_name, '@srsDimension': 3, 'gml:lowerCorner': ' '.join([str(e) for e in self._city.lower_corner]), 'gml:upperCorner': ' '.join([str(e) for e in self._city.upper_corner]) } } } } buildings = [] for building in self._city.buildings: building_dic = { 'bldg:Building': { '@gml:id': building.name, 'gml:description': f'Building {building.name} at {self._city.name}', 'gml:name': f'{building.name}', 'core:creationDate': datetime.datetime.now().strftime('%Y-%m-%d') } } building_dic = EnergyAde._measures(building, building_dic) building_dic = EnergyAde._building_geometry(building, building_dic, self._city) building_dic['bldg:Building']['energy:volume'] = { 'energy:VolumeType': { 'energy:type':'grossVolume', 'energy:value': { '@uom': 'm3', 'energy:value': building.volume } } } building_dic['bldg:Building']['energy:referencePoint'] = { 'gml:Point': { '@srsName': self._city.srs_name, '@gml:id': f'GML_{uuid.uuid4()}', 'gml:Pos': f'{" ".join(map(str,building.centroid))}' } } building_dic['bldg:Building']['energy:thermalZone'] = EnergyAde._thermal_zones(building,self._city) buildings.append(building_dic) energy_ade['core:CityModel']['core:cityObjectMember'] = buildings file_name = self._city.name + '_ade.gml' file_path = Path(self._path / file_name).resolve() with open(file_path, 'w' ) as file: file.write(xmltodict.unparse(energy_ade,pretty=True, short_empty_elements=True)) @staticmethod def _measures(building, building_dic): measures = [] measure = EnergyAde._measure(building.heating, 'year', 'Energy demand heating', 'INSEL') if measure is not None: measures.append(measure) measure = EnergyAde._measure(building.cooling, 'year', 'Energy demand cooling', 'INSEL') if measure is not None: measures.append(measure) if len(measures) != 0: building_dic['bldg:Building']['genobj:measureAttribute'] = measures demands = [] for key in building.heating: if key != 'year': demand = EnergyAde._demand(building.heating, key, 'Heating energy', 'INSEL') demands.append(demand) for key in building.cooling: if key != 'year': demand = EnergyAde._demand(building.cooling, key, 'Cooling energy', 'INSEL') demands.append(demand) if len(demands) != 0: building_dic['bldg:Building']['energy:demands'] = demands return building_dic @staticmethod def _measure(measure_dict, key_value, name, source): measure = None if key_value in measure_dict: measure = { '@name': name, 'genobj:value': { '@uom': 'kWh', '#text': ' '.join([str(e/1000) for e in measure_dict[key_value][source]]) } } return measure @staticmethod def _demand(measure_dict, key_value, description, source): demand = { 'energy:EnergyDemand': { '@gml:id': f'GML_{uuid.uuid4()}', 'energy:energyAmount': { 'energy:RegularTimeSeries': { 'energy:variableProperties': { 'energy:TimeValuesProperties': { 'energy:acquisitionMethod': 'simulation', 'energy:source': source, 'energy:thematicDescription': description, }, 'energy:timeInterval': { '@unit': key_value, '#text': '1', }, 'energy:values': { '@uom': 'kWh', '#text': ' '.join([str(float(e) / 1000) for e in measure_dict[key_value][source]]) } } } }, 'energy:endUse': 'spaceHeating' } } return demand @staticmethod def _building_geometry(building, building_dic, city): building_dic['bldg:Building']['bldg:function'] = building.function building_dic['bldg:Building']['bldg:usage'] = ', '.join([u.usage for u in building.usage_zones]) building_dic['bldg:Building']['bldg:yearOfConstruction'] = building.year_of_construction building_dic['bldg:Building']['bldg:roofType'] = building.roof_type building_dic['bldg:Building']['bldg:measuredHeight'] = { '@uom': 'm', '#text': f'{building.max_height}' } building_dic['bldg:Building']['bldg:storeysAboveGround'] = building.storeys_above_ground if building.lod == 1: building_dic = EnergyAde._lod1(building, building_dic, city) elif building.lod == 2: building_dic = EnergyAde._lod2(building, building_dic, city) else: raise NotImplementedError('Only lod 1 and 2 can be exported') return building_dic @staticmethod def _lod1(building, building_dic, city): raise NotImplementedError('Only lod 1 and 2 can be exported') @staticmethod def _lod2(building, building_dic, city): surface_members = [] boundaries = [{ 'gml:Envelope': { '@srsName': city.srs_name, '@srsDimension': 3, 'gml:lowerCorner': ' '.join([str(e) for e in city.lower_corner]), 'gml:upperCorner': ' '.join([str(e) for e in city.upper_corner]) }}] for surface in building.surfaces: surface_member = { '@xlink:href': f'#PolyId{surface.name}'} surface_members.append(surface_member) if surface.type == 'Wall': surface_type = 'bldg:WallSurface' elif surface.type == 'Ground': surface_type = 'bldg:GroundSurface' else: surface_type = 'bldg:RoofSurface' surface_dic = { surface_type: { '@gml:id': f'GML_{uuid.uuid4()}', 'gml:name': f'{surface.name} ({surface.type})', 'gml:boundedBy': { 'gml:Envelope': { '@srsName': city.srs_name, 'gml:lowerCorner': f'{surface.min_x} {surface.min_y} {surface.min_z}', 'gml:upperCorner': f'{surface.max_x} {surface.max_y} {surface.max_z}' } }, 'bldg:lod2MultiSurface': { 'gml:MultiSurface': { '@srsName': city.srs_name, '@gml:id': f'GML_{uuid.uuid4()}', 'surfaceMember': { 'gml:Polygon': { '@srsName': city.srs_name, '@gml:id': f'PolyId{surface.name}', 'gml:exterior': { 'gml:LinearRing': { '@gml:id': f'PolyId{surface.name}_0', 'gml:posList': { '@srsDimension': '3', '@count': len(surface.points) + 1, '#text': f'{" ".join(map(str, surface.points_list))} {" ".join(map(str, surface.points[0]))}' } } } } } } } } } boundaries.append(surface_dic) building_dic['bldg:Building']['bldg:lod2Solid'] = { 'gml:Solid': { '@gml:id': f'GML_{uuid.uuid4()}', 'gml:exterior': { 'gml:CompositeSurface': { '@srsName': city.srs_name, '@gml:id': f'GML_{uuid.uuid4()}', 'gml:surfaceMember': surface_members } } } } building_dic['bldg:Building']['gml:boundedBy'] = boundaries return building_dic @staticmethod def _thermal_zones(building, city): thermal_zones = [] for index, thermal_zone in enumerate(building.thermal_zones): usage_zones = [] for usage_zone in thermal_zone.usage_zones: usage_zones.append({'@xlink:href': f'#GML_{usage_zone.id}'}) thermal_zone_dic = { 'energy:ThermalZone': { '@gml:id': f'GML_{thermal_zone.id}', 'gml:name': f'Thermal zone {index} in {building.name} building', 'energy:contains': [], 'energy:floorArea': { 'energy:FloorArea' : { 'energy:type': 'grossFloorArea', 'energy:value': { '@uom': 'm2', '#text': f'{thermal_zone.floor_area}' } } }, 'energy:volume': { 'energy:VolumeType': { 'energy:type': 'grossVolume', 'energy:value': { '@uom': 'm3', #todo: for now we have just one thermal zone, therefore is the building volume, this need to be changed '#text': f'{building.volume}' } } }, 'energy:isCooled': f'{thermal_zone.is_cooled}', 'energy:isHeated': f'{thermal_zone.is_heated}', } } thermal_zone_dic['energy:ThermalZone']['energy:contains'] = usage_zones thermal_zones.append(thermal_zone_dic) print(thermal_zones) return thermal_zones