-
Notifications
You must be signed in to change notification settings - Fork 953
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
Support (Multi)Point,FeatureCollection and GeometryCollection in bboxClip #2814
base: master
Are you sure you want to change the base?
Conversation
…Clip. Return Multi* objects with no coordinates instead of defective Polygon and LineString objects.
Oh, a note: it would be nice to have the type signature capture this logic:
I tried. TypeScript defeated me though. |
3.1 suggests that is actually ok? "GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects." Not saying that's any more intuitive. Just seems odd to go from 0 results = multipoint, 1 result = point, 2 or more results = back to multipoint. Would we be better off returning null? Would be easier to check in calling code.
It would be breaking if we started returning a wider variety of types than before.
Where did this come unstuck? Were you trying function overloading? Or some other mechanism? |
Yeah, but since Turf is generating the data, that would essentially be forcing all apps using Turf to adopt the non-standard interpretation.
It's intuitive if you think of the rules of English:
The Multi- strategy is imho the best for consistency.
There is theoretically also the option of returning a feature with a null geometry, which would be legal, but weird in its own way. What I like about my approach:
I did consider that perhaps this behaviour could be an option, like
It's already true that you can pass a Polygon and get back a MultiPolygon. By coincidence, if anyone is using logic like
It was a big chain of conditional generics ( |
Pinging @mourner as the original author in case he has any opinions. |
All fair points about null geometries in features, etc @stevage.
Hadn't thought of it that way. I suppose I'm looking at it from the POV of a user who wants to know "was there anything returned in the clip area?" They would have to do this right?
I understand the approach you're suggesting makes sense if you just feed the result straight into a loop to process the points somehow. If it's empty the loop runs zero times, no dramas. If the user wants to make a decision at that point though they need to know how to detect an empty result. That first test above would require a pretty good understanding of geojson to get right. How about returning
We do that in other places (e.g. intersect, difference) so there is a precedent.
True, however now we're also talking about returning Points, MultiPoints, and FeatureCollections. If client code isn't handling those already (and I don't think they would as 7.2.0 doesn't mention them) that would probably start generating type errors.
Oh god. I hope you've recovered. Something like this could be an option (generics omitted for line length purposes): // Overloaded typedefs
function bboxClip(feature: Feature<Point | MultiPoint>, bbox: BBox): Feature<Point | MultiPoint> | FeatureCollection;
function bboxClip(feature: Feature<Line | MultiLine>, bbox: BBox): Feature<Line | MultiLine> | FeatureCollection
function bboxClip(feature: Feature<Polygon | MultiPolygon>, bbox: BBox): Feature<Line | MultiLine> | FeatureCollection;
// plus options for FeatureCollections and GeometryCollections ...
// Actual function implementation is a superset of all the above
function bboxClip(feature: Feature<Point | MultiPoint | Line | MultiLine | Polygon | MultiPolygon>, bbox: BBox): Feature<Point | MultiPoint | Line | MultiLine | Polygon | MultiPolygon> | FeatureCollection {
// do the stuff
} It's reasonably readable - definitely more so than 15 levels of nested ternary statements! Big picture, thinking of Turf-wide consistency, I think it's useful to have a tidy way to indicate to users there was no result, whatever that means. I believe this PR with its current scope will be a breaking change (cause of other return types), so wondering if that changes the conversation at all? |
It's a pity we don't also have a function that more directly answers that question, a "booleanCrossesBbox" or something.
I think they just have to do something like this:
I don't think this is true.
This is much more problematic, because now we force the user to do the check, because it's very likely any subsequent code will throw errors. I really don't see "nothing in the clipped area" as a special case warranting special handling or thrown exceptions, and I think the function should behave accordingly.
Those types are never returned because those types aren't accepted as arguments currently. If we can get the type signature of the function correct, it won't cause any generated type errors, I think.
Ah yes, overloads looks definitely like the right approach here.
Again, if we get the type signature right, I don't think that's the case. Before: Pass a Polygon, get either a MultiPolygon or Polygon back. After, the same. But also if you happen to pass a Point, now you get either a Point or a MultiPoint instead of it just being a type error. I don't think that's a breaking change? Incidentally, what do you think about the idea of an options flag to request nulls instead of Multis? |
Ok I'm willing to be optimistic, though think we'll have to take the overloading approach to stand a chance.
As long as the user can narrow down which types might come back, agree that should be enough. So the possible permutations would be (types in -> types out):
And the user adjusts their "empty result test" to suit. That cover all the bases?
Inclined to just settle on one approach and keep the interface simple. Let's press ahead with Multis as you originally suggested. |
I think I have achieved this now in my latest commit. I haven't thoroughly tested the type resolution, but my basic tests seem to give the right result. I'm guessing we don't have any actual TypeScript type testing in Turf? The new type definitions are more specific than previously. Before there wasn't even a defined return type. A wrinkle I hadn't considered was that if you pass a non-specific geometry type in, you should just get a non-specific geometry out. Fortunately, that case seems to work without losing the specificity of the other cases.
I don't think think the user has to adjust anything. If they are doing this: const out = bboxClip(myPolygon, bbox);
if (out.geometry.coordinates.length === 0) console.log('nothing in clip area') it will still work. If for some reason they have written: const out = bboxClip(myPolygon, bbox);
if (out.geometry.type === 'Polygon' && out.geometry.coordinates.length === 0) console.log('nothing in clip area') then this will not work. Worth noting that the documentation did not make any promises about what happens when a polygon is not within the clip area. So now it is much clearer. Or to put it differently, it changes an undefined (and incorrect) behaviour to a defined, and correct behaviour. |
Btw after doing a bit more close reading, I should correct some of my statements:
FWIW, geojson.io takes the same interpretation: a LineString with [] coordinates is invalid. Also, the current behaviour of |
So...after all that (sorry!), I've reverted back to returning a It seems I also at certain points thought mistakenly that I did change the behaviour so that if you pass a single-item MultiLineString, you get a single-item MultiLineString back. That just seemed like a bug. |
Lol. All good. Taking a fresh look at the latest commit.
Would you mind dropping an example of what that would look like? Had a go locally and TS is refusing to accept anything I'm doing to prompt the situation.
Not sure we could even if we wanted to. For example, what would be your intuition for the return type of the below (passing in a Polygon)? Btw, seems like the current behaviour merges the bbox with the clipped multiple polygons to be able to return a polygon. Not sure if that was intentional to keep the same return type or some other reason. See #2816. |
Well, this: const test = { type: "Point", coordinates: [0, 0] } as Geometry;
const out = bboxClip(test, bbox); Hovering over
Indeed. To be more precise, I should have said "maintaining the original geometry type, in cases where it's possible, .."
Yeah, I made some assumptions about the current behaviour in this whole PR, but I suspect the current behaviour produces defective results in a lot of places. It's really just a very simplistic wrapper around |
Of course. Thanks.
Guess we cross the bridge when we come to it. Feel the return types are generous enough now that when we fix the behaviour the types won't have to change again. Do you have any thoughts on how to adapt the JSDoc to handle the multiple overloads? There should be a params / returns combo for each to make clear the output types are constrained by the inputs e.g. put in a Polygon and you know you're never going to get a FeatureCollection. |
Hmm, I didn't think about JSdoc. I doubt it can handle anything as complex
as the overloads, but it should have the other types added at least.
I'm away for a few days, so feel free to make that doc change otherwise
I'll get to it in a bit.
|
Will take a look. Thanks for working through it 👍 |
Ok, I have updated the doc. Also, since it seems that currently polygons are not split into MultiPolygons I have updated the doc to reflect that, and added test cases. I do think it should be fixed, but that's covered in #2816. |
Fixes #2813
Also, small change to existing behaviour to return Multi* objects with no coordinates instead of defective Polygon and LineString objects. That is, instead of a Polygon withcoordinates: []
which is in violation of the GeoJSON spec, you get aMultiPolygon
withcoordinates: []
which is ok.Possibly this would count as a breaking change? But would anyone really rely on that?Please provide the following when creating a PR:
contributors
field ofpackage.json
- you've earned it! 👏