# Geospatial Metrics

> NumKong for Rust

Inputs are latitudes and longitudes in radians.
Outputs are meters.

`HaversinesPackedMatrix` and `VincentysPackedMatrix` pack a set of points once each.
Their methods score every point of one set, the rows, against every point of another, the columns, into a caller's row-major slice of __rows × columns__ entries.

```rust
use numkong::{HaversinesPackedMatrix, VincentysPackedMatrix};

// Statue of Liberty (40.6892°N, 74.0445°W) → Big Ben (51.5007°N, 0.1246°W)
let liberty_latitudes = [0.7101605100_f64];
let liberty_longitudes = [-1.2923203180_f64];
let big_ben_latitudes = [0.8988567821_f64];
let big_ben_longitudes = [-0.0021746802_f64];
let mut distance = [0.0_f64; 1];

let liberty = VincentysPackedMatrix::new(&liberty_latitudes, &liberty_longitudes).unwrap();
let big_ben = VincentysPackedMatrix::new(&big_ben_latitudes, &big_ben_longitudes).unwrap();
liberty.vincentys(&big_ben, &mut distance).unwrap(); // ≈ 5,589,857 m (ellipsoidal, baseline)

let liberty = HaversinesPackedMatrix::new(&liberty_latitudes, &liberty_longitudes).unwrap();
let big_ben = HaversinesPackedMatrix::new(&big_ben_latitudes, &big_ben_longitudes).unwrap();
liberty.haversines(&big_ben, &mut distance).unwrap(); // ≈ 5,574,848 m (spherical, ~15 km less)
```

Haversine uses the IUGG mean radius of 6,371,008.8 m, and Vincenty the WGS-84 ellipsoid.
`SqEuclideans3D` scores `[x, y, z]` points the same way, with no packing.
`i32` coordinates, like Earth-fixed centimeters, give exact `u64` squares, and `f64` ones give `f64`.

```rust
use numkong::SqEuclideans3D;

let satellites = [[637_813_700_i32, 0, 0], [0, 637_813_700, 0]];
let stations = [[-637_813_700_i32, 0, 0], [0, 0, 637_813_700], [637_813_600, 0, 0]];
let mut squares = [0_u64; 6];
i32::sqeuclideans3d(&satellites, &stations, &mut squares).unwrap();
assert_eq!(squares[2], 10_000); // a meter apart
```

`use numkong::HaversinesPackedMatrix;`

`use numkong::SqEuclideans3D;`

`use numkong::VincentysPackedMatrix;`

## HaversinesPackedMatrix

Points packed once as unit vectors on the mean sphere, scored with __great-circle distances__ as rows against the columns of another pack.

```rust
pub struct HaversinesPackedMatrix<Alloc: Allocator = Global>
```

The distance is `2R × atan2(‖a − b‖, ‖a + b‖)` over unit vectors `a` and `b`, which stays
conditioned at every separation, from a millimeter to the antipode, with `R` the mean radius.

Implements: `Debug`, `Send`, `Sync`

## new_in

Packs the points at `latitudes` and `longitudes`, in radians, using a custom allocator.

```rust
pub fn new_in(latitudes: &[f64], longitudes: &[f64], alloc: Alloc) -> Result<Self, Error>
```

Returns `Err` if the two slices differ in length or allocation fails.

## *

Returns a reference to the allocator.

```rust
pub fn allocator(&self) -> &Alloc
```

## *

Number of packed points.

```rust
pub fn points(&self) -> usize
```

## pack_*_size

Bytes one pack of [`points`](https://www.unum.cloud/docs/numkong/rust/mesh-alignment.md#points) points takes, to size a buffer without packing.

```rust
pub fn pack__size(points: usize) -> Result<usize, Error>
```

## haversines

Great-circle distances in meters from every point of `self` to every point of `columns`, filling `results` with __[rows,columns]__ entries.

```rust
pub fn haversines<OtherAlloc: Allocator>(
    &self,
    columns: &HaversinesPackedMatrix<OtherAlloc>,
    results: &mut [f64],
) -> Result<(), Error>
```

Returns `Err` if `results` does not hold __rows × columns__ entries.

## new

Packs the points at `latitudes` and `longitudes`, in radians, using the global allocator.

```rust
pub fn new(latitudes: &[f64], longitudes: &[f64]) -> Result<Self, Error>
```

## SqEuclideans3D

Computes __squared Euclidean distances__ between every row point and every column point in 3D.

```rust
pub trait SqEuclideans3D: Sized
```

`i32` coordinates yield exact `u64` squares whenever every axis difference fits 32 bits, which
Earth-fixed centimeters below 4,359 km of altitude always satisfy. `f64` coordinates sum their
three squares in Dot2.

Implementors: - `impl [SqEuclideans3D](#SqEuclideans3D) for i32`
- `impl [SqEuclideans3D](#SqEuclideans3D) for f64`

## Distance

Squared distance type: `u64` for `i32` points, `f64` for `f64` ones.

```rust
type Distance: Copy + Default;
```

## sqeuclideans3d

Fills `results` with the __[rows,columns]__ squared distances from every point of `a` to every point of `b`.

```rust
fn sqeuclideans3d(
    a: &[[Self; 3]],
    b: &[[Self; 3]],
    results: &mut [Self::Distance],
) -> Result<(), Error>
```

Returns `Err` if `results` does not hold __rows × columns__ entries.

## VincentysPackedMatrix

Points packed once as longitudes and the sines and cosines of their reduced latitudes, scored with __Vincenty geodesic distances__ on the WGS-84 ellipsoid as rows against the columns of another pack.

```rust
pub struct VincentysPackedMatrix<Alloc: Allocator = Global>
```

Uses Vincenty's iterative formula for oblate spheroid geodesics:

- Reduced latitudes: `tan(U) = (1−f) × tan(φ)`

- Iterate until convergence: `λ → L + (1−C) × f × sin(α) × [σ + C × sin(σ) × ...]`

- Compute: `u² = cos²(α) × (a² − b²)/b²`

- Series coefficients A, B from u²

- Distance: `s = b × A × (σ − Δσ)`

Where a = equatorial radius, b = polar radius, f = flattening. ~20× more accurate than Haversine
for long distances. Inputs are in radians, outputs in meters. A pair still unsettled after the
iteration limit, which happens only near the antipode, reports its last iterate.

Implements: `Debug`, `Send`, `Sync`

## new_in

Packs the points at `latitudes` and `longitudes`, in radians, using a custom allocator.

```rust
pub fn new_in(latitudes: &[f64], longitudes: &[f64], alloc: Alloc) -> Result<Self, Error>
```

Returns `Err` if the two slices differ in length or allocation fails.

## vincentys

Geodesic distances in meters from every point of `self` to every point of `columns`, filling `results` with __[rows,columns]__ entries.

```rust
pub fn vincentys<OtherAlloc: Allocator>(
    &self,
    columns: &VincentysPackedMatrix<OtherAlloc>,
    results: &mut [f64],
) -> Result<(), Error>
```

Returns `Err` if `results` does not hold __rows × columns__ entries.

## new

Packs the points at `latitudes` and `longitudes`, in radians, using the global allocator.

```rust
pub fn new(latitudes: &[f64], longitudes: &[f64]) -> Result<Self, Error>
```

