[ANN] CoordRefSystems.jl

Coordinate Reference Systems (CRS) in native Julia

@eliascarv and I are happy to share

with the geospatial community.

CoordRefSystems.jl provides conversions between Coordinate Reference Systems (CRS) in native Julia. It was designed to work with units from Unitful.jl, respects projection bounds catalogued in https://epsg.io, and is very fast thanks to advanced parametrizations at compile-time.

This package addresses various design issues encountered in previous attempts such as Geodesy.jl and MapMaths.jl. Our benchmarks show that CoordRefSystems.jl is often faster than PROJ, which is the most widely used software library for cartography in the world (written in C/C++).

Examples of usage are available in the README:

With this package, we are one step closer from a complete GIS experience with GeoStats.jl in native Julia. We are planning to refactor the stack in the following months to accommodate this CRS work.

The package is awaiting registration in the General registry.

Acknowledgements

We would like to thank @cormullion for the awesome logo made with Luxor.jl, and the C/C++ PROJ software from where we got inspiration for this new Julia design.

52 Likes

It has always been a pain to use GeoMakie.jl - not due to GeoMakie’s fault, but because in essense all arguments for the projection where passed as “old C command line arguments” to PROJ, and that made it difficult for me at least to reason with the code and get the results I expected.

It is a very welcomed sight to see a pure Julia alternatve to PROJ!

3 Likes

Not only that, we will now be able to dispatch algorithms in Meshes.jl (and therefore GeoStats.jl) based on CRS, meaning that we will never compute an area with angular coordinates for example.

We carefully designed CoordRefSystems.jl to be able to send GeoTables for visualization without manual selection of axis, like it is done in GeoMakie.jl and the PROJ strings.

4 Likes

The package was renamed to CoordRefSystems.jl given feedback in the General registry, and to avoid confusion with drawing/plotting libraries.

6 Likes

Awesome package, thank you very much for sharing all the expertise and hard work that went into this! CoordRefSystems fixes most of the usability beefs I had with Geodesy.jl and implements a much larger set of coordinate conversions than MapMaths.jl, so I can see myself giving up on MapMaths.jl soon and adopting this package instead. However, CoordRefSystems currently lacks a few features which would make this move somewhat painful, so I’m curious to hear whether you would be willing to add them.

  • Would it be possible to add fallback conversions between Cartesian and Projected coordinate types to cover conversions such as this one?

    julia> convert(Cartesian, WebMercator(0,0))
    ERROR: MethodError: Cannot `convert` an object of type
      WebMercator{WGS84Latest, Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}} to an object of type
      Cartesian
    

    We frequently switch to cartesian coordinates when doing coordinate arithmetic, so it would be great if we could rely on convert(Cartesian, x) to work regardless of what x we throw at it.

  • Another missing conversion:

    julia> convert(LatLon, LatLonAlt(0,0,0))
    ERROR: MethodError: Cannot `convert` an object of type
      GeodeticLatLonAlt{WGS84Latest, Quantity{Float64, NoDims, Unitful.FreeUnits{(°,), NoDims, nothing}}, Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}} to an object of type
      GeodeticLatLon
    

    Admittedly, this isn’t just a conversion since we drop altitude information, but I don’t think this should stop us since CoordRefSystems supports converting Cartesian to LatLon and this conversion faces the same problem.

    julia> convert(LatLon, Cartesian{WGS84Latest}(0,0,0))
    GeodeticLatLon{WGS84Latest} coordinates
    ├─ lat: 180.0°
    └─ lon: 0.0°
    
  • Do you have any plans for adding local coordinates systems (e.g. ENU)? Local coordinates can be very convenient e.g. for computing the LatLon of a point 500m to the east of a given point. Further bonus points will be awarded if you support both cartesian local coordinates and “wrapping” local coordinates (i.e. a non-cartesian local coordinate system such that alt = 0 is always on the surface of the sea).

  • AFAICT, if you want to convert say latlon::LatLon to a pair of WebMercator coordinates without the WebMercator wrapper type, you currently have to do this:

    webm = convert(WebMercator, latlon)
    (webm.x, webm.y)
    

    This operation is required frequently if you have to interact with non-CoordRefSystems-aware software (e.g. maths or plotting libraries), so there’s significant benefit in making it as ergonomic as possible. Would it be possible to add some convenience function for this operation (e.g. cstrip())?

  • If you are agreeable to adding something like cstrip(), would it be possible to also add a function cwrap() such that x -> cwrap(args..., cstrip(args..., x) becomes the identity and cstrip() returns an SVector? Such a pair of functions would make it very easy to express a lot of highly useful coordinate arithmetic. For example:

    function rhumb_line(x::CRS, y::CRS)
        C = Mercator
        return t -> cwrap(C, t * cstrip(C, x) + (1-t) * cstrip(C, y))
    end
    
    function mean(coords::AbstractVector{<:CRS})
        C = Cartesian{WGS84latest}
        m = cwrap(C, mean(cstrip(C, x) for x in coords))
        # Throw an error if mean is too far underground
        # (i.e. the `coords` are not local enough)
        @assert abs(convert(LatLonAlt, m).alt) < 10
        return m
    end
    
    translated_latlon(x::CRS, d::SVector{3}) = cstrip(LatLon, cwrap(ENU(x), d))
    
  • Minor nitpick: The docstring for LatLon currently only shows up if you query for GeodeticLatLon.

    help?> LatLon
      No documentation found.
    
    help?> GeodeticLatLon
      LatLon(lat, lon)
      LatLon{Datum}(lat, lon)
      GeodeticLatLon(lat, lon)
      GeodeticLatLon{Datum}(lat, lon)
    
      Geodetic latitude lat ∈ [-90°,90°] and longitude lon ∈ [-180°,180°] in angular units (default to degree) with a given Datum (default to WGS84).
    

    Absolutely no biggie, of course, but easy enough to fix so why not?

5 Likes

The Juliahub page is currently 404 error. I guess part of the pipeline is not completing?

Nice package, I like how coordinate handling in Julia matures (:

Some parts of the interface seem really similar or just relevant to astronomical coordinates as well (and maybe other spherical scenarios?). We’ve had a successful GitHub - JuliaAstro/SkyCoords.jl: Astronomical coordinate systems in Julia with lat-lon and cartesian parametrizations (+ projected, PR stuck unfortunately add simple projected coordinates by aplavin · Pull Request #43 · JuliaAstro/SkyCoords.jl · GitHub).

Would be nice to understand if there’s something that can be reused or done consistently. Especially given that earth and sky coordinates are used together sometimes.

1 Like

Stupid question from me, regarding astro coordinate systems.
What coordinate system to the astronauts in the International Space Station use?
I would guess htey use UTC time also.

1 Like

Thank you @ettersi for your feedback! We would be happy to fix all issues raised. Can you please copy/paste the bullet points into new GitHub issues in CoordRefSystems.jl?

1 Like

Hi @johnh this is because the package is waiting the registration period. I think JuliaHub only lists registered packages.

Thank you @aplavin! Let’s join forces in CoordRefSystems.jl and absorb any missing CRS from SkyCoords.jl. We need a central place to evolve these systems to be able to seamlessly track objects on Earth and on the Sky.

If the question is to me, then I have no idea (: By astro coordinates, I mean coordinates of astronomical objects on the celestial sphere.

Hm, first it would be nice to understand what are the relevant differences in the design/API. I just noticed a lot of surface-level similarities, and commented on that.
I’m familiar with SkyCoords.jl, and it has generally been good enough for me (aside from the stalled projected coords PR).
Then the best approaches from both could be unified.

I can tell you already that SkyCoords.jl is minimal in functionality compared to CoordRefSystems.jl. It doesn’t have the notion of datum, nor explicit units. CoordRefSystems.jl has a much more ambitious scope.

This looks great. My question is that

  • Can I used it to convert GPS Datum for example from Datum AGD56 to WGS84, ie from old maps to current Datum

  • Can I use it to correct for the correct continental drift of Australia from old maps?

Oh by the way. Australia is the fastest continent on earth bar none!

1 Like

Yes, you just need to define the AGD56 datum, which is a set of constants. The rest is already implemented, and we ported all datum supported by PROJ.

Yes, and this is possible due to our design that attaches epochs to datum. We will add a new function soon that takes any datum and adjusts the epoch for dynamic updates. There is an issue already for this: Add epoch shift to datum · Issue #41 · JuliaEarth/CoordRefSystems.jl · GitHub

1 Like

I always struggle with the convention LatLon, I prefer LonLat. For understanding purposes, what’s the reason behind this convention?

Note also, in the docs, when doing (looks inconsistent)

julia> convert(Mercator, latlon)
Mercator{WGS84Latest} coordinates
├─ x: 6.679169447596414e6 m # this is to Lon, right?
└─ y: 3.482189085408618e6 m # and this to lat? 
1 Like

No major reason as far as I know. The latitude is the most difficult coordinate to compute in the pair, and perhaps people decided to place it first historically in EPSG codes, and textbooks.

This is not inconsistent, it is actually nice that the package explicitly names things to avoid confusion. Projected coordinates are x and y in meters, and they are usually associated with longitude and latitude (reversed) order in degrees.

PROJ handled this issue with the swap_xy option everywhere, but CoordRefSystems.jl avoided that altogether by enforcing structs with well-defined fields and types in a specific order. We could introduce another struct LonLat for convenience later, but there is no use case that really justifies it yet.

Heh, I also noticed that “projected” coordinates here don’t mean what I expected (:
In CRS.jl, you mean projection of the whole sphere onto some surface, like Mercator and similar – right?
What about projection to the tangent plane? Basically, a system that has a reference (lon, lat) position on the sphere, and coordinates are represented in terms of (projected) offsets from that point. In astronomy, this is very common: you may have an image centered at some lon, lat, and coordinates within the image are “relative lat” + “relative projected lon”.

Or both senses of “projected” are the same general beast?..

I believe you are discussing East/Northing/Up coordinates, there is an issue open already for that: Add local coordinates · Issue #48 · JuliaEarth/CoordRefSystems.jl · GitHub

Any historians on here are free to correct me if I’m wrong, but I believe it’s because latitude is the easier of the two to measure, and was used in navigation for centuries before longitude could be reliably calculated. I imagine ships’ masters were used to logging latitude, then started adding longitude second when accurate lunar almanacs and chronometers became available in around the turn of the 19th century. So the answer is “maritime tradition,” though it is also now an official ISO Standard to quote latitude before longitude.

3 Likes