Introducing Geodesic Tools

As part of my job, I work with a lot of earth data. To make my life easier, I made a small Rust library. I haven't put it on crates.io yet, because right now it feels like a hodgepodge of different things useful to me, rather than a cohesive package. Plus, it has little documentation or tests. Therefore, if you want to include it in your Rust project, add the following to your Cargo.toml

[dependencies]
geodesic_coordinates_rs = { git = "https://github.com/IndigoCurnick/geodesic_coordinates_rs", branch = "master" }

In this article I'm just going to show you the distance and bearing calculators.

When moving on the earth, we don't really move on straight lines. We always move on a curved path, owing to the curvature of the earth. This means that the distance between two places as the crow flies is not simple to calculate. Another consequence is that our bearing (direction we face) changes as we move.

The crate offers three different ways to compute these geodesics - with the Haversine, Vincenty or Karney formula. Haversine is the oldest, and least accurate. It assumes that the earth is a sphere, which introduces some inaccuracy (the earth is an oblate spheroid - slightly squished at the poles and slighly bulging at the equator). Haversine is an updated formula from the 1970s, which is very poular and widely used. It's very accurate and easy to implement, but can be somewhat computationally expensive. Although, that depends on your definition of "computationally expensive". Even a low-range CPU could easily perform hundreds of thousands of Vincenty calculations a second. If performance is your concern, then Karney is for you. The Karney method is currently the most accurate and most efficient algorithm I am aware of. However, it's a pain to implement (luckily, I already did that for you). In general, I'd always suggest using Karney.

For each of these there are two "modes" of computation. We can either have a start location and end location and ask the question "what is the distance between these locations, and which bearing would I have to start moving in to get from the first to the second". Notice how the answers matter which way around the locations are. For example, from London to Berlin the bearing is 77.24°. However, from Berlin to London the bearing is 267.93°! The second mode is asking the question "where will I end up and what direction will I be facing if I start with this direction and travel this distance?"

To make the crate easy to use, I made the functions resemble each other as much as possible.

pub fn distance_and_bearing(lat1: Radians, lon1: Radians, lat2: Radians, lon2: Radians) -> DistBearing
pub fn location_and_bearing(lat1: Radians, lon1: Radians, bearing: Radians, distance: Metres) -> LocBearing

All three versions (Haversine, Vincenty and Karney) have these two methods only. Each are in a different module, so you import them differently

use geodesic_coordinates_rs::geodesics::karney::distance_and_bearing;

For example. Notice how the functions take Radians - this is just a type alias for f64. Make sure you convert your human readable latitudes and longitudes into radians first! For instance, London is 51.49684°, -0.16329° or 0.89879, -0.00285. It might seem weird at first, but this is much easier since most of the time when working with earth data the latitudes and longitudes will be in radians. To help you with this, I also export a constant DEG_TO_RAD which you can multiply by your latitudes and longitudes to convert them to radians.

What about DistBearing and LocBearing? These are simple structs that just help giving you results.

pub struct LocBearing {
    pub lat: Radians,
    pub lon: Radians,
    pub bearing: Radians,
}

LocBearing is used to anser that question "if I start at this position and travel this distance in this direction, where will I end up and what direction will I be facing?". Thus, lat and lon will be the final position - again, in radians. You can divide by DEG_TO_RAD to convert them back into familiar coordinates. bearing is also in radians - this is the final direction.

pub struct DistBearing {
    pub distance: Metres,
    pub bearing: Radians,
}

DistBearing is used to answer the question "what is the distance and direction between these two places?". distance is in metres. bearing, again, is in radians and it represents the starting direction. As we already discussed, by travelling in a "straight line" the direction you face will constantly be changing.

I also export all these as JavaScript functions you can use on the web, too. I do this via WASM. Rust has fantastic WASM support, which makes exporting to the web or a Node.js server really simple. Again, I haven't put this on NPM yet because I don't think the crate is ready, but you're welcome to build it from source using

wasm-pack build --target web

You will need to install wasm-pack first.

There's only a small change between the Rust version and the JavaScript version, and that's with WASM there are no modules. So, each of the functions becomes prepended with the type. For instance, there is haversine_distance_and_bearing or karney_location_and_bearing and so on. To use these, you import them from the JavaScript file, which is a thin wrapper for the WASM that wasm-pack will automatically generate for you, something like this

import init, { haversine_distance_and_bearing } from "/path/to/geodesic_coordinates_rs.js";

await init();

It's important to include the await init(); line, since loading WASM has to be done async. If you try and use the functions before then, it'll crash.

Other than that small difference, everything else is exactly the same. You still need to put radians in, and you get the same structs back out. Because this is JavaScript, if you embed it in a webpage then it won't need to make a request to your server to compute the result.

You can see an example of the crate in action here. That shows you something you could make on the web, and of course, everything is open source. I hope you enjoy using this tool!