# Geospatial types

The geospatial types hold places and shapes on the Earth: a point, a
line, a polygon, a bounding box, a circle, a map tile. All 17 are in
WGS84, the coordinate system GPS uses, and every coordinate is a named
field, never a pair of numbers whose order you have to remember.

[Geospatial](geospatial.md) covers the work you do with them: the
`geo.` operations, from measures and predicates to building values with
`geo.from_geojson`, `geo.from_lng_lat`, `geo.from_lat_lng`, and WKT's
`geo.from_wkt` and `geo.to_wkt`; storage in PostGIS `geography` columns;
query conditions and their indexes; and GeoJSON's `Feature` and
`FeatureCollection`. Routing and geocoding come from provider packages.
This section describes the types themselves.

## Points and coordinates

A **`Point`** is a `latitude`, a `longitude`, and an `altitude` that may
be `none`.

- The latitude and longitude are the standard `Latitude` and `Longitude`,
  each a `Decimal` with no `scale:`. Each is held as the shortest decimal
  of the nearest IEEE double, as PostGIS stores it
  ([Precision and determinism](precision-and-determinism.md)).
  So `51.50720000001234567` is kept as `51.50720000001235`, and every
  coordinate of up to 15 significant digits is kept unchanged.
- A latitude runs from -90 to 90 and a longitude from -180 to 180, both
  ends included. So -180 and 180 are both the antimeridian, as RFC 7946
  takes them.
- The altitude is a `Length` of either sign: the height above the WGS84
  ellipsoid, which is what RFC 7946's third coordinate is.

A **`Bearing`** is a direction in degrees clockwise from true north, from
0 to 360, both ends included, since no bound leaves its end out. 360 and
0 are the same direction but different numbers, so an application that
compares bearings writes north one way. A wind's direction is a
`Bearing`: the direction it comes from, as WMO reports it.

## Geometries

A **`Geometry`** is a choice of six variants. Each holds its geometry
under the variant's own name: `Geometry.polygon { polygon: zone }`.

The geometries' parts are objects, so all six are built and read alike:

- a `LineString` and a `MultiPoint` are their `points`;
- a `Polygon` is its `outer` ring and its `holes`;
- a `MultiLineString` is its `lines`;
- a `MultiPolygon` is its `polygons`.

## No text form

A geometry has no single text form, a `Point` included. Neither does a
`BoundingBox`, a `Circle`, an `EastingNorthing` or a `MapTile`.
`text.from` and `text.parse` on one are `type.mismatch`, as on a
`PostalAddress`. Every text form these have (WKT, the `geo:` URI, a
tile's `z/x/y`, a box's four numbers) depends on the order of its
numbers, and an axis order exists only at an edge that names it. WKT is a
format, not a type: only
the two WKT operations ([Interop](geospatial-interop.md)) read and write it.

## GeoJSON

A geometry's JSON is GeoJSON (`Json::GeoJson`), read and written by
`jadpo-value`'s `geo` module, the one place where `[longitude, latitude]`
order exists. Each of the five geometries is its RFC 7946 object.
`Geometry` is the object of the geometry it holds, its `type` saying
which; it is never the tagged form other choices are sent in
([Enums](enums.md)).

Reading is strict where RFC 7946 says MUST, and refuses what this page
leaves out:

- a latitude or a longitude out of range is refused, as `min` or `max` at
  its place in `coordinates`;
- a position is two or three numbers. More are refused, because RFC 7946
  says not to extend it. A geometry's positions all have an altitude or
  none do (`altitudes_agree`; see
  [Altitudes are all or nothing](predicates-and-shapes.md#altitudes-are-all-or-nothing));
- a line has two points or more, and a ring four or more, closed so that
  its last point is its first. Otherwise it is `min_length` or `format`;
- a multi-geometry has at least one part, though RFC 7946 allows none,
  and a polygon has at least its outer ring;
- the members are `type`, `coordinates` and `bbox`. A `bbox` is checked
  as four or six numbers and otherwise ignored, because a value's box is
  worked out from its points. `crs` and any other foreign member are
  `unknown_field`, as every input refuses a field it doesn't have, and
  WGS84 is the only coordinate system;
- `GeometryCollection` is refused. So are a `Feature` and a
  `FeatureCollection` where a geometry goes. Those two are standard types
  of their own, `Feature<G, P>` and `FeatureCollection<G, P>`, read where
  one is the type
  ([Interop](geospatial-interop.md)).

**Ring winding.** A ring wound either way is read, and rewound to the
right-hand rule: the outer ring counterclockwise and each hole clockwise.
RFC 7946 §3.1.6 asks a parser not to refuse a ring wound the other way.
Every ring is written by the rule, including a polygon built in code. The
winding is found exactly, on the plane of longitude and latitude, each
rounded to 9 places. A ring with no area is kept as it is. Whether the
holes sit inside the outer ring and whether rings cross is the rule
`rings_valid`
([Polygons are valid when built](predicates-and-shapes.md#polygons-are-valid-when-built)).

**Numbers on the wire.** RFC 7946 is I-JSON, so a coordinate is read as
the double it is and held as the shortest decimal that is that double; a
whole number is held exactly. It is written as a JSON number with its
places, and an altitude in metres with no trailing zeros. A `Latitude`,
`Longitude` or `Bearing` on its own is a `Decimal`, and is sent as text,
as every `Decimal` is.

## Boxes, circles and other types

- A **`BoundingBox`** is its sides by name, `south`, `west`, `north` and
  `east`, sent as named fields, never as RFC 7946's `bbox` array. Its
  south is at or below its north, its rule `south_not_above_north`. A
  west that is east of its east crosses the antimeridian (RFC 7946 §5.2).
  It is shown from its south-west corner to its north-east one.
- A **`Circle`** is a `centre`, a `Point`, and a `radius`, a `Length`
  never below 0. It is sent as named fields, its centre as GeoJSON, and
  shown `1,000 m around 51.5072° N, 0.1276° W`.
- A **`Crs`**, a coordinate reference system, is `EPSG:` and its code, 1
  to 6 digits with no leading zero. WGS84 is `EPSG:4326`. URNs and
  `OGC:CRS84` aren't read.
- A **`Geohash`** is 1 to 12 characters of its base 32, in lower case
  only.
- An **`H3Cell`** is a cell of Uber's H3 grid, its index (H3's mode 1) as
  15 lower-case hexadecimal digits. Its bits are checked: reserved bits
  clear, a resolution from 0 to 15, one of the 122 base cells, a digit
  from 0 to 6 for each resolution it has and 7 for each it hasn't, and no
  deleted subsequence on a pentagon. An edge's or a vertex's index isn't a
  cell.
- A **`MapTile`** is a tile in OpenStreetMap's XYZ scheme, its row counted
  from the north, not TMS's. Its `zoom` is from 0 to 30, which keeps `x`
  and `y` within a 32-bit integer, as PostGIS's `ST_TileEnvelope` takes
  them. Each of `x` and `y` is below 2 to the power of the zoom, its rule
  `within_zoom`. It is shown as a tile's URL writes it, `10/511/340`.
- An **`EastingNorthing`** is a position on a national grid: an `easting`
  and a `northing`, `Decimal`s held as written, and its `Crs`. It is shown
  with labels, `easting 530,000, northing 180,000 (EPSG:27700)`. It isn't
  converted to WGS84 ([What geospatial leaves out, and what to use instead](geospatial-left-out.md)).

## Shown, stored and matched

**Shown to people:** a latitude or a longitude in the locale's digits,
with a degree sign and its hemisphere's letter, `N`, `S`, `E` or `W` in
every locale, and no letter for 0. A point is shown as its latitude, then
its longitude, then its altitude. A line or a ring is its points in
brackets, joined by `; `, and a geometry made of others is theirs in
brackets, joined by `, `.

**Stored:** a geometry is one PostGIS `geography` column, checked to its
type (`Storage::Geography`), and a `BoundingBox` and a `Circle` are named
columns ([Storage](geospatial-storage.md)). A `Latitude`, `Longitude`
or `Bearing` is one `numeric` column; a `Crs`, `Geohash` or `H3Cell` one
`text` column; and a `MapTile` and an `EastingNorthing` a column per
part, an easting's and a northing's `numeric`.

**Shapes:** `type.standard_exists` matches `Point`'s shape: a latitude and
a longitude by their common names, each built on `Int` or `Decimal`.
`LineString`'s and `Polygon`'s shapes match through a list of a type that
matches `Point`. They are one shape, so the error names both
([Packages use the standard types](packages-use-the-standard-types.md)).

**Not personal data:** a position is as often where a depot or a sensor
is as where a person is. To mark a person's position, use the
[`location` category](marking-personal-fields.md).

[What is built: Geospatial](geospatial-built.md) says which
parts of geospatial work are built.
