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

turf-destination strips altitude/elevation member from origin coordinate. #2850

Open
prozvora opened this issue Mar 7, 2025 · 8 comments
Open

Comments

@prozvora
Copy link

prozvora commented Mar 7, 2025

The destination function does not return all members of the origin parameter.

https://github.com/Turfjs/turf/blob/master/packages/turf-destination/index.ts#L40

Note the return value creates a Position array with [lng, lat], but the origin parameter may have a third element, which should be carried over.
https://github.com/Turfjs/turf/blob/master/packages/turf-destination/index.ts#L70

GeoJSON specifies that a Position may have a third element, representing altitude or elevation.
https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.1

This also affects turf-ellipse, which uses destination in its conversion of an ellipse to a polygon.
https://github.com/Turfjs/turf/blob/master/packages/turf-ellipse/index.ts#L151

@prozvora
Copy link
Author

prozvora commented Mar 7, 2025

Image

@smallsaucepan
Copy link
Member

Thanks for raising this @prozvora. Turf hasn't traditionally used altitude in its calculations, so I reckon you'll find several more places where that third coordinate is dropped. Even though starting to use altitude is probably out of scope for the foreseeable future, we should at least pass it through unchanged where that it makes sense to.

Bearing that in mind, is this something you'd be able to look at a PR for? Adding tests, making sure it doesn't unintentionally impact other packages?

Fwiw, I suspect it would actually help in some cases e.g. #2672

That might be more than the four lines you mention above though so wanted to ask first if you had the bandwidth to take a deeper look.

@prozvora
Copy link
Author

prozvora commented Mar 8, 2025

I know it affects destination and ellipse. I can add make a PR from my fork and add tests on those packages. I'll also take a look at #2672.

@smallsaucepan If there are any other packages you think may be affected, please just list them and I can add test cases for them too. I haven't used the entire turf repo extensively.

Thanks!

@smallsaucepan
Copy link
Member

Thanks for putting that PR together. Have found a few questions.

Currently point() does a runtime check to make sure the elements it returns are numbers. This hasn't been an issue as Turf more or less ignored altitude and made no promises about what was coming back in that third position.

We're now saying Turf does at least recognise altitude, so we should make sure we aren't returning values that violate the spec. That would mean expanding the existing runtime check to look at the third element as well. However, that could break existing code if someone has been (unwisely) passing non-numeric data in that field. Any ideas on how we could handle that?

The other question is about tesselate. Looking at the spec it seems possible to have some positions with altitude defined and some without. What's the expected behaviour if there are a mixture? Is there a strategy that would make the result predictable and backwards compatible?

For example, do we call earcut with 3 dimensions all the time, and trim each resulting point individually if altitude is undefined?

@prozvora
Copy link
Author

prozvora commented Mar 9, 2025

We're now saying Turf does at least recognise altitude, so we should make sure we aren't returning values that violate the spec.

I wouldn't say that Turf would be committed to checking the validity of the altitude member of the coordinate array. If so far, turf has been stripping the value, I don't think passing through the value, unchanged, would be less correct. You can give the same disclaimer as earcut does below, and continue to not make promises about the third position (other than not changing it?)

For example, do we call earcut with 3 dimensions all the time, and trim each resulting point individually if altitude is undefined?

I don't think that's necessary. The earcut docs say that regardless of the dimension of the input array, only the first two elements are used, and the rest are ignored. Including elevation in tesselate doesn't change the result from it.

Image

The other question is about tesselate. Looking at the spec it seems possible to have some positions with altitude defined and some without. What's the expected behaviour if there are a mixture? Is there a strategy that would make the result predictable and backwards compatible?

Can you give me an example? I'm not sure how to think about this.

My first impulse would be to mimic what's happening in earcut. Operations do not use the Z axis. Everything in Z coordinate gets mapped down to zero for calculations. Z gets carried over to the output and passed through.

@smallsaucepan
Copy link
Member

Sounds reasonable about the checking, so long as we do mention the limitation in the docs for now. Will re-look at tightening up the runtime check for the next major release (i.e. v8).

On the "only some points might have elevation values" take this square:

[
  [1, 1],
  [1, 2, 50],
  [2, 2, 75],
  [2, 1],
  [1, 1],
]

According to the spec this is valid. Each of the individual positions has two or three numeric values. The current elevation test in the PR only checks the first position though. As is it would strip the elevations from all points.

I'm guessing what we should return is these two triangles:

[
  [1, 1],
  [1, 2, 50],
  [2, 2, 75],
  [1, 1],
]

and

[
  [1, 1],
  [2, 2, 75],
  [2, 1],
  [1, 1],
]

Neither losing elevation where it was defined, nor adding it where it wasn't.

So, to make sure we respect elevation, we either need to:

  1. check every position before we start and if there are any with elevation defined, pad those remaining with elevation=undefined and pass to earcut with dimensions=3, then trim any back to 2 any elements that still have elevation as undefined
  2. don't bother checking and just do all the above every time
  3. Something else???

@prozvora
Copy link
Author

I think 1 is the most intuitive from a user perspective, and coincides with a "pass through" philosophy for that coordinate element.

I can take a stab at implementing that for tessellate, if that wouldn't increase the scope of my PR too much. Or I can split out tessellate changes into a separate PR, since the destination/ellipse changes don't involve any further manipulation of the elevation coordinate member.

@smallsaucepan
Copy link
Member

Note that options 1 and 2 were identical from the user's perspective. Was pointing out it might not be worth checking first - just do it the same way all the time.

Good idea. Let's keep this PR focused on ellipse. Then we can have a look at the issue more broadly and do some experiments to see what might be the best approach.

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

No branches or pull requests

2 participants