Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validity Handling #275

Merged
merged 27 commits into from Jan 31, 2022
Merged

Validity Handling #275

merged 27 commits into from Jan 31, 2022

Conversation

keithdoggett
Copy link
Member

Summary

Combination of #262 #269 and #271

@keithdoggett
Copy link
Member Author

GEOS 3.10 reworked their validity handling somewhat and the DUPLICATE_RING error is now folded in with SELF_INTERSECTION.

keithdoggett and others added 22 commits January 13, 2022 18:57
Replace a manual validity check on MultiPolygon, which was iterating on
each polygon combination, by a direct call of GEOSisValid.
…created SweeplineIntersector class to compute intersections on groups of segments.

Segment#segment_intersection sligtly modifies the code from intersects_segment? and actually computes the intersection point (or nil if none).
Edge cases for colinear segments are handled by return a single point from the intersection.
intersects_segment? was modified to just check if segment_intersection is not nil.

A SweeplineIntersector class was added that uses a basic sweepline algorithm to compute the intersections in a set of segments.
This should be faster or as fast as the current validate_geometry intersection algorithm being used (more testing needed), since
it only compares segments where their y-range includes the y value of the event the sweepline is handling.
There are options to return all intersections or proper intersections that will filter out the connections in LineStrings.

In the future, it is possible to make a Sweepline class and have the SweeplineIntersector inherit from it. This would allow
us to use the sweepline more generically in other applications (some polygon clipping algos use a sweepline).
…rved_segments with Set, condense event creation branching.
Adds a PlanarGraph implementation based on a Doubly Connected Edge List (DCEL). A DCEL is a graph data structure of
vertices and half-edges. Half-edges are directed edges where one segment can be represented by a pair of half-edges.
Half-edges are linked so that faces can be traversed (although faces are not stored).

The PlanarGraph accepts an array of segments to build it and additional edges can be added via the add_edge and add_edges methods.
When edges are added, intersections are automatically recomputed and handled. When possible, existing half-edges are preserved
so that geometries can reference those half-edges.

Also adds GeometryGraph which is a convenient way to interface geometries to a planar graph and provides ways to find important
half-edges in each geometry.

I haven't done a full performance test yet, but one notable area of improvement is that everytime an edge or edges is added,
all of the half-edges are re-linked when we likely only need to re-link the half-edges that originate at new vertices.
…idity checking of geometries.

The ValidOp module provides methods based on simple geometric predicates to determine validity for a geometry and can be used as a fallback or overridden.
The module can be overriden fully (in the case of Cartesian polygons) or just by implementing #invalid_reason (FFI and CAPI factories).

Removes #validate_geometry from all classes and replaces them with #prepare_geometry. Any required validations were moved to the constructors.

Adds the ValidityTest module to test/common to ensure that all classes of geometries validate in the same way.
ValidOp is to be included in feature classes and has the invalid_reason and valid? methods, as well as feature type specific methods that walk through the validity flow for each
geometry type.

ValidOpHelpers is a collection of functions where an object is passed to them. These perform the specific checks and return the invalid reason if applicable.

Also added overrides for both modules for cartesian functions.
…nd fix tests, ValidOp so the spherical factory passes validity checks
Co-authored-by: Keith Doggett <keith.doggett887@gmail.com>
if (str) result = (str[0] == '\0' || !strcmp(str, "Valid Geometry")) ? Qnil : rb_str_new2(str);
else result = rb_str_new2("Exception");
GEOSFree_r(self_data->geos_context, str);
// TODO: should we consider using the flag GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keithdoggett since you implemented validity on the ruby part, may I have your opinion on that one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make it inconsistent with ours, but if you're already using GEOS you likely don't care about the ruby implementation, so I think it's ok to let the user pick if they want to allow that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well since users never had this option and no one requested it I'm not sure I'm in favor of that. The question was more : we have to pick one option (either always or never allow), which is the ruby counterpart? I'll try and find an example in the GEOS test suite to answer that question myself

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def ring(pts, factory: RGeo::Geos.factory)
  pts = pts.each_slice(2) if pts.first.is_a?(Numeric) && pts.size.even?
  factory.linear_ring(
    pts.map { factory.point(_1, _2) }
  )
end
[RGeo::Geos.factory, RGeo::Cartesian.simple_factory].each do |f|
	puts ring([0,0,	5,5,	0,10,	10,10,	5,5,	10,0,	0,0], factory: f).invalid_reason
end

Hence I'd be in favor of not adding the flag and sticking to SFS.

Another thing I found however that we may want to fix before releasing v3 is that when calling make_valid on the above ring we get nil. As we want to harmonize and always raise on error, I'd we should raise in that particular case. What do you say?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, gotcha yes, I agree that we should not use that flag and the ring self-intersections be treated as invalid since that is not standard.

Good catch with make_valid return nil. Should definitely raise if it can't make it valid.

I've added our `.yardopts` to make sure our options are used by
rubydoc.info

I've made examples point images to github to be sure we always see an
image, wether we're on rubydoc or github.

Completed TODOs.

Added Klaxit to the gallery :)
@keithdoggett
Copy link
Member Author

@BuonOmo thanks for writing that doc. I added a section at the end that clarifies which types of methods require a validity check first.

BuonOmo and others added 2 commits January 19, 2022 12:14
(1) `make_valid` should raise when it cannot produce a geometry, not
    return nil.

(2) +, -, * operator methods would generate uncallable `unsafe_+`
    methods, hence we alias those with text.
@BuonOmo
Copy link
Member

BuonOmo commented Jan 31, 2022

Merging into v3-dev now since the workflow is satisfying, issues left will be adressed per PR

@BuonOmo BuonOmo merged commit c24255b into v3-dev Jan 31, 2022
@BuonOmo BuonOmo deleted the validity-handling branch January 31, 2022 17:15
BuonOmo added a commit that referenced this pull request Mar 17, 2022
Get rid of `lenient_assertions` in favor of another way of handling validity: never fail fast, fail when
impossible to do otherwise.

Co-authored-by: Keith Doggett <keith.doggett887@gmail.com>
Co-authored-by: Quentin Wentzler <quentin.wentzler@klaxit.com>
Co-authored-by: Ulysse Buonomo <buonomo.ulysse@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants