Skip to content

Conversation

@pramsey
Copy link
Member

@pramsey pramsey commented Apr 16, 2021

Port of locationtech/jts#704

Adds a new approach to making valid geometry.

@dr-jts
Copy link
Contributor

dr-jts commented Apr 16, 2021

Also see RFC for JTS implementation.

Copy link
Contributor

@rouault rouault left a comment

Choose a reason for hiding this comment

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

Sorry for the very boring review, mostly/only on stylistic things. Feel free to keep or ditch :-) I didn't try to review the alg itself.

@dr-jts
Copy link
Contributor

dr-jts commented Apr 16, 2021

The major difference between GeometryFixer and MakeValid is the handling of invalid polygons with self-intersections and overlaps Here's some examples of how GeometryFixer handles invalid polygonal geometry:

Polygon with self-overlap

image

MakeValid result:

image

Polygon with overlapping holes

image

MakeValid result:
image

MultiPolygon with overlapping elements

image

MakeValid result:
image

@dbaston
Copy link
Member

dbaston commented Apr 16, 2021

Thoughts on just having GEOSMakeValid delegate to this implementation?

@pramsey
Copy link
Member Author

pramsey commented Apr 16, 2021

I'm interested in hearing from our friends on postgis-devel, the old MakeValid behaviour was very specific due to requirements from a funder, and I'm not sure if a change in behaviour is acceptable to some. Same goes for all of downstream, though I do in general think the new results are (a) faster and (b) more aesthetically pleasing by and large. Past a certain level of awfulness the question "what is the valid form" becomes very open to interpretation.

{
if (holes == nullptr || holes->isEmpty())
return shell->clone();
return operation::overlayng::OverlayNGRobust::Overlay(shell, holes,
Copy link
Member

Choose a reason for hiding this comment

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

Why call a specific implementation instead of shell->difference(holes) ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Just following the JTS implementation... I guess this one has the fallbacks for sure (which the default does now too!)

Copy link
Contributor

Choose a reason for hiding this comment

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

This is because in JTS OverlayNG is not (yet) the default. In GEOS it's fine to use the Geometry.difference() method.

* @param poly1 a polygonal geometry (which may be empty)
* @return a combined polygonal geometry
*/
static geom::Geometry* combine(
Copy link
Member

Choose a reason for hiding this comment

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

Redundant w/GeometryCombiner ?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, because the combine method also contains logic to restrict to polygons and handle empty results appropriately for this context.

Anyway it looks like @pramsey changed this code to be inline. So this declaration should be removed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep

@dbaston
Copy link
Member

dbaston commented Apr 17, 2021

I'm interested in hearing from our friends on postgis-devel, the old MakeValid behaviour was very specific due to requirements from a funder, and I'm not sure if a change in behaviour is acceptable to some. Same goes for all of downstream, though I do in general think the new results are (a) faster and (b) more aesthetically pleasing by and large. Past a certain level of awfulness the question "what is the valid form" becomes very open to interpretation.

How about two signatures:

GEOSGeometry* GEOSMakeValid(GEOSGeometry* g) // delegates to GeometryFixer

GEOSGeometry* GEOSMakeValidWithParams(GEOSGeometry* g, GEOSMakeValidParams* gp) 

The second one is analogous to GEOSBufferWithParams. The GEOSMakeValidParams structure could include variables for keepCollapsed and algorithmVersion and could be extended to include more parameters in the future if needed, without breaking the signature. This is API is a bit more difficult (you need a GEOSMakeValidParams_create, etc.) but

  • the number of users that require a specific behavior is small; they can deal with a verbose API
  • the term MakeValid is pretty entrenched at this point and introducing a new one is likely to cause confusion
  • without taking over the established GEOSMakeValid signature, I'm not sure how much usage GeometryFixer is going to see.

That said, I've no real horse in this race -- my geometries are always valid 😉

@pramsey
Copy link
Member Author

pramsey commented Apr 17, 2021

I like the idea of one signature with parameters to make the old behaviour still available, unfortunately that doesn't change the fact that the default behaviour will alter (surprise!) which for some folks is a no-go. I'll poke this up on postgis-devel so we can get a read on it.

@nyalldawson
Copy link

How does this fixing method handle z values - are they discarded? (I can't see any examples in the unit tests with z)

@dr-jts
Copy link
Contributor

dr-jts commented Apr 19, 2021

How does this fixing method handle z values - are they discarded? (I can't see any examples in the unit tests with z)

Yes, Z handling needs to be looked at. It probably mostly works, but that needs to be tested and any shortcomings fixed.

capi/geos_c.h.in Outdated
* Options structure to fill out when passing options
* to GEOSMakeValidWithOptions.
*/
typedef struct {
Copy link
Member

Choose a reason for hiding this comment

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

Does this need to be defined in the implementation file only so that the layout is defined by the library we link against rather than the header we built against?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it needs to be in the header, or the user can't form the right struct to pass in. I think I may have over-thought this though, copying the GDAL api for grid interpolation, which has 5 different algorithms each of which is several distinct options. But here I have two algorithms and only one currently has an option. A single struct with all the possible options makes more sense, and can avoid the void* in the call.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is GEOSBufferParams a pattern that can be copied? Pretty verbose tho...

Copy link
Member Author

Choose a reason for hiding this comment

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

There's a level of conditionality in this (if algorithm A then options A, if B then options for B) that doesn't exist in the buffer case (which might have been better handled in named parameters, frankly, precisely because the options are fixed, but named parameters didn't exist at the time). I'm probably going to write up something a little more general for this form of options string like the GDAL CSList so at least they can share some utility code.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a point. Although all the optionality exists on the GeoemetryFixer side, and at the moment MakeValid gets the no-option signature. So the options can just apply to GeometryFixer. If and when the default is changed, then we can add an option that indicates "use the MakeValid algorithm".

Copy link
Member

Choose a reason for hiding this comment

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

I tried to keep structs out of that header as much as possible. Opaque void pointers are what made it possible so far to avoid breaking the ABI

Copy link
Member Author

Choose a reason for hiding this comment

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

Ug. Makes me want to just have two function end points, less cluttered than having a whole new options object with creators/destructors/setters/getters for what is, currently, two options (algorithm and keepCollapsed).

Copy link
Member

Choose a reason for hiding this comment

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

It's the price of a C API, I think.

capi/geos_c.h.in Outdated
* \param makeValidOptions to control the behaviour or the repair method.
* \return A repaired geometry. Caller must free with GEOSGeom_destroy().
*/
extern GEOSGeometry GEOS_DLL *GEOSMakeValidWithOptions(
Copy link
Member

Choose a reason for hiding this comment

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

I'd call this GEOSMakeValidWithParams for similarity to GEOSBufferWithParams, unless there is some distinction between "options" and "params"

pramsey added 2 commits April 22, 2021 15:43
…n opaque params object instead of a header struct, following BufferParams model.
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.

6 participants