Source: https://gometry.monicz.dev/api/toplevel/geodesy-navigation/

# Geodesy & navigation

Bearings, destinations, route interpolation, and cross-track distance.

**Related:** [CRS, units & measurement](https://gometry.monicz.dev/guide/crs/)

## bearing

Accepted call forms

```python
bearing(left: Point, right: Point, *, path: Literal['geodesic', 'rhumb'] = 'geodesic') -> float
```

```python
bearing(left: GeometryArray[Point | None], right: Point | GeometryArray[Point | None], *, path: Literal['geodesic', 'rhumb'] = 'geodesic') -> NDArray[float64]
```

```python
bearing(left: Point, right: GeometryArray[Point | None], *, path: Literal['geodesic', 'rhumb'] = 'geodesic') -> NDArray[float64]
```

```python
bearing(left: Point | GeometryArray[Point | None], right: Point | GeometryArray[Point | None], *, path: Literal['geodesic', 'rhumb'] = 'geodesic') -> float | NDArray[float64]
```

Initial bearing from one point to another, in degrees clockwise from north
(`0..360`).

Geodesic by default on a geographic CRS (on the CRS ellipsoid); grid
azimuth on a projected or CRS-free point. `path='rhumb'` selects the
constant compass course on a geographic CRS. An empty point yields `nan`.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `left` | `Point or GeometryArray` | Origin and destination; must share a CRS. | *required* |
| `right` | `Point or GeometryArray` | Origin and destination; must share a CRS. | *required* |
| `path` | `('geodesic', 'rhumb')` | Route model. Rhumb bearings require a geographic CRS. | `'geodesic'` |

Returns:

| Type | Description |
| --- | --- |
| `float or ndarray` | One bearing per input pair. |

Raises:

| Type | Description |
| --- | --- |
| `GeometryTypeError` | If either operand is not a `Point`. |
| `CRSMismatchError` | If the operands' CRS or coordinate-epoch metadata differ. |
| `CRSError` | If a coordinate is outside the longitude/latitude domain. |

Examples:

```pycon
>>> import gometry as gm
>>> gm.bearing(gm.Point(0, 0), gm.Point(1, 0))
90.0
```

## cross\_track\_distance

Accepted call forms

```python
cross_track_distance(point: Point, start: Point, end: Point) -> float
```

```python
cross_track_distance(point: GeometryArray[Point | None], start: Point | GeometryArray[Point | None], end: Point | GeometryArray[Point | None]) -> NDArray[float64]
```

```python
cross_track_distance(point: Point, start: GeometryArray[Point | None], end: Point | GeometryArray[Point | None]) -> NDArray[float64]
```

```python
cross_track_distance(point: Point, start: Point, end: GeometryArray[Point | None]) -> NDArray[float64]
```

```python
cross_track_distance(point: Point | GeometryArray[Point | None], start: Point | GeometryArray[Point | None], end: Point | GeometryArray[Point | None]) -> float | NDArray[float64]
```

Signed distance from a point to the great circle through `start` and
`end`, in meters.

Positive when the point lies left of the directed path `start -> end`,
negative right, zero on it. Spherical on the CRS ellipsoid's mean radius.
Geographic CRS only. An empty point yields `nan`.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `point` | `Point or GeometryArray` | The probe point and two distinct endpoints defining the directed path; all must share a CRS. | *required* |
| `start` | `Point or GeometryArray` | The probe point and two distinct endpoints defining the directed path; all must share a CRS. | *required* |
| `end` | `Point or GeometryArray` | The probe point and two distinct endpoints defining the directed path; all must share a CRS. | *required* |

Returns:

| Type | Description |
| --- | --- |
| `float or ndarray` | Signed meters off-path. |

Raises:

| Type | Description |
| --- | --- |
| `GeometryTypeError` | If any operand is not a `Point`. |
| `CRSMismatchError` | If the operands' CRS or coordinate-epoch metadata differ. |
| `CRSError` | If the CRS is missing or not geographic. |
| `InvalidGeometryError` | If `start` and `end` are coincident or antipodal and therefore do not define a unique great circle. |

Examples:

```pycon
>>> import gometry as gm
>>> a, b = gm.Point(0, 0, crs=4326), gm.Point(1, 0, crs=4326)
>>> probe = gm.Point(0.5, 0.1, crs=4326)
>>> round(gm.cross_track_distance(probe, a, b) / 1000, 1)
11.1
```

## point\_between

Accepted call forms

```python
point_between(left: Point, right: Point, distance: ScalarFloatArray, *, normalized: bool = False, path: Literal['geodesic'] = 'geodesic', unit: Literal['planar', 'meters'] | None = None) -> Point
```

```python
point_between(left: Point, right: Point, distance: FloatScalar | ScalarFloatArray, *, normalized: bool = False, path: Literal['geodesic'] = 'geodesic', unit: Literal['planar', 'meters'] | None = None) -> Point
```

```python
point_between(left: GeometryArray[Point], right: Point | GeometryArray[Point], distance: FloatInput, *, normalized: bool = False, path: Literal['geodesic'] = 'geodesic', unit: Literal['planar', 'meters'] | None = None) -> GeometryArray[Point]
```

```python
point_between(left: Point, right: GeometryArray[Point], distance: FloatInput, *, normalized: bool = False, path: Literal['geodesic'] = 'geodesic', unit: Literal['planar', 'meters'] | None = None) -> GeometryArray[Point]
```

```python
point_between(left: Point, right: Point, distance: ScalarFloatArray, *, normalized: bool = False, path: Literal['geodesic', 'rhumb'] = 'geodesic', unit: None = None) -> Point
```

```python
point_between(left: Point, right: Point, distance: FloatColumn | ndarray[tuple[int], dtype[floating[Any] | signedinteger[Any] | unsignedinteger[Any]]], *, normalized: bool = False, path: Literal['geodesic'] = 'geodesic', unit: Literal['planar', 'meters'] | None = None) -> GeometryArray[Point]
```

```python
point_between(left: Point, right: Point, distance: FloatScalar | ScalarFloatArray, *, normalized: bool = False, path: Literal['geodesic', 'rhumb'] = 'geodesic', unit: None = None) -> Point
```

```python
point_between(left: GeometryArray[Point], right: Point | GeometryArray[Point], distance: FloatInput, *, normalized: bool = False, path: Literal['geodesic', 'rhumb'] = 'geodesic', unit: None = None) -> GeometryArray[Point]
```

```python
point_between(left: Point, right: GeometryArray[Point], distance: FloatInput, *, normalized: bool = False, path: Literal['geodesic', 'rhumb'] = 'geodesic', unit: None = None) -> GeometryArray[Point]
```

```python
point_between(left: Point, right: Point, distance: FloatColumn | ndarray[tuple[int], dtype[floating[Any] | signedinteger[Any] | unsignedinteger[Any]]], *, normalized: bool = False, path: Literal['geodesic', 'rhumb'] = 'geodesic', unit: None = None) -> GeometryArray[Point]
```

```python
point_between(left: GeometryArray[Point | None], right: Point | GeometryArray[Point | None], distance: FloatInput, *, normalized: bool = False, path: Literal['geodesic'] = 'geodesic', unit: Literal['planar', 'meters'] | None = None) -> GeometryArray[Point | None]
```

```python
point_between(left: Point, right: GeometryArray[Point | None], distance: FloatInput, *, normalized: bool = False, path: Literal['geodesic'] = 'geodesic', unit: Literal['planar', 'meters'] | None = None) -> GeometryArray[Point | None]
```

```python
point_between(left: GeometryArray[Point | None], right: Point | GeometryArray[Point | None], distance: FloatInput, *, normalized: bool = False, path: Literal['geodesic', 'rhumb'] = 'geodesic', unit: None = None) -> GeometryArray[Point | None]
```

```python
point_between(left: Point, right: GeometryArray[Point | None], distance: FloatInput, *, normalized: bool = False, path: Literal['geodesic', 'rhumb'] = 'geodesic', unit: None = None) -> GeometryArray[Point | None]
```

Return a point interpolated from `left` towards `right`.

CRS-aware like every metric: geodesic on a geographic CRS, planar on a
projected CRS, and raw coordinate units when CRS-free. `path='rhumb'` follows
the constant-bearing track on a geographic CRS. Z/M interpolate. An empty
endpoint yields `POINT EMPTY`.

Parameters:

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `left` | `Point or GeometryArray` | Endpoints; must share a CRS. | *required* |
| `right` | `Point or GeometryArray` | Endpoints; must share a CRS. | *required* |
| `distance` | `float or sequence of float` | Finite distance(s) from `left` (or fractions if `normalized`). Values outside the segment clamp to its endpoints. | *required* |
| `normalized` | `bool` | Treat `distance` as a fraction of the total distance. | `False` |
| `path` | `('geodesic', 'rhumb')` | Route model. Rhumb paths require a geographic CRS and use meters. | `'geodesic'` |
| `unit` | `('planar', 'meters')` | `None` (the default) follows the CRS: geographic shape area and length use ellipsoidal chart-edge measures while point distances use geodesics; projected measurements use native linear units, and CRS-free measurements use raw coordinate units (area results square the corresponding unit). `'planar'` uses Cartesian coordinate space, with geographic coordinates in their native angular units. `'meters'` uses the CRS-aware metric and requires a CRS.  `path='rhumb'` uses meters and does not accept a unit override. | `'planar'` |

Returns:

| Type | Description |
| --- | --- |
| `Point or GeometryArray` | One interpolated point per input pair. |

Raises:

| Type | Description |
| --- | --- |
| `GeometryTypeError` | If either operand is not a `Point`. |
| `CRSMismatchError` | If the operands' CRS or coordinate-epoch metadata differ. |
| `GeometryError` | If `distance` is non-finite. |

Examples:

```pycon
>>> import gometry as gm
>>> gm.point_between(gm.Point(0, 0), gm.Point(2, 0), 0.5).to_wkt()
'POINT (0.5 0)'
```
