Meridian handbook
Content modellingFields

Places

The geo field (a point), the geo_shape field (an area boundary as GeoJSON) and the address field (a country-aware postal address), with parameters, validation, stored shapes and template values.

All three are held once for every locale: where a place is does not change with the language.

geo

A point on the map: where a listing is, where a walk starts. The core event and listing types have a required, indexed location, and area an optional centre.

fields:
  - name: startPoint
    type: geo
    required: true
    index: true
    bounds: { min_lat: 49.8, min_lon: -8.7, max_lat: 60.9, max_lon: 1.8 }
ParameterTypeDefaultWhat it does
bounds{ min_lat, min_lon, max_lat, max_lon }noneThe point must fall inside this box.
CodeMessage
geoEnter a latitude and a longitude
latLatitude must be between -90 and 90
lonLongitude must be between -180 and 180
precisionPrecision must be one of rooftop, street, locality, approximate
boundsThe point is outside the allowed area

Stored value: { "lat": 52.0629, "lon": -1.3398, "precision": "rooftop" }. precision is optional and one of rooftop, street, locality or approximate, from a geocoder’s exact match down to a hand-placed pin. With index: true the latitude and longitude are projected, so lists can filter by a bounding box and by distance.

Template value:

Property or methodWhat it is
lat, lonThe coordinates.
precisionAs stored, or empty.
derivedTrue on an area-style centre the engine took from the type’s boundary (below).
distanceTo(other)The great-circle distance in kilometres to another point (any value with lat and lon).
staticMap(...)A map image of the point drawn by the engine from the install’s tile provider and served from the site’s own origin, with the provider’s credit. Prints nothing when maps.tiles is not set in engine.yaml (maps); a map is a core feature, in both bundles. Options: width and height (64 to 1280; 600 and two thirds of the width by default), zoom (15), alt, name, marker (true), and any HTML attribute for the figure.
{% for l in entry.nearbyListings %}
<p>{{ l.title }}, {{ entry.startPoint.distanceTo(l.location)|number:1 }} km away</p>
{% endfor %}
{{ entry.startPoint.staticMap(width=480, zoom=14) }}

Printing the value itself shows nothing useful: read lat and lon. |distance formats metres in the viewer’s measurement system (Filters).

geo_shape

An area with an edge: a town, a national park, a council ward. The DMO bundle’s core area type has a required boundary of this type, which is what “listings in this area” containment queries test against. Use a shape, not a point with a radius, wherever “in” matters.

fields:
  - name: boundary
    type: geo_shape
    geometries: [Polygon, MultiPolygon]
    max_vertices: 10000
    simplify_m: 5
ParameterTypeDefaultWhat it does
geometrieslist of Polygon, MultiPolygonbothThe geometry types accepted. Points and lines are never accepted.
max_verticeswhole number10000Above this, the shape is simplified on save until it fits.
simplify_mnumbernoneA Douglas-Peucker tolerance in metres applied on every save.

The editor takes pasted GeoJSON (a geometry, a Feature or a one-feature FeatureCollection) or an uploaded GeoJSON or KML file, and draws the shape for checking. On save the shape is normalised: rings closed, duplicate consecutive vertices dropped, coordinates rounded to six decimal places, shells counter-clockwise and holes clockwise.

CodeMessage
geojsonNot a usable shape: (the reason)
geometryMust be a Polygon or MultiPolygon
shapeThe polygon, ring and vertex that are wrong: too few vertices, coordinates off the globe, a self-intersection, a hole outside its shell

Stored value: standard GeoJSON in WGS 84, coordinates as [lon, lat]:

{ "type": "Polygon", "coordinates": [[[-1.36, 52.05], [-1.33, 52.05], [-1.33, 52.07], [-1.36, 52.07], [-1.36, 52.05]]] }

A shape is not written to the query index as such; its bounding box, centroid and area are kept for containment and distance queries.

Template value:

Property or methodWhat it is
typePolygon or MultiPolygon.
geometryThe GeoJSON, for a map script (`{{ entry.boundary.geometry
verticesHow many vertices it has.
areaKm2Its area in square kilometres, to two places.
bbox{ minLat, minLon, maxLat, maxLon }.
centroid{ lat, lon }.
contains(point)Whether a point (any value with lat and lon) is inside.

The centre of a shape. A type with a geo_shape field and a geo field named centre gets a centre in templates even when the editor sets none: the shape’s centroid, with precision: approximate and derived: true. A centre the editor sets always wins, since the centroid of an odd shape can fall in a field.

A derived centre has lat, lon and distanceTo(), but no staticMap(): entry.centre.staticMap(...) on it prints nothing, with no error from check or render. The shape’s own centroid does have staticMap(), so draw the map from whichever exists:

{% if entry.centre and not entry.centre.derived %}{{ entry.centre.staticMap(width=480) }}{% elif entry.boundary %}{{ entry.boundary.centroid.staticMap(width=480, marker=false) }}{% endif %}

address

A postal address whose parts follow the country: the components, which are required, the order of lines and the postal code pattern come from a built-in country dataset, so no country is refused and none is assumed.

fields:
  - name: address
    type: address
    index: true
    country: GB
    countries: [GB, IE]
    geo: true
ParameterTypeDefaultWhat it does
countryISO 3166 codethe site’s countryThe country the editor offers first.
countrieslist of codesevery countryRestricts the countries accepted.
geobooleanfalseOffers a pin alongside the address.

Components. The keys an address holds depend on the country. The common ones are line1, line2, dependent_locality, locality, administrative_area, postal_code and sorting_code; a country with no shipped data offers line1 (required), line2, locality, administrative_area and postal_code. The editor shows each country’s own labels (for the United Kingdom, “Postcode” and “Town or city”).

CodeMessage
country_emptyChoose a country
country_allowedMust be an address in GB, IE
required(Label) is required, at the component
postal_code(Value) is not a valid (postal code label)
script(Code) is not a script code

Stored value:

{
  "country": "GB",
  "components": { "line1": "Brewery Lane", "locality": "Hook Norton", "administrative_area": "Oxfordshire", "postal_code": "OX15 5NY" },
  "formatted": ["Brewery Lane", "Hook Norton", "Oxfordshire", "OX15 5NY"],
  "geo": { "lat": 51.999, "lon": -1.499 }
}

formatted is computed on every save from the country’s layout; you never write it. variants may hold the same address in another script, keyed by ISO 15924 code ("Latn": { "components": { ... } }), each formatted in its own layout. With index: true the country, locality, administrative area and postal code are projected as address.country, address.locality, address.administrative_area and address.postal_code, and the pin as address.geo, so “listings in Banbury” is an indexed query.

Template value:

PropertyWhat it is
formattedThe lines, in the country’s order, as a list.
oneLineThe lines joined with commas.
componentsThe components by key.
country, countryNameThe code and the country’s name.
scriptThe script the lines are in.
geoThe pin as a geo value, or nothing.

A page in a locale whose script differs from the country’s, with a variant in that script, shows the variant: a Japanese address reads in Latin script on an English page when the editor gave a romanised form.

<address>{% for line in entry.address.formatted %}{{ line }}{% if not forloop.Last %}<br />{% endif %}{% endfor %}</address>