GeoJSON and vector tiles
Spatibase serves published map data in two forms: entries.geojson, a paged GeoJSON FeatureCollection with every field, and vector tiles (Mapbox Vector Tile, one layer named entries) described by a TileJSON document. This page is for developers putting Spatibase content into MapLibre GL, Mapbox GL, Leaflet, QGIS or ArcGIS.
Examples use $API for the project’s Content Delivery API base URL; $PROJECT_ID is the UUID in the project’s address in the web app:
export API=https://api.spatibase.com/delivery/v1/projects/$PROJECT_ID
Should I use GeoJSON or vector tiles?
Use GeoJSON for small and medium projects when you want every field in one request; use vector tiles for large projects and GIS tools, because they load only what is on screen. Both contain only the main locations of published entries of enabled types.
entries.geojson |
Vector tiles | |
|---|---|---|
| Content | every field, nested values intact | id, type, title, color and scalar fields only |
| Size | up to 5000 features per request (page for more) | any size, loaded tile by tile |
| Filters | every /entries parameter |
type, subtypes and group only |
| Geometry | exact coordinates (7 decimals) | clipped and simplified per zoom level |
| Clients | Leaflet, MapLibre or Mapbox geojson source, QGIS, scripts |
MapLibre or Mapbox vector source, QGIS |
To list every published entry as just id, type, title and bounding box, without fields, use entries.index (compact index); it pages like entries.geojson.
GeoJSON
entries.geojson returns published entries that have a main location as a GeoJSON FeatureCollection:
https://api.spatibase.com/delivery/v1/projects/$PROJECT_ID/entries.geojson
- It takes every
/entriesparameter:type,subtypes,group,q,filter[...],bbox,near,radius,within,withinEntry,withinField,sort,pageandinclude.limitdefaults to 1000, at most 5000. - The response has
Content-Type: application/geo+json; charset=utf-8, with the same caching and errors as the rest of the Content Delivery API. - Left out: entries without a main location, entries of types with
locationMode: none(even if a location is stored), drafts, unpublished entries and switched-off types.meta.totalcounts only the features that qualify.
To save a file:
curl -g -o entries.geojson "$API/entries.geojson?limit=5000"
What does a GeoJSON feature contain?
Each feature has the entry id as its id, the main location as its geometry, and properties with id, type, title and then every field.
curl -sg "$API/entries.geojson?type=store-location&limit=1"
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "192a713e-995a-4cb6-a84a-a5da45e9646a",
"geometry": {"type": "Point", "coordinates": [-124.0828, 40.8665]},
"properties": {
"id": "192a713e-995a-4cb6-a84a-a5da45e9646a",
"type": "store-location",
"title": "Pacific Coast Outdoors – Arcata",
"rental-phone": "+1 707-555-0135",
"region": {"id": "66f36784-bb65-4de3-a745-bfd294cdf017", "type": "region", "title": "Northern California Coast"},
"opens-at": "09:00",
"delivery-area": {"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [...]}, "properties": {}}]}
}
}
],
"meta": {"page": 1, "limit": 1, "total": 4, "totalPages": 4},
"links": {
"self": "https://api.spatibase.com/delivery/v1/projects/6806f635-636f-4814-ae20-988d4c84369e/entries.geojson?type=store-location&limit=1",
"next": "https://api.spatibase.com/delivery/v1/projects/6806f635-636f-4814-ae20-988d4c84369e/entries.geojson?type=store-location&limit=1&page=2",
"prev": null
}
}
- Field values have the same shapes as in the JSON API (Field values). Element apiKeys cannot be
id,typeortitle, so fields never overwrite them. - Not included:
typePath,groups, the timestamps anddistance.nearandsort=distancestill filter and order the features. Use/entrieswhen you need those keys. geometryis aPoint,LineString,Polygonor aMulti*form, in WGS84[lng, lat].metaandlinksare GeoJSON “foreign members” (RFC 7946, section 6.1), which map libraries ignore. To get more than 5000 features, followlinks.next, or use vector tiles.- Nested values (objects and arrays) stay nested. MapLibre GL turns nested properties into JSON strings when it loads a
geojsonsource, soJSON.parsethem in event handlers, or load the entry from/entries/<id>.
Vector tiles
Vector tiles serve published main locations as Mapbox Vector Tiles at this URL template:
https://api.spatibase.com/delivery/v1/projects/$PROJECT_ID/tiles/{z}/{x}/{y}.pbf
- XYZ scheme (y counted from the top, as in OpenStreetMap), Web Mercator,
zfrom 0 to 22,xandyfrom 0 to 2^z - 1. Anything else is a400JSON error, such asTile z must be an integer between 0 and 22; other extensions such as.mvtare a404. - A tile with features is a
200withContent-Type: application/vnd.mapbox-vector-tile(MVT version 2, not gzip-compressed). A tile with no features is204 No Content, with the sameCache-ControlandETagas tiles with data. - Tiles are built on request from published main locations of enabled types whose
locationModeis notnone. For heavy traffic, put a CDN in front.
Which layer and properties do tiles have?
Every tile has one layer named entries (extent 4096, buffer 64) with points, lines and polygons mixed. Geometries are clipped to the tile and simplified for the zoom level, so very small polygons and lines can disappear at low zoom. Multi* geometries become multi-part features.
Large projects stay light at low zoom. From zoom 0 to 8 a tile keeps one point per small grid cell (the entry with the lowest id stands for the others), so a world tile of a project with thousands of points stays well under 150 KB. Every point in those tiles has a point_count property, the number of entries it stands for (1 for a lone point). From zoom 9 on, tiles hold every point and have no point_count. Lines and polygons are not thinned, filters apply before thinning, and the same tile always gives the same bytes. Use point_count to size circles a little or to label a cluster; a click handler should still read the entry through /entries/<id>.
| Property | Type | Value |
|---|---|---|
id |
string | the entry id. The MVT feature id is not set; use promoteId for feature state |
type |
string | the type apiKey |
title |
string | the published title, at most 255 characters |
color |
string | the type’s color (Type color in the app), #rrggbb |
point_count |
number | only at zoom 0 to 8, on points: how many entries this point stands for |
<apiKey> |
string, number or boolean | scalar fields of the kinds text (not the JSON editor), number, boolean, date (single values, not ranges), choice, color, identifier and phone |
- Strings are cut at 255 characters. Multiple choice values are joined with
", ", for example"amenities": "restrooms, parking, camping". Empty fields are absent, notnull. - Values follow the field’s current settings, like the JSON output: a choice that is no longer an option is left out, and a field switched between single and multiple choice, or a date whose mode changed, shows the current shape. A field switched to a date range leaves the tiles.
- Not in tiles:
markdown,reference,link,images,files,contact,geo,embed,location, JSON text and date ranges. Load the entry from/entries/<id>when someone clicks a feature. - The fixed properties win over an element with the same apiKey; only
colorcan clash.
Filter tiles
Tiles accept type (including subtypes unless subtypes=false) and group, with the same rules and 400 errors as /entries. Every other parameter (filter[...], q, bbox, near, sort, include) is ignored without an error.
https://api.spatibase.com/delivery/v1/projects/$PROJECT_ID/tiles/{z}/{x}/{y}.pbf?type=park
To filter by field values, use style expressions on the client, for example ['>=', ['get', 'rating'], 4.5].
How are tiles cached?
Tiles use the Content Delivery API’s caching headers (Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=86400) and an ETag that changes with the project’s content version. A publish reaches map viewers within that cache window, and the tile URLs themselves never change.
TileJSON
tiles.json is a TileJSON 3.0 document that tells a map library the tile URL, zoom range, bounds and field names. Point a vector source at it instead of hard-coding the tile template.
curl -s "$API/tiles.json"
{
"tilejson": "3.0.0",
"name": "Pacific Coast Outdoors",
"description": "Parks, regions, stores and trip ideas along the Pacific coast of California, Oregon and Washington.",
"version": "12",
"scheme": "xyz",
"tiles": ["https://api.spatibase.com/delivery/v1/projects/6806f635-636f-4814-ae20-988d4c84369e/tiles/{z}/{x}/{y}.pbf"],
"minzoom": 0,
"maxzoom": 22,
"bounds": [-124.85, 37.8, -122.45, 48.42],
"center": [-123.65, 43.11, 6],
"vector_layers": [{
"id": "entries",
"description": "Published entries with a main location",
"fields": {"id": "String", "type": "String", "title": "String", "color": "String", "point_count": "Number", "slug": "String", "amenities": "String", "rating": "Number", "opens-at": "String"},
"minzoom": 0,
"maxzoom": 22
}]
}
versionis the project’s content version as a string.descriptionis left out when the project has none.boundsis the extent of all published main locations; a project without any gets[-180, -85.0511, 180, 85.0511].centeris the middle ofboundsat zoom 6.fieldslists every tile property:Numberfor number fields,Booleanfor boolean fields,Stringfor the rest. It covers the scalar elements of all enabled types, so a field can be listed even if no tile contains it.- The
tilestemplate has no query string, except?api_key=when the TileJSON of a private project was requested with one. A source built from the TileJSON URL is therefore unfiltered; for a filtered source, pass thetilesarray yourself (see the MapLibre example below).
How do tiles and GeoJSON work in a private project?
Tiles, TileJSON and GeoJSON of a private project need an API key like every other Content Delivery API endpoint. Map libraries and QGIS load tile URLs without custom headers, so request tiles.json?api_key=$API_KEY: the tiles template in that response carries ?api_key= as well.
- If you write the tile template yourself, append
?api_key=$API_KEY, or&api_key=$API_KEYafter?type=.... - In MapLibre GL or Mapbox GL you can send a header instead, only to Spatibase URLs:
const map = new maplibregl.Map({
container: 'map',
style: 'https://tiles.openfreemap.org/styles/liberty',
transformRequest: url => url.startsWith('https://api.spatibase.com/delivery/')
? { url, headers: { 'X-API-Key': API_KEY } }
: { url }
})
- Only read keys (for a website or app) work in
?api_key=; edit keys for AI assistants are refused there. - A key used in a web page is visible to anyone who can open the page. Give each site its own key. Details are in Authentication.
MapLibre GL JS and Mapbox GL JS
MapLibre GL JS reads the TileJSON URL directly as a vector source. This snippet draws areas and points in their type colors, and adds a second source that only contains parks:
const API = `https://api.spatibase.com/delivery/v1/projects/${PROJECT_ID}`
map.on('load', () => {
map.addSource('spatibase', {
type: 'vector',
url: `${API}/tiles.json`,
promoteId: { entries: 'id' } // feature.id = entry id, for feature-state and click handling
})
map.addLayer({
id: 'entry-areas', type: 'fill', source: 'spatibase', 'source-layer': 'entries',
filter: ['==', ['geometry-type'], 'Polygon'],
paint: { 'fill-color': ['get', 'color'], 'fill-opacity': 0.35 }
})
map.addLayer({
id: 'entry-lines', type: 'line', source: 'spatibase', 'source-layer': 'entries',
filter: ['==', ['geometry-type'], 'LineString'],
paint: { 'line-color': ['get', 'color'], 'line-width': 3 }
})
map.addLayer({
id: 'entry-points', type: 'circle', source: 'spatibase', 'source-layer': 'entries',
filter: ['==', ['geometry-type'], 'Point'],
paint: { 'circle-radius': 6, 'circle-color': ['get', 'color'] }
})
// Filtered tiles: pass the template directly, with the query string
map.addSource('parks', {
type: 'vector',
tiles: [`${API}/tiles/{z}/{x}/{y}.pbf?type=park`],
minzoom: 0, maxzoom: 22
})
})
- The snippet assumes a map with a basemap style already loaded, for example
style: 'https://tiles.openfreemap.org/styles/liberty'(OpenFreeMap: free, no key). Spatibase serves your entries, not a basemap. Do not usetile.openstreetmap.orgas a basemap in production; OpenStreetMap’s public tile servers are not meant for apps. - On click,
e.features[0].propertiesholds the tile properties only. Fetch/entries/<id>for images, descriptions and other fields. - Mapbox GL JS uses the same source and layer definitions (style specification v8); Mapbox GL JS itself needs a Mapbox access token.
- For a
geojsonsource, setdatato theentries.geojson?limit=5000URL. A complete page is in the Quickstart. - Leaflet draws GeoJSON with
L.geoJSON; the Quickstart has a complete page.
QGIS
QGIS can load Spatibase as a vector tile layer (fast, few attributes) or as a GeoJSON layer (every attribute, a snapshot). The steps follow the QGIS 3 menus.
Vector tiles:
- Choose Layer, Add Layer, Add Vector Tile Layer….
- Choose New, then New Generic Connection….
- Enter any name, and as URL the tile template
https://api.spatibase.com/delivery/v1/projects/$PROJECT_ID/tiles/{z}/{x}/{y}.pbf(add?type=...to filter,?api_key=...for a private project). Set the minimum zoom to0and the maximum zoom to22. Leave the style URL empty. - Click OK, then Add. QGIS draws the layer
entrieswith a default style; style it by thetypeorcolorattributes.
GeoJSON with every attribute:
- Choose Layer, Add Layer, Add Vector Layer….
- Set the source type to Protocol: HTTP(S), cloud, etc. with type HTTP/HTTPS/FTP.
- As URI, enter
https://api.spatibase.com/delivery/v1/projects/$PROJECT_ID/entries.geojson?limit=5000(the default limit is 1000). - Click Add. Nested values such as images, references and geo fields are not flat attributes; depending on the QGIS version they appear as JSON text or are left out. The layer is a snapshot: reload it to see newly published content.
ArcGIS
ArcGIS Online and ArcGIS Pro can read the GeoJSON URL, which is the simplest way in. In ArcGIS Online’s Map Viewer, add a layer from a URL and give it https://api.spatibase.com/delivery/v1/projects/$PROJECT_ID/entries.geojson?limit=5000. The Content Delivery API allows any origin, so the request works from the browser.
ArcGIS draws vector tiles through a style document (a Mapbox GL style JSON or a VectorTileServer), and Spatibase serves no style document. To use the tiles, write a style JSON with a vector source pointing at the tile template and layers on the source layer entries, host it yourself, and add that style’s URL.
Related
- Preview your data: a ready-made map page of the same published data, no code needed
- Content Delivery API reference
- Quickstart
- Recipes
- Authentication
Updated