Source code for dragonfly_energy.writer

# coding=utf-8
"""Methods to write files for URBANopt simulation from a Model."""
import sys
import os
import re
import json

from ladybug_geometry.geometry2d import Point2D
from ladybug.futil import nukedir, preparedir
from honeybee.config import folders
from honeybee.model import Model as hb_model


[docs]def model_to_urbanopt( model, location, point=Point2D(0, 0), shade_distance=None, use_multiplier=True, add_plenum=False, solve_ceiling_adjacencies=False, des_loop=None, electrical_network=None, road_network=None, ground_pv=None, folder=None, tolerance=0.01 ): r"""Generate an URBANopt feature geoJSON and honeybee JSONs from a dragonfly Model. Args: model: A dragonfly Model for which an URBANopt feature geoJSON and corresponding honeybee Model JSONs will be returned. location: A ladybug Location object possessing longitude and latitude data. point: A ladybug_geometry Point2D for where the location object exists within the space of a scene. The coordinates of this point are expected to be in the units of this Model. (Default: (0, 0)). shade_distance: An optional number to note the distance beyond which other objects' shade should not be exported into a given honeybee Model. This is helpful for reducing the simulation run time of each Model when other connected buildings are too far away to have a meaningful impact on the results. If None, all other buildings will be included as context shade in each and every Model. Set to 0 to exclude all neighboring buildings from the resulting models. (Default: None). use_multiplier: If True, the multipliers on the Model's Stories will be passed along to the generated Honeybee Room objects, indicating the simulation will be run once for each unique room and then results will be multiplied. If False, full geometry objects will be written for each and every floor in the building that are represented through multipliers and all resulting multipliers will be 1. (Default: True). add_plenum: Boolean to indicate whether ceiling/floor plenums should be auto-generated for the Rooms. (Default: False). solve_ceiling_adjacencies: Boolean to note whether adjacencies should be solved between interior stories when Room2Ds perfectly match one another in their floor plate. This ensures that Surface boundary conditions are used instead of Adiabatic ones. Note that this input has no effect when the object_per_model is Story. (Default: False). des_loop: An optional District Energy System (DES) ThermalLoop that's associated with the dragonfly Model. (Default: None). electrical_network: An optional OpenDSS ElectricalNetwork that's associated with the dragonfly Model. (Default: None). road_network: An optional RNM RoadNetwork that's associated with the dragonfly Model. (Default: None). ground_pv: An optional list of REopt GroundMountPV objects representing ground-mounted photovoltaic fields to be included in the REopt simulation. (Default: None). folder: An optional folder to be used as the root of the model's URBANopt folder. If None, the files will be written into a sub-directory of the honeybee-core default_simulation_folder. tolerance: The minimum distance between points at which they are not considered touching. (Default: 0.01, suitable for objects in meters). Returns: A tuple with three values. feature_geojson -- The path to an URBANopt feature geoJSON that has been written by this method. hb_model_jsons -- An array of file paths to honeybee Model JSONs that correspond to the detailed_model_filename keys in the feature_geojson. hb_models -- An array of honeybee Model objects that were generated in process of writing the URBANopt files. """ # make sure the model is in meters and, if it's not, duplicate and scale it conversion_factor, original_units = None, 'Meters' if model.units != 'Meters': original_units = model.units conversion_factor = hb_model.conversion_factor_to_meters(model.units) point = point.scale(conversion_factor) if shade_distance is not None: shade_distance = shade_distance * conversion_factor tolerance = tolerance * conversion_factor model = model.duplicate() # duplicate the model to avoid mutating the input model.convert_to_units('Meters') if des_loop is not None: des_loop.scale(conversion_factor) if electrical_network is not None: electrical_network.scale(conversion_factor) if road_network is not None: road_network.scale(conversion_factor) if ground_pv is not None: for g_pv in ground_pv: g_pv.scale(conversion_factor) # prepare the folder for simulation tr_msg = 'The following simulation folder is too long to be used with URBANopt:' \ '\n{}\nSpecify a shorter folder path in which to write the GeoJSON.' if folder is None: # use the default simulation folder assert len(folders.default_simulation_folder) < 55, \ tr_msg.format(folders.default_simulation_folder) sim_dir = re.sub(r'[^.A-Za-z0-9_-]', '_', model.display_name) folder = os.path.join(folders.default_simulation_folder, sim_dir) if len(folder) >= 60: tr_len = 58 - len(folders.default_simulation_folder) folder = os.path.join(folders.default_simulation_folder, sim_dir[:tr_len]) else: assert len(folder) < 60, tr_msg.format(folder) # get rid of all simulation files that exists in the folder already dir_to_delete = ('hb_json', 'mappers', 'run') ext_to_delete = ('.bat', '.geojson', '.epw', '.mos') file_to_delete = ( 'Gemfile', 'Gemfile.lock', 'honeybee_scenario.csv', 'runner.conf', 'simulation_parameter.json', 'system_params.json', 'electrical_database.json', 'network.json' ) if os.path.isdir(folder): files = os.listdir(folder) for f in files: path = os.path.join(folder, f) if os.path.isdir(path): if f in dir_to_delete: nukedir(path, True) else: if f in file_to_delete: os.remove(path) elif f.endswith(ext_to_delete): os.remove(path) else: preparedir(folder) # create the directory if it's not there # prepare the folder into which honeybee Model JSONs will be written hb_model_folder = os.path.join(folder, 'hb_json') # folder for honeybee JSONs preparedir(hb_model_folder) # create GeoJSON dictionary geojson_dict = model.to_geojson_dict(location, point, tolerance=tolerance) for feature_dict in geojson_dict['features']: # add the detailed model filename if feature_dict['properties']['type'] == 'Building': bldg_id = feature_dict['properties']['id'] feature_dict['properties']['detailed_model_filename'] = \ os.path.join(hb_model_folder, '{}.json'.format(bldg_id)) # add the DES to the GeoJSON dictionary if des_loop is not None: if hasattr(des_loop, 'to_geojson_dict'): des_features = des_loop.to_geojson_dict( model.buildings, location, point, tolerance=tolerance) geojson_dict['features'].extend(des_features) sys_p_json = os.path.join(folder, 'system_params.json') with open(sys_p_json, 'w') as fp: des_dict = des_loop.to_des_param_dict(model.buildings, tolerance=tolerance) json.dump(des_dict, fp, indent=2) if conversion_factor is not None: des_loop.scale(1 / conversion_factor) # add the electrical network to the GeoJSON dictionary if electrical_network is not None: electric_features = electrical_network.to_geojson_dict( model.buildings, location, point, tolerance=tolerance) geojson_dict['features'].extend(electric_features) electric_json = os.path.join(folder, 'electrical_database.json') with open(electric_json, 'w') as fp: json.dump(electrical_network.to_electrical_database_dict(), fp, indent=4) if conversion_factor is not None: electrical_network.scale(1 / conversion_factor) # add the road network to the GeoJSON dictionary if road_network is not None: road_features = road_network.to_geojson_dict(location, point) geojson_dict['features'].extend(road_features) if conversion_factor is not None: road_network.scale(1 / conversion_factor) # add the ground-mounted PV to the GeoJSON dictionary if ground_pv is not None and len(ground_pv) != 0: pv_features = [g_pv.to_geojson_dict(location, point) for g_pv in ground_pv] geojson_dict['features'].extend(pv_features) if conversion_factor is not None: for g_pv in ground_pv: g_pv.scale(1 / conversion_factor) # write out the GeoJSON file feature_geojson = os.path.join(folder, '{}.geojson'.format(model.identifier)) if (sys.version_info < (3, 0)): # we need to manually encode it as UTF-8 with open(feature_geojson, 'wb') as fp: obj_str = json.dumps(geojson_dict, indent=4, ensure_ascii=False) fp.write(obj_str.encode('utf-8')) else: with open(feature_geojson, 'w', encoding='utf-8') as fp: obj_str = json.dump(geojson_dict, fp, indent=4, ensure_ascii=False) # write out the honeybee Model JSONs from the model hb_model_jsons = [] hb_models = model.to_honeybee( 'Building', shade_distance, use_multiplier, add_plenum, solve_ceiling_adjacencies=solve_ceiling_adjacencies, tolerance=tolerance) for bldg_model in hb_models: try: bldg_model.remove_degenerate_geometry(0.01) except ValueError: error = 'Failed to remove degenerate Geometry.\nYour Model units system is: {}. ' \ 'Is this correct?'.format(original_units) raise ValueError(error) model_dict = bldg_model.to_dict(triangulate_sub_faces=True) bldg_model.properties.energy.add_autocal_properties_to_dict(model_dict) bld_path = os.path.join(hb_model_folder, '{}.json'.format(bldg_model.identifier)) if (sys.version_info < (3, 0)): # we need to manually encode it as UTF-8 with open(bld_path, 'wb') as fp: obj_str = json.dumps(model_dict, indent=4, ensure_ascii=False) fp.write(obj_str.encode('utf-8')) else: with open(bld_path, 'w', encoding='utf-8') as fp: obj_str = json.dump(model_dict, fp, indent=4, ensure_ascii=False) hb_model_jsons.append(bld_path) return feature_geojson, hb_model_jsons, hb_models