Class: RGeo::Cartesian::BoundingBox

Inherits:
Object
  • Object
show all
Defined in:
lib/rgeo/cartesian/bounding_box.rb

Overview

This is a bounding box for Cartesian data. The simple cartesian implementation uses this internally to compute envelopes. You may also use it directly to compute and represent bounding boxes.

A bounding box is a set of ranges in each dimension: X, Y, as well as Z and M if supported. You can compute a bounding box for one or more geometry objects by creating a new bounding box object, and adding the geometries to it. You may then query it for the bounds, or use it to determine whether it encloses other geometries or bounding boxes.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(factory_, opts_ = {}) ⇒ BoundingBox

Create a new empty bounding box with the given factory.

The factory defines the coordinate system for the bounding box, and also defines whether it should track Z and M coordinates. All geometries will be cast to this factory when added to this bounding box, and any generated envelope geometry will have this as its factory.

Options include:

:ignore_z

If true, ignore z coordinates even if the factory supports them. Default is false.

:ignore_m

If true, ignore m coordinates even if the factory supports them. Default is false.


96
97
98
99
100
101
102
103
104
105
# File 'lib/rgeo/cartesian/bounding_box.rb', line 96

def initialize(factory_, opts_={})
  @factory = factory_
  if (values_ = opts_[:raw])
    @has_z, @has_m, @min_x, @max_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m = values_
  else
    @has_z = !opts_[:ignore_z] && factory_.property(:has_z_coordinate) ? true : false
    @has_m = !opts_[:ignore_m] && factory_.property(:has_m_coordinate) ? true : false
    @min_x = @max_x = @min_y = @max_y = @min_z = @max_z = @min_m = @max_m = nil
  end
end

Class Method Details

.create_from_geometry(geom_, opts_ = {}) ⇒ Object

Create a bounding box given a geometry to surround. The bounding box will be given the factory of the geometry. You may also provide the same options available to BoundingBox.new.


73
74
75
76
# File 'lib/rgeo/cartesian/bounding_box.rb', line 73

def self.create_from_geometry(geom_, opts_={})
  factory_ = geom_.factory
  new(factory_, opts_)._add_geometry(geom_)
end

.create_from_points(point1_, point2_, opts_ = {}) ⇒ Object

Create a bounding box given two corner points. The bounding box will be given the factory of the first point. You may also provide the same options available to BoundingBox.new.


62
63
64
65
# File 'lib/rgeo/cartesian/bounding_box.rb', line 62

def self.create_from_points(point1_, point2_, opts_={})
  factory_ = point1_.factory
  new(factory_, opts_)._add_geometry(point1_).add(point2_)
end

Instance Method Details

#_add_geometry(geometry_) ⇒ Object

:nodoc:


444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/rgeo/cartesian/bounding_box.rb', line 444

def _add_geometry(geometry_)  # :nodoc:
  case geometry_
  when Feature::Point
    _add_point(geometry_)
  when Feature::LineString
    geometry_.points.each{ |p_| _add_point(p_) }
  when Feature::Polygon
    geometry_.exterior_ring.points.each{ |p_| _add_point(p_) }
  when Feature::MultiPoint
    geometry_.each{ |p_| _add_point(p_) }
  when Feature::MultiLineString
    geometry_.each{ |line_| line_.points.each{ |p_| _add_point(p_) } }
  when Feature::MultiPolygon
    geometry_.each{ |poly_| poly_.exterior_ring.points.each{ |p_| _add_point(p_) } }
  when Feature::GeometryCollection
    geometry_.each{ |g_| _add_geometry(g_) }
  end
  self
end

#_add_point(point_) ⇒ Object

:nodoc:


465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/rgeo/cartesian/bounding_box.rb', line 465

def _add_point(point_)  # :nodoc:
  if @min_x
    x_ = point_.x
    @min_x = x_ if x_ < @min_x
    @max_x = x_ if x_ > @max_x
    y_ = point_.y
    @min_y = y_ if y_ < @min_y
    @max_y = y_ if y_ > @max_y
    if @has_z
      z_ = point_.z
      @min_z = z_ if z_ < @min_z
      @max_z = z_ if z_ > @max_z
    end
    if @has_m
      m_ = point_.m
      @min_m = m_ if m_ < @min_m
      @max_m = m_ if m_ > @max_m
    end
  else
    @min_x = @max_x = point_.x
    @min_y = @max_y = point_.y
    @min_z = @max_z = point_.z if @has_z
    @min_m = @max_m = point_.m if @has_m
  end
end

#add(geometry_) ⇒ Object

Adjusts the extents of this bounding box to encompass the given object, which may be a geometry or another bounding box. Returns self.


311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/rgeo/cartesian/bounding_box.rb', line 311

def add(geometry_)
  case geometry_
  when BoundingBox
    add(geometry_.min_point)
    add(geometry_.max_point)
  when Feature::Geometry
    if geometry_.factory == @factory
      _add_geometry(geometry_)
    else
      _add_geometry(Feature.cast(geometry_, @factory))
    end
  end
  self
end

#center_mObject

Returns the midpoint M, or nil if this bounding box is empty or has no M.


265
266
267
# File 'lib/rgeo/cartesian/bounding_box.rb', line 265

def center_m
  @max_m ? (@max_m + @min_m) * 0.5 : nil
end

#center_xObject

Returns the midpoint X, or nil if this bounding box is empty.


181
182
183
# File 'lib/rgeo/cartesian/bounding_box.rb', line 181

def center_x
  @max_x ? (@max_x + @min_x) * 0.5 : nil
end

#center_yObject

Returns the midpoint Y, or nil if this bounding box is empty.


209
210
211
# File 'lib/rgeo/cartesian/bounding_box.rb', line 209

def center_y
  @max_y ? (@max_y + @min_y) * 0.5 : nil
end

#center_zObject

Returns the midpoint Z, or nil if this bounding box is empty or has no Z.


237
238
239
# File 'lib/rgeo/cartesian/bounding_box.rb', line 237

def center_z
  @max_z ? (@max_z + @min_z) * 0.5 : nil
end

#contains?(rhs_, opts_ = {}) ⇒ Boolean

Returns true if this bounding box contains the given object, which may be a geometry or another bounding box.

Supports these options:

:ignore_z

Ignore the Z coordinate when testing, even if both objects have Z. Default is false.

:ignore_m

Ignore the M coordinate when testing, even if both objects have M. Default is false.

Returns:

  • (Boolean)

371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/rgeo/cartesian/bounding_box.rb', line 371

def contains?(rhs_, opts_={})
  if Feature::Geometry === rhs_
    contains?(BoundingBox.new(@factory).add(rhs_))
  elsif rhs_.empty?
    true
  elsif empty?
    false
  elsif @min_x > rhs_.min_x || @max_x < rhs_.max_x || @min_y > rhs_.min_y || @max_y < rhs_.max_y
    false
  elsif @has_m && rhs_.has_m && !opts_[:ignore_m] && (@min_m > rhs_.min_m || @max_m < rhs_.max_m)
    false
  elsif @has_z && rhs_.has_z && !opts_[:ignore_z] && (@min_z > rhs_.min_z || @max_z < rhs_.max_z)
    false
  else
    true
  end
end

#degenerate?Boolean

Returns true if this bounding box is degenerate. That is, it is nonempty but has zero area because either or both of the X or Y spans are 0.

Returns:

  • (Boolean)

146
147
148
# File 'lib/rgeo/cartesian/bounding_box.rb', line 146

def degenerate?
  @min_x && (@min_x == @max_x || @min_y == @max_y)
end

#empty?Boolean

Returns true if this bounding box is still empty.

Returns:

  • (Boolean)

127
128
129
# File 'lib/rgeo/cartesian/bounding_box.rb', line 127

def empty?
  @min_x.nil?
end

#eql?(rhs_) ⇒ Boolean Also known as: ==

:nodoc:

Returns:

  • (Boolean)

108
109
110
111
112
113
114
# File 'lib/rgeo/cartesian/bounding_box.rb', line 108

def eql?(rhs_)  # :nodoc:
  rhs_.is_a?(BoundingBox) && @factory == rhs_.factory &&
    @min_x == rhs_.min_x && @max_x == rhs_.max_x &&
    @min_y == rhs_.min_y && @max_y == rhs_.max_y &&
    @min_z == rhs_.min_z && @max_z == rhs_.max_z &&
    @min_m == rhs_.min_m && @max_m == rhs_.max_m
end

#factoryObject

Returns the bounding box's factory.


120
121
122
# File 'lib/rgeo/cartesian/bounding_box.rb', line 120

def factory
  @factory
end

#has_mObject

Returns true if this bounding box tracks M coordinates.


160
161
162
# File 'lib/rgeo/cartesian/bounding_box.rb', line 160

def has_m
  @has_m
end

#has_zObject

Returns true if this bounding box tracks Z coordinates.


153
154
155
# File 'lib/rgeo/cartesian/bounding_box.rb', line 153

def has_z
  @has_z
end

#infinitesimal?Boolean

Returns true if this bounding box is degenerate. That is, it is nonempty but contains only a single point because both the X and Y spans are 0. Infinitesimal boxes are also always degenerate.

Returns:

  • (Boolean)

137
138
139
# File 'lib/rgeo/cartesian/bounding_box.rb', line 137

def infinitesimal?
  @min_x && @min_x == @max_x && @min_y == @max_y
end

#m_spanObject

Returns the M span, 0 if this bounding box is empty, or nil if it has no M.


272
273
274
# File 'lib/rgeo/cartesian/bounding_box.rb', line 272

def m_span
  @has_m ? (@max_m ? @max_m - @min_m : 0) : nil
end

#max_mObject

Returns the maximum M, or nil if this bounding box is empty.


258
259
260
# File 'lib/rgeo/cartesian/bounding_box.rb', line 258

def max_m
  @max_m
end

#max_pointObject

Returns a point representing the maximum extent in all dimensions, or nil if this bounding box is empty.


295
296
297
298
299
300
301
302
303
304
# File 'lib/rgeo/cartesian/bounding_box.rb', line 295

def max_point
  if @min_x
    extras_ = []
    extras_ << @max_z if @has_z
    extras_ << @max_m if @has_m
    @factory.point(@max_x, @max_y, *extras_)
  else
    nil
  end
end

#max_xObject

Returns the maximum X, or nil if this bounding box is empty.


174
175
176
# File 'lib/rgeo/cartesian/bounding_box.rb', line 174

def max_x
  @max_x
end

#max_yObject

Returns the maximum Y, or nil if this bounding box is empty.


202
203
204
# File 'lib/rgeo/cartesian/bounding_box.rb', line 202

def max_y
  @max_y
end

#max_zObject

Returns the maximum Z, or nil if this bounding box is empty.


230
231
232
# File 'lib/rgeo/cartesian/bounding_box.rb', line 230

def max_z
  @max_z
end

#min_mObject

Returns the minimum M, or nil if this bounding box is empty.


251
252
253
# File 'lib/rgeo/cartesian/bounding_box.rb', line 251

def min_m
  @min_m
end

#min_pointObject

Returns a point representing the minimum extent in all dimensions, or nil if this bounding box is empty.


280
281
282
283
284
285
286
287
288
289
# File 'lib/rgeo/cartesian/bounding_box.rb', line 280

def min_point
  if @min_x
    extras_ = []
    extras_ << @min_z if @has_z
    extras_ << @min_m if @has_m
    @factory.point(@min_x, @min_y, *extras_)
  else
    nil
  end
end

#min_xObject

Returns the minimum X, or nil if this bounding box is empty.


167
168
169
# File 'lib/rgeo/cartesian/bounding_box.rb', line 167

def min_x
  @min_x
end

#min_yObject

Returns the minimum Y, or nil if this bounding box is empty.


195
196
197
# File 'lib/rgeo/cartesian/bounding_box.rb', line 195

def min_y
  @min_y
end

#min_zObject

Returns the minimum Z, or nil if this bounding box is empty.


223
224
225
# File 'lib/rgeo/cartesian/bounding_box.rb', line 223

def min_z
  @min_z
end

#subdivide(opts_ = {}) ⇒ Object

Returns this bounding box subdivided, as an array of bounding boxes. If this bounding box is empty, returns the empty array. If this bounding box is a point, returns a one-element array containing the current point. If the x or y span is 0, bisects the line. Otherwise, generally returns a 4-1 subdivision in the X-Y plane. Does not subdivide on Z or M.

:bisect_factor

An optional floating point value that should be greater than 1.0. If the ratio between the larger span and the smaller span is greater than this factor, the bounding box is divided only in half instead of fourths.


404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/rgeo/cartesian/bounding_box.rb', line 404

def subdivide(opts_={})
  return [] if empty?
  if infinitesimal?
    return [
      BoundingBox.new(@factory, :raw => [@has_z, @has_m,
        @min_x, @max_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m])
    ]
  end
  factor_ = opts_[:bisect_factor]
  factor_ ||= 1 if degenerate?
  if factor_
    if x_span > y_span * factor_
      return [
        BoundingBox.new(@factory, :raw => [@has_z, @has_m,
          @min_x, center_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m]),
        BoundingBox.new(@factory, :raw => [@has_z, @has_m,
          center_x, @max_x, @min_y, @max_y, @min_z, @max_z, @min_m, @max_m])
      ]
    elsif y_span > x_span * factor_
      return [
        BoundingBox.new(@factory, :raw => [@has_z, @has_m,
          @min_x, @max_x, @min_y, center_y, @min_z, @max_z, @min_m, @max_m]),
        BoundingBox.new(@factory, :raw => [@has_z, @has_m,
          @min_x, @max_x, center_y, @max_y, @min_z, @max_z, @min_m, @max_m])
      ]
    end
  end
  [
    BoundingBox.new(@factory, :raw => [@has_z, @has_m,
      @min_x, center_x, @min_y, center_y, @min_z, @max_z, @min_m, @max_m]),
    BoundingBox.new(@factory, :raw => [@has_z, @has_m,
      center_x, @max_x, @min_y, center_y, @min_z, @max_z, @min_m, @max_m]),
    BoundingBox.new(@factory, :raw => [@has_z, @has_m,
      @min_x, center_x, center_y, @max_y, @min_z, @max_z, @min_m, @max_m]),
    BoundingBox.new(@factory, :raw => [@has_z, @has_m,
      center_x, @max_x, center_y, @max_y, @min_z, @max_z, @min_m, @max_m])
  ]
end

#to_geometryObject

Converts this bounding box to an envelope, which will be the empty collection (if the bounding box is empty), a point (if the bounding box is not empty but both spans are 0), a line (if only one of the two spans is 0) or a polygon (if neither span is 0).


332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/rgeo/cartesian/bounding_box.rb', line 332

def to_geometry
  if @min_x
    extras_ = []
    extras_ << @min_z if @has_z
    extras_ << @min_m if @has_m
    point_min_ = @factory.point(@min_x, @min_y, *extras_)
    if infinitesimal?
      point_min_
    else
      extras_ = []
      extras_ << @max_z if @has_z
      extras_ << @max_m if @has_m
      point_max_ = @factory.point(@max_x, @max_y, *extras_)
      if degenerate?
        @factory.line(point_min_, point_max_)
      else
        @factory.polygon(@factory.linear_ring([point_min_,
          @factory.point(@max_x, @min_y, *extras_), point_max_,
          @factory.point(@min_x, @max_y, *extras_), point_min_]))
      end
    end
  else
    @factory.collection([])
  end
end

#x_spanObject

Returns the X span, or 0 if this bounding box is empty.


188
189
190
# File 'lib/rgeo/cartesian/bounding_box.rb', line 188

def x_span
  @max_x ? @max_x - @min_x : 0
end

#y_spanObject

Returns the Y span, or 0 if this bounding box is empty.


216
217
218
# File 'lib/rgeo/cartesian/bounding_box.rb', line 216

def y_span
  @max_y ? @max_y - @min_y : 0
end

#z_spanObject

Returns the Z span, 0 if this bounding box is empty, or nil if it has no Z.


244
245
246
# File 'lib/rgeo/cartesian/bounding_box.rb', line 244

def z_span
  @has_z ? (@max_z ? @max_z - @min_z : 0) : nil
end