module URBANopt::GeoJSON::Helper

Public Class Methods

adjust_vertices_to_area(vertices, desired_area, runner, eps = 0.1) click to toggle source

Used to scale footprint to desired area while keeping the original shape.

Parameters
  • vertices - Type:Array - An array of vertices for the original floorprint

  • desired_area - Type:String - Area to which you want to scale the vertices to

  • runner - Type:String - An instance of Openstudio::Measure::OSRunner for the measure run.

# File lib/urbanopt/geojson/helper.rb, line 215
def self.adjust_vertices_to_area(vertices, desired_area, runner, eps = 0.1)
  ar = ScaleArea.new(vertices, desired_area, runner, eps)

  n = Newton.nlsolve(ar, [0])

  return ar.new_vertices
end
convert_to_shading_surface_group(space) click to toggle source

This method loops though all the surfaces of the space and creates shading surfaces. It also removes the thermal zone and space type assigned to the space, if any.

Returns an Array of instances of OpenStudio::Model::ShadingSurfaceGroup .

Used to convert adjacent buildings to shading surfaces.

Parameters
  • space - Type:String - An instance of OpenStudio::Model::Space .

# File lib/urbanopt/geojson/helper.rb, line 19
def self.convert_to_shading_surface_group(space)
  name = space.name.to_s
  model = space.model
  shading_group = OpenStudio::Model::ShadingSurfaceGroup.new(model)
  space.surfaces.each do |surface|
    shading_surface = OpenStudio::Model::ShadingSurface.new(surface.vertices, model)
    shading_surface.setShadingSurfaceGroup(shading_group)
  end
  thermal_zone = space.thermalZone
  if !thermal_zone.empty?
    thermal_zone.get.remove
  end
  space_type = space.spaceType
  space.remove
  if !space_type.empty? && space_type.get.spaces.empty?
    space_type.get.remove
  end
  shading_group.setName(name)
  return [shading_group]
end
create_photovoltaics(feature, height, model, origin_lat_lon, runner) click to toggle source

Returns array containing instance of OpenStudio::Model::ShadingSurface .

Used to create Photovoltaics and assign efficiency.

Parameters
  • feature - Type:String - An instance of Feature class.

  • height - Type:Integer - Indicates the building height.

  • model - Type:String - An instance of OpenStudio::Model::Model .

  • origin_lat_lon - Type:Float - An instance of OpenStudio::PointLatLon indicating the origin's latitude & longitude.

  • runner - Type:String - An instance of Openstudio::Measure::OSRunner for the measure run.

# File lib/urbanopt/geojson/helper.rb, line 52
def self.create_photovoltaics(feature, height, model, origin_lat_lon, runner)
  feature_id = feature.feature_json[:properties][:properties]
  name = feature.name
  floor_prints = []
  multi_polygons = feature.get_multi_polygons
  multi_polygons.each do |multi_polygon|
    if multi_polygon.size > 1
      runner.registerWarning('Ignoring holes in polygon')
    end
    multi_polygon.each do |polygon|
      floor_print = floor_print_from_polygon(polygon, height, origin_lat_lon, runner)
      if floor_print
        floor_prints << OpenStudio.reverse(floor_print)
      else
        runner.registerWarning("Cannot create footprint for '#{name}'")
      end
      break
    end
  end
  shading_surfaces = []
  floor_prints.each do |floor_print|
    shading_group = OpenStudio::Model::ShadingSurfaceGroup.new(model)
    shading_surface = OpenStudio::Model::ShadingSurface.new(floor_print, model)
    shading_surface.setShadingSurfaceGroup(shading_group)
    shading_surface.setName('Photovoltaic Panel')
    shading_surfaces << shading_surface
  end
  # create the inverter # :nodoc:
  inverter = OpenStudio::Model::ElectricLoadCenterInverterSimple.new(model)
  inverter.setInverterEfficiency(0.95)
  # create the distribution system # :nodoc:
  elcd = OpenStudio::Model::ElectricLoadCenterDistribution.new(model)
  elcd.setInverter(inverter)
  shading_surfaces.each do |shading_surface|
    panel = OpenStudio::Model::GeneratorPhotovoltaic.simple(model)
    panel.setSurface(shading_surface)
    performance = panel.photovoltaicPerformance.to_PhotovoltaicPerformanceSimple.get
    performance.setFractionOfSurfaceAreaWithActiveSolarCells(1.0)
    performance.setFixedEfficiency(0.3)
    elcd.addGenerator(panel)
  end
  return shading_surfaces
end
create_shading_surfaces(feature, model, origin_lat_lon, runner, spaces) click to toggle source

Returns array containing instance of OpenStudio::Model::ShadingSurface .

Parameters
  • feature - Type:String - An instance of Feature class.

  • model - Type:String - An instance of OpenStudio::Model::Model .

  • origin_lat_lon - Type:Float - An instance of OpenStudio::PointLatLon indicating the origin's latitude and longitude.

  • runner - Type:String - The measure run's instance of OpenStudio::Measure::OSRunner .

  • spaces -Type:Array - Instances of OpenStudio::Model::Space .

# File lib/urbanopt/geojson/helper.rb, line 106
def self.create_shading_surfaces(feature, model, origin_lat_lon, runner, spaces)
  max_z = 0
  spaces.each do |space|
    bb = space.boundingBox
    max_z = [max_z, bb.maxZ.get].max
  end
  return create_photovoltaics(feature, max_z + 1, model, origin_lat_lon, runner)
end
create_space_types(stories, model, runner) click to toggle source

This method loops through all the stories in the model, and returns any space types previously assigned.

Returns array of OpenStudio::Model::SpaceTypes .

Used to create space types for each building story.

Parameters
  • stories - Type:Array - An array of model/building stories.

  • model - Type:String - An instance of OpenStudio::Model::Model .

  • runner - Type:String - The measure run's instance of OpenStudio::Measure::OSRunner .

# File lib/urbanopt/geojson/helper.rb, line 127
def self.create_space_types(stories, model, runner)
  space_types = []
  stories.each_index do |i|
    space_type = nil
    space = stories[i].spaces.first
    if space&.spaceType&.is_initialized
      space_type = space.spaceType.get
    else
      space_type = OpenStudio::Model::SpaceType.new(model)
      runner.registerInfo("Story #{i} does not have a space type, creating new one")
    end
    space_types[i] = space_type
  end
  return space_types
end
floor_print_from_polygon(polygon, elevation, origin_lat_lon, runner, zoning = false, scaled_footprint_area = 0) click to toggle source

Returns an OpenStudio::Point3dVector .

Creates the floor print for a given polygon.

Parameters
  • polygon - Type:Array - An array of coordinate pairs.

e.g.

polygon = [
 [1, 5],
 [5, 5],
 [5, 1],
]
  • elevation - Type:Integer - Indicates the elevation.

  • origin_lat_lon - Type:Float - An instance of OpenStudio::PointLatLon indicating the origin's latitude and longitude.

  • runner - Type:String - The measure run's instance of OpenStudio::Measure::OSRunner .

  • zoning - Type:Boolean - Value is True if utilizing detailed zoning, else False. Zoning is set to False by default.

  • scaled_footprint_area - Used to scale the footprint area using the floor area. 0 by default (no scaling).

# File lib/urbanopt/geojson/helper.rb, line 163
def self.floor_print_from_polygon(polygon, elevation, origin_lat_lon, runner, zoning = false, scaled_footprint_area = 0)
  floor_print = OpenStudio::Point3dVector.new
  all_points = OpenStudio::Point3dVector.new
  polygon.each do |p|
    lon = p[0]
    lat = p[1]
    point_3d = origin_lat_lon.toLocalCartesian(OpenStudio::PointLatLon.new(lat, lon, 0))
    point_3d = OpenStudio::Point3d.new(point_3d.x, point_3d.y, elevation)
    curr_print = zoning ? OpenStudio.getCombinedPoint(point_3d, all_points, 1.0) : point_3d
    floor_print << curr_print
  end
  if floor_print.size < 3
    runner.registerWarning('Cannot create floor print, fewer than 3 points')
    return nil
  end
  floor_print = OpenStudio.removeCollinear(floor_print)
  normal = OpenStudio.getOutwardNormal(floor_print)
  if normal.empty?
    runner.registerWarning('Cannot create floor print, cannot compute outward normal')
    return nil
  elsif normal.get.z > 0
    floor_print = OpenStudio.reverse(floor_print)
    runner.registerWarning('Reversing floor print')
  end

  # check for scaling
  if scaled_footprint_area > 0

    # check that the scaled_footprint_area desired is no less than X % of the original
    original_floor_print_area = OpenStudio.getArea(floor_print).get
    if scaled_footprint_area / original_floor_print_area <= 0.5 || scaled_footprint_area / original_floor_print_area >= 2
      # TOO MUCH SCALING...using original footprint when scaled is 2x bigger or smaller than the original
      runner.registerWarning('Desired scaled_footprint_area is a factor of 2 of the original footprint...keeping original footprint (no scaling!)')
    else
      new_floor_print = adjust_vertices_to_area(floor_print, scaled_footprint_area, runner)
      new_footprint_area = OpenStudio.getArea(new_floor_print).get
      runner.registerInfo("New floor area: #{new_footprint_area}, compared to scaled area desired: #{scaled_footprint_area}")
      floor_print = new_floor_print
    end
  end

  return floor_print
end
is_shadowed(potentially_shaded, potential_shader, origin_lat_lon) click to toggle source

Returns Boolean which indicates whether the specified building is shadowed by other building.

Parameters
  • potentially_shaded - Type:Array - An array of instances of OpenStudio::Point3d .

  • potential_shader - Type:Array - Other array of instances of OpenStudio::Point3d .

  • origin_lat_lon Type:Float - An instance of OpenStudio::PointLatLon indicating the origin's latitude and longitude.

# File lib/urbanopt/geojson/helper.rb, line 301
def self.is_shadowed(potentially_shaded, potential_shader, origin_lat_lon)
  # not using origin_lat_lon but have not removed it yet
  min_distance = nil
  min_pair = nil
  potentially_shaded.each do |building_point|
    potential_shader.each do |other_building_point|
      vector = other_building_point - building_point
      distance = Math.sqrt(vector.x * vector.x + vector.y * vector.y)
      if min_distance.nil? || distance < min_distance
        min_distance = distance
        min_pair = {
          building_point: building_point,
          other_building_point: other_building_point,
          vector: vector,
          distance: vector.length
        }
      end
    end
  end

  if is_shaded(min_pair[:building_point], min_pair[:other_building_point], origin_lat_lon)
    return true
  end
  return false
end
process_other_buildings(building, other_building_type, other_buildings, model, origin_lat_lon, runner, zoning = false) click to toggle source

Calculate which other buildings are shading the current feature and return as an array of OpenStudio::Model::Space.

Parameters
  • building - Type:URBANopt::GeoJSON::Building - The core building that other buildings will be referenced.

  • other_building_type - Type:String - Describes the surrounding buildings.

  • other_buildings - Type:URBANopt::GeoJSON::FeatureCollection - List of surrounding buildings to include (self will be ignored if present in list).

  • model - Type:OpenStudio::Model::Model - An instance of an OpenStudio Model.

  • origin_lat_lon - Type:Float - An instance of OpenStudio::PointLatLon indicating the latitude and longitude of the origin.

  • runner - Type:String - An instance of Openstudio::Measure::OSRunner for the measure run.

  • zoning - Type:Boolean - Value is true if utilizing detailed zoning, else false. Zoning is set to false by default.

# File lib/urbanopt/geojson/helper.rb, line 236
def self.process_other_buildings(building, other_building_type, other_buildings, model, origin_lat_lon, runner, zoning = false)
  # Empty array to store the new OpenStudio model spaces that need to be converted to shading objects
  feature_points = building.feature_points(origin_lat_lon, runner, zoning)

  other_spaces = []
  runner.registerInfo("#{other_buildings[:features].size} nearby buildings found")
  other_buildings[:features].each do |other_building|
    other_id = other_building[:properties][:id]
    next if other_id == building.id
    # Consider building, if other building type is ShadingOnly and other id is not equal to building id
    if other_building_type == 'ShadingOnly' && other_id != building.id
      # Checks if any building point is shaded by any other building point.
      roof_elevation = other_building[:properties][:roof_elevation]
      number_of_stories = other_building[:properties][:number_of_stories]
      number_of_stories_above_ground = other_building[:properties][:number_of_stories_above_ground]
      maximum_roof_height = other_building[:properties][:maximum_roof_height]

      if number_of_stories_above_ground.nil?
        number_of_stories_above_ground = number_of_stories
        number_of_stories_below_ground = 0

      else
        number_of_stories_below_ground = number_of_stories - number_of_stories_above_ground
      end

      floor_to_floor_height = 3
      if number_of_stories_above_ground && number_of_stories_above_ground > 0 && maximum_roof_height
        floor_to_floor_height = maximum_roof_height / number_of_stories_above_ground
      end

      # check that feature has a # stories
      if number_of_stories_above_ground.nil?
        runner.registerWarning("[geojson process_other_buildings] Unable to include feature #{other_building[:properties][:id]} in shading calculations: no 'number of stories' data")
      end
      next if number_of_stories_above_ground.nil?

      other_height = number_of_stories_above_ground * floor_to_floor_height
      # find the polygon of the other_building by passing it to the get_multi_polygons method
      other_building_points = building.other_points(other_building, other_height, origin_lat_lon, runner, zoning)
      shadowed = URBANopt::GeoJSON::Helper.is_shadowed(feature_points, other_building_points, origin_lat_lon)
      if shadowed
        runner.registerInfo("Feature #{other_building[:properties][:id]} is acting as shading object for #{building.id}")
      end
      next unless shadowed
      new_building = building.create_other_building(:space_per_building, model, origin_lat_lon, runner, zoning, 0, other_building)
      if new_building.nil? || new_building.empty?
        runner.registerWarning("Failed to create spaces for other building '#{name}'")
      end
      other_spaces.concat(new_building)

    elsif other_building_type == 'None'
    end
  end
  return other_spaces
end