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.

Instance Attribute Summary collapse

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.



93
94
95
96
97
98
99
100
101
102
# File 'lib/rgeo/cartesian/bounding_box.rb', line 93

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

Instance Attribute Details

#factoryObject (readonly)

Returns the bounding box’s factory.



24
25
26
# File 'lib/rgeo/cartesian/bounding_box.rb', line 24

def factory
  @factory
end

#has_mObject (readonly)

Returns true if this bounding box tracks M coordinates.



30
31
32
# File 'lib/rgeo/cartesian/bounding_box.rb', line 30

def has_m
  @has_m
end

#has_zObject (readonly)

Returns true if this bounding box tracks Z coordinates.



27
28
29
# File 'lib/rgeo/cartesian/bounding_box.rb', line 27

def has_z
  @has_z
end

#max_mObject (readonly)

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



54
55
56
# File 'lib/rgeo/cartesian/bounding_box.rb', line 54

def max_m
  @max_m
end

#max_xObject (readonly)

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



36
37
38
# File 'lib/rgeo/cartesian/bounding_box.rb', line 36

def max_x
  @max_x
end

#max_yObject (readonly)

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



42
43
44
# File 'lib/rgeo/cartesian/bounding_box.rb', line 42

def max_y
  @max_y
end

#max_zObject (readonly)

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



48
49
50
# File 'lib/rgeo/cartesian/bounding_box.rb', line 48

def max_z
  @max_z
end

#min_mObject (readonly)

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



51
52
53
# File 'lib/rgeo/cartesian/bounding_box.rb', line 51

def min_m
  @min_m
end

#min_xObject (readonly)

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



33
34
35
# File 'lib/rgeo/cartesian/bounding_box.rb', line 33

def min_x
  @min_x
end

#min_yObject (readonly)

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



39
40
41
# File 'lib/rgeo/cartesian/bounding_box.rb', line 39

def min_y
  @min_y
end

#min_zObject (readonly)

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



45
46
47
# File 'lib/rgeo/cartesian/bounding_box.rb', line 45

def min_z
  @min_z
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.



71
72
73
74
# File 'lib/rgeo/cartesian/bounding_box.rb', line 71

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.



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

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) ⇒ Object

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



222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/rgeo/cartesian/bounding_box.rb', line 222

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

#add_geometry(geometry) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/rgeo/cartesian/bounding_box.rb', line 348

def add_geometry(geometry)
  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

#center_mObject

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



178
179
180
# File 'lib/rgeo/cartesian/bounding_box.rb', line 178

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.



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

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.



150
151
152
# File 'lib/rgeo/cartesian/bounding_box.rb', line 150

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.



162
163
164
# File 'lib/rgeo/cartesian/bounding_box.rb', line 162

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)


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

def contains?(rhs, opts = {})
  return contains?(BoundingBox.new(@factory).add(rhs)) if Feature::Geometry === rhs

  return true if rhs.empty?

  return false if empty?

  cmp_xymz =
    (@min_x > rhs.min_x || @max_x < rhs.max_x || @min_y > rhs.min_y || @max_y < rhs.max_y) ||
    (@has_m && rhs.has_m && !opts[:ignore_m] && (@min_m > rhs.min_m || @max_m < rhs.max_m)) ||
    (@has_z && rhs.has_z && !opts[:ignore_z] && (@min_z > rhs.min_z || @max_z < rhs.max_z))

  !cmp_xymz
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)


132
133
134
# File 'lib/rgeo/cartesian/bounding_box.rb', line 132

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)


115
116
117
# File 'lib/rgeo/cartesian/bounding_box.rb', line 115

def empty?
  @min_x.nil?
end

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

:nodoc:

Returns:

  • (Boolean)


104
105
106
107
108
109
110
# File 'lib/rgeo/cartesian/bounding_box.rb', line 104

def eql?(other) # :nodoc:
  other.is_a?(BoundingBox) && @factory == other.factory &&
    @min_x == other.min_x && @max_x == other.max_x &&
    @min_y == other.min_y && @max_y == other.max_y &&
    @min_z == other.min_z && @max_z == other.max_z &&
    @min_m == other.min_m && @max_m == other.max_m
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)


124
125
126
# File 'lib/rgeo/cartesian/bounding_box.rb', line 124

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.



184
185
186
187
188
189
190
# File 'lib/rgeo/cartesian/bounding_box.rb', line 184

def m_span
  return unless @has_m

  return 0 unless @max_m

  @max_m - @min_m
end

#max_pointObject

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



208
209
210
211
212
213
214
215
216
# File 'lib/rgeo/cartesian/bounding_box.rb', line 208

def max_point
  return unless @min_x

  extras = []
  extras << @max_z if @has_z
  extras << @max_m if @has_m

  @factory.point(@max_x, @max_y, *extras)
end

#min_pointObject

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



195
196
197
198
199
200
201
202
203
# File 'lib/rgeo/cartesian/bounding_box.rb', line 195

def min_point
  return unless @min_x

  extras = []
  extras << @min_z if @has_z
  extras << @min_m if @has_m

  @factory.point(@min_x, @min_y, *extras)
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.



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/rgeo/cartesian/bounding_box.rb', line 309

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



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
# File 'lib/rgeo/cartesian/bounding_box.rb', line 242

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.



144
145
146
# File 'lib/rgeo/cartesian/bounding_box.rb', line 144

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.



156
157
158
# File 'lib/rgeo/cartesian/bounding_box.rb', line 156

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.



168
169
170
171
172
173
174
# File 'lib/rgeo/cartesian/bounding_box.rb', line 168

def z_span
  return unless @has_z

  return 0 unless @max_z

  @max_z - @min_z
end