Module: RGeo::ImplHelper::ValidOpHelpers

Included in:
Cartesian::ValidOpHelpers
Defined in:
lib/rgeo/impl_helper/valid_op.rb

Overview

Helper functions for specific validity checks

Class Method Summary collapse

Class Method Details

.check_connected_interiors(poly) ⇒ String

Checks that the interior of the polygon is connected. A disconnected interior can be described by this polygon for example POLYGON((0 0, 10 0, 10 10, 0 10, 0 0), (5 0, 10 5, 5 10, 0 5, 5 0))

Which is a square with a diamond inside of it.

Parameters:

Returns:

  • (String)

    invalid_reason



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/rgeo/impl_helper/valid_op.rb', line 304

def check_connected_interiors(poly)
  # This is not proper and will flag valid geometries as invalid, but
  # is an ok approximation.
  # Idea is to check if a single hole has multiple points on the
  # exterior ring.
  poly.interior_rings.each do |ring|
    touches = Set.new
    ring.points.each do |pt|
      touches.add(pt) if poly.exterior_ring.contains?(pt)
    end

    return Error::DISCONNECTED_INTERIOR if touches.size > 1
  end

  nil
end

.check_consistent_area(poly) ⇒ String

Checks that the edges in the polygon form a consistent area.

Specifically, checks that there are intersections no between the holes and the shell.

Also checks that there are no duplicate rings.

Parameters:

Returns:

  • (String)

    invalid_reason



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/rgeo/impl_helper/valid_op.rb', line 205

def check_consistent_area(poly)
  # Holes don't cross exterior check.
  exterior = poly.exterior_ring
  poly.interior_rings.each do |ring|
    return Error::SELF_INTERSECTION if ring.crosses?(exterior)
  end

  # check interiors do not cross
  poly.interior_rings.combination(2).each do |ring1, ring2|
    return Error::SELF_INTERSECTION if ring1.crosses?(ring2)
  end

  # Duplicate rings check
  rings = [exterior] + poly.interior_rings
  return Error::SELF_INTERSECTION if rings.uniq.size != rings.size

  nil
end

.check_consistent_area_mp(mpoly) ⇒ String

Checks that polygons do not intersect in a multipolygon.

Parameters:

Returns:

  • (String)

    invalid_reason



326
327
328
329
330
331
# File 'lib/rgeo/impl_helper/valid_op.rb', line 326

def check_consistent_area_mp(mpoly)
  mpoly.geometries.combination(2) do |p1, p2|
    return Error::SELF_INTERSECTION if p1.exterior_ring.crosses?(p2.exterior_ring)
  end
  nil
end

.check_holes_in_shell(poly) ⇒ String

Checks holes are contained inside the exterior of a polygon. Assuming check_consistent_area has already passed on the polygon, a simple point in polygon check can be done on one of the points in each hole to verify (since we know none of them intersect).

Parameters:

Returns:

  • (String)

    invalid_reason



261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/rgeo/impl_helper/valid_op.rb', line 261

def check_holes_in_shell(poly)
  # get hole-less shell as test polygon
  shell = poly.exterior_ring
  shell = shell.factory.polygon(shell)

  poly.interior_rings.each do |interior|
    test_pt = interior.start_point
    return Error::HOLE_OUTSIDE_SHELL unless shell.contains?(test_pt) || poly.exterior_ring.contains?(test_pt)
  end

  nil
end

.check_holes_not_nested(poly) ⇒ String

Checks that holes are not nested within each other.

Parameters:

Returns:

  • (String)

    invalid_reason



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

def check_holes_not_nested(poly)
  # convert holes from linear_rings to polygons
  # Same logic that applies to check_holes_in_shell applies here
  # since we've already passed the consistent area test, we just
  # have to check if one point from each hole is contained in the other.
  holes = poly.interior_rings
  holes = holes.map { |v| v.factory.polygon(v) }
  holes.combination(2).each do |p1, p2|
    if p1.contains?(p2.exterior_ring.start_point) || p2.contains?(p1.exterior_ring.start_point)
      return Error::NESTED_HOLES
    end
  end

  nil
end

.check_invalid_coordinate(point) ⇒ String

Checks that the given point has valid coordinates.

Parameters:

Returns:

  • (String)

    invalid_reason



187
188
189
190
191
192
193
# File 'lib/rgeo/impl_helper/valid_op.rb', line 187

def check_invalid_coordinate(point)
  x = point.x
  y = point.y
  return if x.finite? && y.finite? && x.real? && y.real?

  Error::INVALID_COORDINATE
end

.check_no_self_intersecting_rings(poly) ⇒ String

Check that rings do not self intersect in a polygon

Parameters:

Returns:

  • (String)

    invalid_reason



239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/rgeo/impl_helper/valid_op.rb', line 239

def check_no_self_intersecting_rings(poly)
  exterior = poly.exterior_ring

  check = check_no_self_intersections(exterior)
  return check unless check.nil?

  poly.interior_rings.each do |ring|
    check = check_no_self_intersections(ring)
    return check unless check.nil?
  end

  nil
end

.check_no_self_intersections(ring) ⇒ String

Checks that the ring does not self-intersect. This is just a simplicity check on the ring.

Parameters:

Returns:

  • (String)

    invalid_reason



230
231
232
# File 'lib/rgeo/impl_helper/valid_op.rb', line 230

def check_no_self_intersections(ring)
  return Error::SELF_INTERSECTION unless ring.simple?
end

.check_shells_not_nested(mpoly) ⇒ String

Checks that individual polygons within a multipolygon are not nested.

Parameters:

Returns:

  • (String)

    invalid_reason



338
339
340
341
342
343
344
345
346
347
# File 'lib/rgeo/impl_helper/valid_op.rb', line 338

def check_shells_not_nested(mpoly)
  # Since we've passed the consistent area test, we can just check
  # that one point lies in the other.
  mpoly.geometries.combination(2) do |p1, p2|
    if p1.contains?(p2.exterior_ring.start_point) || p2.contains?(p1.exterior_ring.start_point)
      return Error::NESTED_SHELLS
    end
  end
  nil
end