Exports & Integrations
Exports exist so that nothing you put into GeoLens is stuck there. Every format below is an open one that other tools already read, and the same data is also live over OGC API and STAC without any export step. If you want the full picture of how to move everything out, see Taking your data with you.
GeoLens exports any catalogued vector dataset to a portable file, so the data can move into another system. Vector datasets export to GeoPackage, GeoJSON, Shapefile, CSV, GeoParquet, FlatGeobuf, or PMTiles. Every export accepts optional bbox, attribute filter, and CRS reprojection at request time, so the file you save is exactly the subset you need.
Beyond file downloads, the catalog is also reachable through live standards URLs, OGC API Features and STAC, that you point a client at directly (these are standards endpoints, not export formats). Raster datasets are downloaded as a Cloud-Optimized GeoTIFF via a separate download route.
This page covers the seven export formats, the on-the-fly transformations, the OGC API + STAC integrations, the raster download route, and machine-client examples for QGIS, GDAL, Python, DuckDB, and MapLibre GL JS.
Export formats
Section titled “Export formats”GeoLens exports vector datasets in the following seven formats. The format
is selected in the Export card on the Access tab of any
dataset detail page, or by passing
?format=<slug> to the /api/datasets/{id}/export endpoint.
| Format | Slug | Output | Best for |
|---|---|---|---|
| GeoPackage | gpkg | .gpkg file | Vector with metadata preserved; the best general-purpose format (default) |
| GeoJSON | geojson | .geojson file | Web / portable; small to medium datasets |
| Shapefile | shp | .zip archive | Legacy GIS interop; required by some older tools |
| CSV | csv | .csv file | Tabular only; geometry preserved as WKT in a single column |
| GeoParquet | parquet | .parquet file | Columnar analytics; readable by DuckDB, GeoPandas, and QGIS |
| FlatGeobuf | fgb | .fgb file | Streaming-friendly single-file vector with a spatial index; fast partial reads over HTTP |
| PMTiles | pmtiles | .pmtiles file | Pre-rendered vector-tile pyramid (MapLibre, Protomaps); host it from any range-request-capable static server or object store |
gpkg is the default: calling /export with no format returns a
GeoPackage. Passing a slug outside this table returns an error. A dataset
with no geometry column exports as CSV only — the other six formats
return 400. Raster and VRT datasets have no feature table, so this route
refuses them with a 400 as well; use the raster download route below.
Both 1.16 additions are advertised the way the older formats are:
FlatGeobuf (application/vnd.flatgeobuf) and PMTiles
(application/vnd.pmtiles) appear as DCAT distributions and as assets on
the dataset’s OGC API record. (They do not appear under /api/stac — the
STAC catalog covers raster datasets only.)
An export is a URL, not a job. GET /api/datasets/{id}/export?format=<slug>
answers 200 with the file bytes; there is nothing to submit and no status to
poll, so a client can be a single URL inside read_parquet() or ogr2ogr.
There is a ceiling. An unfiltered export of a dataset with more than
5,000,000 features is refused with a 413, and so is a filtered export whose
selection still exceeds that count — narrow it with bbox or where.
GeoParquet counts the live table, so the cap applies even to datasets whose
feature count the catalog has not recorded.
The route also answers HEAD and byte-range requests against a cached export
artifact, which the server holds for about a minute after a build. The request
that builds the export is answered whole: a Range on a cold URL is generally
answered 200 with the entire file (a range starting at byte 0, or one resumed
with a matching If-Range, may still come back as a 206), and a cold HEAD
omits Content-Length, because building the file to measure it is the work
the probe is trying to avoid. So a tool that opens a file remotely (GDAL
/vsicurl/, DuckDB’s httpfs) pays one full conversion on its first open and
reads just the slices it needs from the warm artifact after that
(geolens#1585).
A warm artifact also carries a strong ETag, and honors conditional
requests ahead of the range logic above: If-Match that no longer matches
the current export answers 412 Precondition Failed (the export changed
since you fetched that ETag), and If-None-Match that matches answers 304 Not Modified. Preconditions only apply once an artifact is cached — a cold
URL has nothing to check a precondition against yet, and always answers
whole.
GeoParquet output is a spec-valid GeoParquet 1.1 file and is always
emitted in EPSG:4326 (OGC:CRS84) — combining format=parquet with any
other target_crs returns a 400. PMTiles is always rendered in EPSG:3857
(Web Mercator), tiling’s native projection, so any other target_crs
returns a 400 there too. The other five formats reproject freely.
A PMTiles export is a pre-cut pyramid of MVT vector tiles in a single
archive rather than a feature file. Any static file server or object store
that honors HTTP range requests can serve it to a map client directly, with
no tile server in the path — clients read the archive by byte offset, so a
host that ignores Range forces whole-file downloads. A browser client
loading the archive from another origin also needs CORS headers on that
host.
The pyramid’s depth adapts to the dataset’s extent: citywide data gets
street-level tiles down to zoom 14, while a global dataset stops around
zoom 8.
Raster downloads
Section titled “Raster downloads”Raster datasets aren’t part of the vector export card. Download a raster
as a Cloud-Optimized GeoTIFF (COG) from its
dataset detail page, which calls
GET /api/datasets/{id}/download/cog.
Unlike the vector /export route, the COG download always needs a
credential on the request, even for a public raster. Send X-Api-Key or an
Authorization header, or mint a short-lived download token with
POST /api/auth/download-token/{id} and pass it as ?token=; a session JWT
on ?token= is rejected. That mint accepts anonymous callers for public
datasets, so an unauthenticated script can still do this in two requests. A
generic client that won’t make that POST should read the raster through the
tile template instead — which is why the DCAT and STAC feeds advertise the
tiles and not this URL.
The COG download answers HEAD and byte-range requests against the stored
file, with the same conditional-request behavior as a cached vector export
above: it carries a strong ETag, a failed If-Match answers 412 Precondition Failed, and a matching If-None-Match answers 304 Not Modified. Unlike the vector export’s cache, the COG’s ETag is always
present — there’s no cold-URL exception here, since the file already exists
in storage rather than being built on request.
Live standards URLs (not export formats)
Section titled “Live standards URLs (not export formats)”OGC API Features and STAC are live URL endpoints, not file formats: you point an OGC/STAC client at the URL and it reads against live data, so subsequent edits to the underlying dataset propagate through automatically. See the OGC API access and STAC API sections below for the URL patterns, and OGC API & Standards Endpoints for the authoritative per-route reference.
Bbox + attribute filters at export time
Section titled “Bbox + attribute filters at export time”Any export can apply transformations at request time, before the file is written:
- Bbox: restrict the export to features (or raster pixels) within a
bounding box. Supplied as
?bbox=west,south,east,north(EPSG:4326). - Attribute filter: supply a SQL-style boolean expression in
?where=...to restrict the export to features matching it. This supports comparison operators,AND/OR/NOT,IN,IS NULL,LIKE/ILIKE, andBETWEENover the dataset’s columns only — no function calls and no spatial predicates (usebboxfor spatial restriction). - CRS reprojection: supply
?target_crs=EPSG:<code>(e.g.,?target_crs=EPSG:3857) to reproject geometry on output. Useful when the consuming tool expects a specific projection.?crs=is not a parameter on this route; it is ignored rather than refused, so you get a 200 with no reprojection applied.
These can be combined: ?format=gpkg&bbox=...&where=...&target_crs=...
exports a GeoPackage of only the features within the bbox that match the
attribute filter, reprojected.
The UI’s export control is format-only: pick a format and download. Bbox,
where, and target_crs are request-time parameters on
/api/datasets/{id}/export — use the URL, the CLI, or a script when you
need a filtered or reprojected file.
OGC API access
Section titled “OGC API access”GeoLens exposes the catalog as a set of live OGC API endpoints. Anything you can do through a UI export, you can also reach through a standards-based URL. Any client that speaks these standards (QGIS, ogr2ogr, owslib, pystac-client) reads them natively.
Conformance classes
Section titled “Conformance classes”GeoLens implements the following OGC API and STAC conformance classes:
OGC API
- OGC API Common Part 1 v1.0: Core, Landing Page, JSON
- OGC API Features Part 1 v1.0: Core, GeoJSON
- OGC API Features Part 3 v1.0: Queryables, Filter, Features Filter
- CQL2 v1.0: Text, JSON, Basic CQL2, Advanced Comparison Operators, Basic Spatial Functions
- OGC API Records Part 1 v1.0: Record Core, Query Parameters, Sorting, JSON
STAC API v1.0
- STAC API Core
- STAC Collections
- STAC Item Search
The authoritative list is GET /api/conformance on your own instance — the
classes above are what a stock 1.16 build advertises, and an older or newer
one may differ. For per-route schemas and curl examples on every endpoint,
see OGC API & Standards Endpoints. The overlap between
that page and this one is deliberate: this page keeps enough detail to finish
an export without leaving it.
Top-level endpoints
Section titled “Top-level endpoints”GET /api/: OGC API Common landing document; entry point for any OGC client.GET /api/conformance: list of conformance class URIs.GET /api/collections/datasets/items: OGC API Records collection; the catalog itself, listable and CQL2-filterable.GET /api/collections/{dataset_id}/items: OGC API Features collection; per-dataset feature access, CQL2-filterable since 1.16.GET /api/stac/: STAC root; for raster collections.GETorPOST /api/stac/search: STAC Item Search.
The OGC API Records endpoint mirrors the catalog search you use in the UI; the OGC API Features endpoints mirror the per-dataset data tab in machine-readable form.
STAC API
Section titled “STAC API”For raster collections, GeoLens also exposes a STAC 1.0 catalog at
/api/stac/. STAC clients can search items, list collections, and fetch
asset URLs:
GET /api/stac/: STAC root catalog (browseable directly from a browser).GET /api/stac/collections: list all STAC collections.GET /api/stac/collections/{id}: single collection metadata.GET /api/stac/collections/{id}/items: list items in a collection.GETorPOST /api/stac/search: full STAC search (bbox, datetime, collections, ids, intersects, limit). GET takes the same fields as query parameters.
Every raster item carries a raster_tiles asset whose href is an XYZ
tile template rather than a file; see
STAC 1.0 for the asset set and
Tile endpoints for how the template
authenticates.
Machine clients
Section titled “Machine clients”Once you know the OGC API or STAC URL, plugging it into a tool is a copy-paste exercise. Below are the most common client recipes. Runnable versions live in the examples repo.
QGIS speaks OGC API Features and OGC API Records natively. There’s no plugin install. Both connection types are built in.
OGC API Features (vector data):
1. Layer > Add Layer > Add WFS / OGC API Features Layer...2. New connection - Name: GeoLens - URL: https://geolens.example.com/api/ - Version: OGC API - Features3. Private instance: Authentication > Configurations > +, method "API Header", header X-Api-Key = <your-api-key>; select it on the connection.4. Connect -> pick a collection -> Add.The collection list shows every dataset visible to your API key, and
each collection adds as a vector layer. The API Header configuration
keeps the key in QGIS’s encrypted auth database instead of the project
file; do not put ?api_key= in the connection URL. Keep the trailing
slash on /api/. Attribute filter expressions push down to the server as
CQL2 on any current QGIS, and QGIS 3.44 or later adds explicit spatial
predicates; panning itself uses the Core bbox parameter. See
Use GeoLens from QGIS. The longer walkthrough, with
screenshots and a ready-made project, is
qgis/README.md.
OGC API Records (catalog search):
1. Web > MetaSearch > MetaSearch (built-in plugin, no install)2. Services tab > New - Name: GeoLens - URL: https://geolens.example.com/api/ - Catalog Type: OGC API - Records3. Save -> Search tab > search by keyword, bbox, or CQL2.This gives QGIS users a “search GeoLens from inside QGIS” experience: useful for big catalogs where browsing the full list is impractical.
XYZ tiles (raster):
QGIS’s XYZ Tiles connection is raster-only. Use the .png template from
the collection’s tiles link:
1. Browser panel > XYZ Tiles > Right-click -> New Connection...2. Name: GeoLens - <dataset> URL: https://geolens.example.com/raster-tiles/{dataset_id}/tiles/{z}/{x}/{y}.png?v=<n>3. Private dataset: select the API Header authentication configuration.4. Click OK, then drag the connection onto the canvas.Vector tiles (MVT):
1. Layer > Add Layer > Add Vector Tile Layer...2. New > New Generic Connection... - Name: GeoLens - <dataset> - URL: https://geolens.example.com/api/tiles/{table_path}/{z}/{x}/{y}.pbf3. Private dataset: select the API Header authentication configuration, or append ?sig=<sig>&exp=<exp>&scope=<scope> from GET /api/tiles/token/{dataset_id}/.4. Click OK, then add the connection.{table_path} is data. plus the dataset’s table_name. A pasted tile
token expires within 16 minutes; treat it as a session credential
and use the authentication configuration for anything longer. See
Tile endpoints for how tokens are
minted and scoped.
GDAL / ogr2ogr
Section titled “GDAL / ogr2ogr”GDAL’s OAPIF driver speaks OGC API Features over HTTP. Useful for scripted exports, format conversion, or pulling subsets without using the UI:
# List collectionsogrinfo OAPIF:https://geolens.example.com/api/
# Download a collection to GeoPackageogr2ogr -f GPKG out.gpkg \ OAPIF:https://geolens.example.com/api/ \ <collection-id>
# With API key (header doesn't work in OAPIF, so use the query param)ogrinfo "OAPIF:https://geolens.example.com/api/?api_key=YOUR_KEY"
# Convert to Shapefile, restricting to bbox + attribute filterogr2ogr -f "ESRI Shapefile" out.shp \ "OAPIF:https://geolens.example.com/api/?api_key=YOUR_KEY" \ <collection-id> \ -spat -122.5 37.5 -122.0 38.0 \ -where "population > 10000"The OAPIF driver’s ?api_key= query-string auth is the recommended way
to authenticate ogr2ogr against a private GeoLens instance: the OAPIF
driver doesn’t currently let you set HTTP headers, so the
header-form API key won’t work here.
Python: pystac-client
Section titled “Python: pystac-client”For STAC catalogs, pystac-client is the canonical client:
from pystac_client import Client
client = Client.open("https://geolens.example.com/api/stac/")
# Find recent items in a collectionsearch = client.search( collections=["my-raster-collection"], bbox=[-122.5, 37.5, -122.0, 38.0], datetime="2024-01-01T00:00:00Z/2024-12-31T23:59:59Z",)
for item in search.items(): print(item.id, item.assets["raster_tiles"].href)For an authenticated instance, supply a Modifier to inject the API key
header into every request:
from pystac_client import Client
def add_api_key(request): request.headers["X-Api-Key"] = "<your-api-key>" return request
client = Client.open( "https://geolens.example.com/api/stac/", request_modifier=add_api_key,)Python: owslib
Section titled “Python: owslib”For OGC API Features and OGC API Records (vector + catalog), owslib is the generic OGC client:
from owslib.ogcapi.features import Features
features = Features("https://geolens.example.com/api/")
# List collectionsfor c in features.feature_collections(): print(c["id"], c["title"])
# Pull a collection's itemsitems = features.collection_items( "my-vector-collection", bbox=[-122.5, 37.5, -122.0, 38.0],)Since 1.16, per-dataset feature collections accept the same CQL2
filter= parameter as the catalog (Records) collection, so a client can
filter a dataset’s features server-side; see
Filtering with CQL2 for the
parameters and the per-collection /queryables document. For a filtered
file rather than a filtered feature stream, the export endpoint’s
?where= parameter described above still applies.
Python: raw requests
Section titled “Python: raw requests”For workflows where pystac-client and owslib are too heavy, GeoLens’s
endpoints are plain HTTP-and-JSON; requests is enough:
import requests
API = "https://geolens.example.com/api"KEY = "<your-api-key>"
# Fetch a dataset's features as GeoJSON (spatial subset via bbox)response = requests.get( f"{API}/collections/my-vector-collection/items", headers={"X-Api-Key": KEY}, params={"bbox": "-122.5,37.5,-122.0,38.0"},)features = response.json()["features"]
# Attribute filtering uses the export endpoint's ?where= parameterexport = requests.get( f"{API}/datasets/<dataset-id>/export", headers={"X-Api-Key": KEY}, params={"format": "geojson", "where": "population > 10000"},)For authentication options (JWT, header-form API key, query-string API key, OAuth-issued JWT), see API Authentication.
DuckDB
Section titled “DuckDB”DuckDB’s spatial and httpfs extensions read a GeoParquet export
straight off the URL. Once the server has built the export (the first
request builds it), a query that names only a few columns fetches only the
byte ranges it needs, and DESCRIBE reads just the footer. The artifact
stays sliceable for about a minute after the build, so a query more than a
minute after that build pays for another conversion, whether or not the session
was idle in between:
INSTALL spatial; LOAD spatial;INSTALL httpfs; LOAD httpfs;
DESCRIBE SELECT *FROM read_parquet('https://geolens.example.com/api/datasets/<dataset-id>/export?format=parquet');The geometry column arrives already typed GEOMETRY('OGC:CRS84'), so no
ST_GeomFromWKB is needed. For a private dataset, one HTTP secret covers
read_parquet and ST_Read:
CREATE SECRET geolens ( TYPE http, SCOPE 'https://geolens.example.com', EXTRA_HTTP_HEADERS MAP{'X-Api-Key': '<your-api-key>'});One trap: name the source CRS OGC:CRS84, not EPSG:4326, when you
reproject. Both label lon/lat data, but they disagree about axis order,
and ST_Transform(geom, 'EPSG:4326', ...) on lon/lat input returns a
well-formed answer for the wrong hemisphere. A complete, CI-checked
script is
duckdb/query.py
in the examples repo.
MapLibre GL JS
Section titled “MapLibre GL JS”A map saved in the map builder is a machine
client target too: GET /api/maps/{map_id}/style.json returns the whole
composition as a MapLibre style document (spec version 8) — sources, layers,
sprite and glyph references, and the saved viewport — so your own web app can
draw it. No basemap source is included; the author’s basemap choice is
recorded under metadata.geolens only, so mount your own beneath the GeoLens
layers. For the same document from the UI side, see
Style JSON export and import.
The route is read-gated like the map itself: a public map exports with no
credential, a private one takes X-Api-Key (or a bearer token), a map you
can’t read answers 404, and a credential that doesn’t resolve answers 401.
Layers whose dataset you can’t see are dropped from the document rather than
failing the request.
The sprite and tile URLs inside are relative, so make them absolute before
handing the style to MapLibre — it rejects a relative sprite outright. Vector
tiles and the sprite resolve against the API base; raster tiles are served at
the site root:
const SITE = 'https://geolens.example.com';const abs = (u) => u.startsWith('/raster-tiles/') ? SITE + u : `${SITE}/api${u}`;
const style = await ( await fetch(`${SITE}/api/maps/${mapId}/style.json`)).json();style.sprite = style.sprite.map((s) => ({ ...s, url: abs(s.url) }));for (const src of Object.values(style.sources)) { if (src.tiles) src.tiles = src.tiles.map(abs);}new maplibregl.Map({ container: 'map', style });Fetch the style on each page load instead of pinning a copy: its vector tile
URLs carry the same short-lived signature as the tile templates above. A page
served from another origin also needs that origin in the instance’s
CORS_ALLOWED_ORIGINS — the tile routes answer any origin, this one does not.
Tips and gotchas
Section titled “Tips and gotchas”- Shapefile column names. Shapefile truncates column names to 10 characters. GeoLens preserves the original column names in the catalog, but on Shapefile export, names are truncated and possibly renamed to resolve collisions. If you need full names, use GeoPackage or GeoJSON.
- CSV and geometry. CSV exports write geometry as WKT in a leading
column named
geom. - Reprojection accuracy. Reprojection in
?target_crs=uses PROJ-default transformations. For projects that require a specific datum-shift grid, reproject locally with GDAL after export instead of relying on?target_crs=. - Tile URL caveats. Tile URLs for private datasets are HMAC-signed
per dataset and short-lived. Treat them as session credentials: mint a
new one with
GET /api/tiles/token/{dataset_id}/when an old one expires. Tile tokens are not API keys; they can’t be used to fetch features or metadata.
See also
Section titled “See also”- Dataset detail: exports originate from any dataset’s detail page
- OGC API & Standards Endpoints: full machine-client API reference (per-endpoint schemas, conformance URIs, every example reproduced)
- API Authentication: API keys and JWTs for ogr2ogr, pystac-client, and any non-UI client
- Examples gallery and repo: runnable DuckDB, GeoPandas, QGIS, and browser clients, verified against the public demo in CI