Geo fields
Store points, lines, polygons and their multi-part forms in collections as GeoJSON, and import or export geographic data.
#Geo fields
A geo field stores one GeoJSON geometry per record. There are seven geo field types:
| Type | Stores | Use for |
|---|---|---|
point |
A single [lng, lat] coordinate |
Shop locations, waypoints, addresses |
linestring |
An ordered sequence of coordinates | Routes, trails, pipelines |
polygon |
A closed ring of coordinates (with optional holes) | Boundaries, zones, catchment areas |
multipoint |
Several coordinates as one value | A chain of shops, every entrance to a park |
multilinestring |
Several disconnected lines as one value | A river and its tributaries |
multipolygon |
Several separate areas as one value | Island groups, a council split by a river |
geometry |
Any of the above — the editor picks the type at input time | Mixed datasets |
Each of the first six accepts only its own GeoJSON type. A multipolygon field refuses a bare Polygon exactly as a polygon field refuses a MultiPolygon; if a collection needs both, the field is a geometry. Being strict is the point — anything reading the column back can rely on the shape.
GeometryCollection is not supported by any of them. It mixes geometry types inside one value, and every per-part rule in the spatial index assumes one type per value. Store one in a json field instead; it will not be spatially indexed.
#Parts
A multi geometry is a list of parts — the members of a MultiPolygon are whole polygons, the members of a MultiLineString whole lines. The point of storing them as one value rather than as several records is that they are one thing with one set of attributes: an island group has a single name, a single owner, a single set of dates.
Not every operator sees the parts, though, and Spatial queries says which do. _geo_within and _geo_dwithin resolve a record to a single stored point instead of testing its parts — a permanent property of those two rather than a gap waiting to be filled.
A one-part multi geometry is perfectly valid, and common — many exporters emit MultiPoint for a single dropped pin. The dashboard's map editor draws one part at a time and wraps it for you; several parts are entered through its GeoJSON editor.
#How storage works
Each geo field named {name} expands into twelve physical columns in the collection's D1 table:
| Column | Type | Purpose |
|---|---|---|
{name}_json |
TEXT |
Canonical GeoJSON geometry |
{name}_lat |
REAL |
Centroid latitude |
{name}_lng |
REAL |
Centroid longitude |
{name}_rep_lat |
REAL |
Representative point latitude — a point guaranteed inside the geometry |
{name}_rep_lng |
REAL |
Representative point longitude |
{name}_s2_13 |
INTEGER |
Spatial index cell ID, coarse level |
{name}_s2_17 |
INTEGER |
Spatial index cell ID, fine level |
{name}_bbox |
TEXT |
Bounding box [west, south, east, north] (non-point geometries only) |
{name}_bbox_w |
REAL |
The bounding box's west edge, indexed |
{name}_bbox_s |
REAL |
The bounding box's south edge, indexed |
{name}_bbox_e |
REAL |
The bounding box's east edge, indexed |
{name}_bbox_n |
REAL |
The bounding box's north edge, indexed |
The bounding box is stored twice on purpose. {name}_bbox is the readable
canonical value; the four _bbox_* columns are the same four numbers in a shape
a database index can search, which is what keeps a viewport query off a full
table scan. Both are written from one computation, so they cannot disagree.
Everything except points also stores covering cell ranges in an _s2_coverings table, so spatial queries can find a record by its extent rather than only by a single point. The covering is computed per part, so an island group is indexed as its islands rather than as one box around the sea between them.
#The two stored points
A geometry that is not a point stores two points standing in for it, and they answer different questions:
- Centroid (
_lat/_lng) — the balance point. It is a good "where is this, roughly" for sorting and for placing a label, and it is what distance calculations use. It is not guaranteed to lie inside the geometry: the centroid of a horseshoe is in the gap, and the centroid of an archipelago is in open water. - Representative Point (
_rep_lat/_rep_lng) — a point guaranteed to lie inside the geometry, in its largest part. This is the one to use when "inside" is what matters.
Both are computed on write; there is nothing to configure. The reasoning is recorded in ADR 0021.
#Accepted input formats
When you write a record, the geo field accepts:
- A GeoJSON geometry object:
{ "type": "Point", "coordinates": [151.2093, -33.8688] } - A
{ lat, lng }shorthand object (points only), converted to a GeoJSON Point - Either of the above as a JSON string
Coordinates in GeoJSON are always [longitude, latitude] — the reverse of the { lat, lng } shorthand.
A single part whose consecutive coordinates jump the antimeridian (180°) is refused on write, for every geo type — within one part, longitude is a flat number line. Split such data at 180° into several parts, as RFC 7946 §3.1.9 directs; that IS one of the things multi geometries are for, and a split geometry is stored and queried correctly, its bounding box taken on the shortest arc of longitude containing every part. See ADR 0022.
#HTTP
POST /api/v1/collections/campsites/records
Authorization: Bearer sk-your-api-key
Content-Type: application/json
{
"name": "Acacia Flat",
"location": { "type": "Point", "coordinates": [150.3542, -33.6412] }
}
#SDK
import { EmuView } from '@emuview/sdk';
const sdk = new EmuView({ url: 'https://your-api.example.com', apiKey: 'sk-your-api-key' });
const campsite = await sdk.collection('campsites').create({
name: 'Acacia Flat',
location: { lat: -33.6412, lng: 150.3542 } // shorthand — stored as a GeoJSON Point
});
#Editing with the map input
In the record form, a geo field renders as an interactive map:
- Point placement — click the map or drag the marker
- Line drawing — click to add vertices, double-click to finish
- Polygon drawing — click to add vertices, click the first vertex to close the ring
- Manual coordinates — lat/lng number inputs (point mode)
- GPS location — a "use my location" button backed by browser geolocation (point mode)
- GeoJSON editor — a raw JSON text editor, for pasting geometries. It applies the same validation the API does, so a geometry the field cannot hold — a
MultiPolygonpasted into apointfield, say — is rejected in the form rather than at save time - Address search — geocoding via OpenStreetMap Nominatim, showing up to five results
- Geometry mode switcher — Point / Line / Polygon buttons, shown for
geometryfields
Existing lines and polygons can be edited vertex by vertex: drag a vertex to move it, click + to insert one, Shift+click to delete one.
#When the map is read-only
The vertex editor models a shape as one flat list of positions, so there are shapes it cannot change without losing part of them. Rather than change them anyway, it shows the geometry read-only, says why, and leaves the GeoJSON editor available — which can edit any of them.
That happens in two cases:
- A shape the vertex list cannot rebuild. A polygon with interior rings (holes), and the three multi-part types —
MultiPoint,MultiLineStringandMultiPolygon. The rule is the round trip itself, not a list of types: if reading the geometry into vertices and rebuilding it does not return the same geometry, the map will not open it. - More than 500 vertices, whatever the type. The editor creates about two map markers and two DOM nodes per vertex and rebuilds all of them after every change. A single imported administrative boundary is far above this.
Multi-part geometries still display normally, both here and in map views — only editing them on the map is refused. Drawing a multi-part geometry with several parts by hand is not supported; import one, or paste it into the GeoJSON editor. Drawing ONE part is: click a multipoint field and you get a one-member MultiPoint, draw on a multipolygon field and you get a one-member MultiPolygon. There is no gesture for "start a second island".
#Map input options
Configure the input per field via interfaceOptions:
| Option | Type | Default | Description |
|---|---|---|---|
mapInitialCenter |
[lng, lat] |
[0, 20] |
Map centre when the field is empty |
mapZoom |
number |
2 |
Initial zoom level when the field is empty |
mapStyle |
string |
System basemap | MapLibre style URL for the editor basemap |
geocodingEnabled |
boolean |
true for point and multipoint fields |
Show the address search bar |
gpsLocation |
boolean |
true |
Show the GPS location button in point mode |
#Importing geo data
Import records from a file with POST /api/v1/collections/:name/records/import. Four formats are supported, detected from the file extension or content when format isn't given:
| Format | Detection | Notes |
|---|---|---|
| GeoJSON | .geojson, .json, or JSON content |
Accepts a FeatureCollection, a Feature array, or a single Feature. Feature properties map to fields by name |
| CSV | .csv |
Latitude/longitude columns map to a point field; other columns map to fields by header name |
| KML | .kml or XML content |
Placemarks convert to GeoJSON; ExtendedData becomes properties |
| GPX | .gpx or XML content |
Waypoints, tracks, and routes convert to GeoJSON |
In the dashboard, the import dialog walks through four steps: Upload → Mapping (CSV only) → Importing → Complete.
const result = await sdk.collection('campsites').geoImport(file, {
format: 'geojson', // or 'csv' — omit to auto-detect
geoField: 'location', // target geo field; defaults to the first geo field
latColumn: 'lat', // CSV only
lngColumn: 'lng' // CSV only
});
// { imported: 120, failed: 2, total: 122, errors: [...] }
#Exporting geo data
Export records with GET /api/v1/collections/:name/records/export:
| Format | Content type |
|---|---|
csv |
text/csv |
json |
application/json |
geojson |
application/geo+json |
GeoJSON export produces a FeatureCollection. Use geo_field to choose which geo field supplies the geometry when a collection has more than one; other fields become feature properties.
const blob = await sdk.collection('campsites').export({
format: 'geojson',
geoField: 'location'
});
Exports respect row-level access rules — users only export records they can read.
#Related concepts
- Spatial queries — filter records by location using the stored index
- Field types — all collection field types, including geo types
- Map views — browse and style geo records on a map