Module: URBANopt::GeoJSON::Helper

Defined in:
lib/urbanopt/geojson/helper.rb

Class Method Summary collapse

Class Method Details

.adjust_vertices_to_area(vertices, desired_area, runner, eps = 0.1) ⇒ Object

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.



215
216
217
218
219
220
221
# 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) ⇒ Object

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 .



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 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) ⇒ Object

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.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# 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) ⇒ Object

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 .



106
107
108
109
110
111
112
113
# 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) ⇒ Object

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 .



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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) ⇒ Object

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# 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_shaded(building_point, other_building_point, origin_lat_lon) ⇒ Object

Returns Boolean indicating if specified building is shadowed.

Parameters
  • building_point - Type:Float - An instance of OpenStudio::Point3d .

  • other_building_point - Type:Float - Other instance of OpenStudio::Point3d .

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



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/urbanopt/geojson/helper.rb', line 335

def self.is_shaded(building_point, other_building_point, origin_lat_lon)
  # not using origin_lat_lon but have not removed it yet
  vector = other_building_point - building_point
  distance = Math.sqrt(vector.x * vector.x + vector.y * vector.y)
  if distance < 1
    return true
  end
  elevation_angle = 2.5 #not sure of best value maybe allow as project level argument
  height = vector.z
  apparent_angle_rad = Math.atan2(height, distance)
  apparent_angle = OpenStudio.radToDeg(apparent_angle_rad)
  if elevation_angle < apparent_angle
    result = true
  else
    result = false
  end
  return result
end

.is_shadowed(potentially_shaded, potential_shader, origin_lat_lon) ⇒ Object

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.



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# 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) ⇒ Object

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.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# 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