JuliaGeo / Geodesy.jl

Work with points defined in various coordinate systems.
MIT License
111 stars 24 forks source link

Validity of longitudes and latitudes #52

Open anowacki opened 4 years ago

anowacki commented 4 years ago

Currently, no check is done on the range of values when either constructing or converting LLAs or LatLons:

julia> using Geodesy

julia> p = LLA(lat=1000.0, lon=1000.0, alt=0.0)
LLA(lat=1000.0°, lon=1000.0°, alt=0.0)

julia> ECEF(p, wgs84)
ECEF(192951.75490366874, -1.0942837796485086e6, -6.25954296102869e6)

julia> ENU(p, p, wgs84)
ENU(0.0, 0.0, 0.0)

julia> UTMZ(p, wgs84)
UTMZ(NaN, NaN, 0.0, zone=polar (north))

(It appears the ECEF conversion works as if the latitude wraps around over the pole, whilst clearly the UTM conversion needs valid latitudes.)

It is also the case that longitudes are compared as if they are linear numbers, when in reality they are periodic, and thus two points can represent the same position when n × 360° apart but not be 'equal':

julia> p1, p2 = LLA(0, 10, 0), LLA(0, 370, 0);

julia> p1 == p2
false

julia> ECEF(p1, wgs84) == ECEF(p2, wgs84)
true

This raises a few questions:

  1. Should latitudes be enforced to be in the correct range? I guess doing so in an inner constructor may reduce performance, but I haven't checked this.
  2. Should converting a permitted invalid latitude return nonsense values, an error, or a 'bad value' flag (as is currently done I think inadvertently for the UTM conversion)?
  3. Should longitudes: a. be normalised to a range on construction (say [–180,180] or [0,360]); or b. be compared modulo 360 when comparing LLAs and LatLons; or c. remain unnormalised and points only be equal when longitudes are strictly the same?

Letting the user make their own mistakes is an acceptable approach, but I think it should be noted carefully somewhere if this is the case. Alternatively, the safety net of bailing out when abs(latitude) > 90 is often worth the effort, not least since it quite often might catch cases of latitude and longitude being mixed up!

Happy to submit a PR for whichever route is best.

(My own preference would be for enforcement of -90 ≤ lat ≤ 90 in construction for LLAs and LatLons, and modulo-360 comparison of longitudes. This allows one to keep longitudes around some arbitrary meridian.)

c42f commented 4 years ago

Good questions.

  1. Tentatively, yes. This seems helpful and not too expensive.
  2. For UTM conversion, it's always possible to validate the input if necessary. The UTM conversion is complicated and I'd be surprised if we saw any measurable performance hit.
  3. Um, this one is hard. The type of equality operation distinguishes between comparing values in the coordinate system vs comparing points on the underlying manifold. Which is most useful? Comparison mod 360 is always going to run afoul of numerical precision if floats are used:
julia> lon = 1/60  # one arc minute
0.016666666666666666

julia> (lon + 360) - 360 == lon
false
andyferris commented 4 years ago

This is really interesting. Where should the validation occur? We can definitely verify that the latitude is sensible for UTM conversions and catch that case. Should user's be expected to do the same with their code? Or should the inner constructor do it for them?

I could go any way on this... I'd probably be tempted to trust the user to do the right thing and just treat LLA as a simple struct (without invariants enforced) and let operations worry about that, but that is not a strong opinion.

c42f commented 4 years ago

Yes I could go either way as well. Having the LLA as "simple data" has certian advantages, for example it allows you to reinterpret a buffer of a separate type as LLAs which could be handy for interop. It also makes things more flexible when dealing with data from outside Geodesy which may want different or broken invariants. A good analogy here is String which allows invalid UTF-8 data for the same reasons:

julia> s = "\xff"
"\xff"

julia> s[1]
'\xff': Malformed UTF-8 (category Ma: Malformed, bad data)