Schema
Dive Site Data Model
Each dive site is stored as a row with typed, indexed columns for the fields
you filter on most (location, environment, depth, entry, region, sea) and a flexible
tags JSONB field for additional
objective metadata (local names). This hybrid approach gives you fast queries
without rigid migrations.
Site classification uses two independent dimensions: environment (where the dive happens — ocean, lake, river, etc.) and topologies (terrain features — reef, wall, wreck, etc.). A site has exactly one environment but can have multiple topologies.
All measurements use SI / metric units as the canonical format,
following international diving standards (PADI, SSI).
max_depth is always in meters.
Client applications handle imperial conversion for display (meters × 3.28084 = feet).
Fields
id VARCHAR(6) auto-generated Primary key. 6-character base36 string (a-z, 0-9).
name TEXT required Canonical name of the dive site — the locally used name or the one most recognized by the international diving community. Not a translation. Localized alternatives go in tags as names_{lang} arrays.
latitude DOUBLE PRECISION required Decimal degrees, WGS 84. Range: -90 to 90.
longitude DOUBLE PRECISION required Decimal degrees, WGS 84. Range: -180 to 180.
country_code CHAR(2) required ISO 3166-1 alpha-2 country code. Examples: MX, ID, BZ.
country_name TEXT computed Human-readable country name resolved via JOIN from the countries lookup table. E.g. Mexico, Indonesia.
sea_mrgid INTEGER optional Marine Regions
MRGID identifying the IHO Sea Area. E.g. 4314 = Gulf of California, 4287 = Caribbean Sea.
Foreign key to a local seas lookup table.
sea_name TEXT computed Human-readable sea name resolved via JOIN from the seas lookup table. Not stored on the dive site row itself.
environment TEXT required Where the dive happens. Exactly one value per site. Default: ocean.
topologies TEXT[] required Terrain features of the dive site. One or more values per site. Stored in a separate junction table, returned as a JSON array in the API.
max_depth SMALLINT optional Maximum depth in meters. Always metric (SI).
entry TEXT required How divers enter the water at this site. Default: boat.
tags JSONB optional Flexible key-value store for additional metadata. See tags reference below.
created_at TIMESTAMPTZ auto Set automatically on insert.
updated_at TIMESTAMPTZ auto Updated automatically on every edit via trigger.
Tags reference
The tags field accepts any valid JSON object.
The following keys are conventionally used and understood by the API and frontend.
names_es / names_en / ...
string[]
Localized names as arrays, keyed by names_{lang} using
ISO 639-1 language codes.
First element is the preferred name for that language. A site can have multiple names in the same language.
Follows the OpenStreetMap /
WoRMS pattern of one canonical ID with localized labels.
GeoJSON output
The API returns dive sites as GeoJSON (RFC 7946). A single site from
GET /sites/:id returns a Feature:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [longitude, latitude]
},
"properties": {
"id": "a7x3km",
"name": "string",
"country_code": "XX",
"country_name": "string",
"sea_mrgid": 1234,
"sea_name": "string",
"environment": "ocean | lake | river | ...",
"topologies": ["reef", "wall", ...],
"max_depth": 0,
"entry": "shore | boat | other",
"tags": { ... },
"created_at": "ISO 8601",
"updated_at": "ISO 8601"
}
} GET /sites returns a FeatureCollection wrapping
an array of Features in the same shape. Supports filters:
?country=,
?entry=,
?environment=,
?topology=,
?region=,
?sea= (MRGID),
?bbox=west,south,east,north.
Example
A real record from the database — El Bajo Seamount, Gulf of California:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-110.3981, 24.2156]
},
"properties": {
"id": "a7x3km",
"name": "El Bajo Seamount",
"country_code": "MX",
"country_name": "Mexico",
"sea_mrgid": 4314,
"sea_name": "Gulf of California",
"environment": "ocean",
"topologies": ["pinnacle"],
"max_depth": 40,
"entry": "boat",
"tags": {
"names_es": ["El Bajo", "El Bajito"]
}
}
} Version history
Every update to a dive site automatically snapshots the previous version into
dive_sites_history via a database trigger.
Retrieve past versions with GET /sites/:id/history,
which returns an array ordered by most recent change first.