Short answer

A data delivery portal for a consulting practice is the layer that turns a one-off study into a referenceable, queryable asset the client can trust six months later. The durable pattern is a PostGIS backend feeding three delivery surfaces from the same source of truth: signed-URL downloads (GeoPackage + PDF), a static web map (MapLibre over PMTiles), and an optional live API (OGC API Features). Everything carries explicit CRS, version, and provenance so the deliverable can be cited and audited.

This article describes the architecture, the exact tools and commands, and the metadata discipline that keeps a portal cheap to run and defensible under scrutiny.

What a delivery portal must actually do

Consultants are not building Google Earth. The job is narrower and the constraints are sharper:

  • Confirm, don't explore. The client opens the portal to verify a result you already delivered in a report, not to do open-ended analysis. The web map exists so a non-GIS stakeholder can see the flood extent, the contamination plume, or the corridor and recognise it.
  • Hand off clean files. Most clients ultimately want the data inside their own GIS. The portal must serve formats they can open: GeoPackage for desktop GIS, GeoJSON for web developers, CSV for the office, PDF for the record.
  • Survive an audit. Two years on, an insurer, regulator, or opposing expert may ask which dataset version produced figure 7. If you cannot answer with a version tag, a checksum, and the source extract, the deliverable is weak.

Design backwards from those three needs rather than forward from a platform feature list.

Reference architecture

A practical stack separates five concerns. Keeping them distinct is what lets every update improve the asset instead of forcing a rebuild.

  1. Source ingestion. Raw client data, public datasets, field GPS, lab results. Stored read-only, never edited in place.
  2. Normalized storage. A PostGIS database (or, for small jobs, a single GeoPackage) holding cleaned, reprojected, schema-stable layers. This is the source of truth.
  3. Analysis outputs. Derived layers: buffers, interpolations, screening scores. Generated by scripts you can re-run, not hand edits.
  4. Publishing formats. The artifacts clients receive — GeoPackage, PMTiles, GeoJSON, styled PDF.
  5. Interface. A static site (MapLibre GL JS) plus, when needed, a thin API.

The key discipline: layers 4 and 5 are generated from layers 2 and 3. Nothing in the portal is authored by hand.

Storage: PostGIS as the source of truth

Load everything into PostGIS and reproject on ingest so every layer shares one CRS. Pick a metric projected CRS for analysis (a UTM zone or a national grid such as EPSG:2154 for France, EPSG:27700 for Britain) and reproject to EPSG:4326 only at publish time for web layers.

-- store analysis layers in a metric CRS for correct length/area
ALTER TABLE faults
  ALTER COLUMN geom TYPE geometry(LineString, 2154)
  USING ST_Transform(ST_SetSRID(geom, 4326), 2154);

CREATE INDEX faults_geom_idx ON faults USING GIST (geom);

A spatial GiST index is not optional once a layer exceeds a few thousand features; bounding-box queries from the map go from seconds to milliseconds.

Downloads: signed URLs over object storage

Export each published layer with ogr2ogr and push it to object storage. Serve it through time-limited signed URLs so links cannot be scraped or shared indefinitely.

# export a clean, versioned GeoPackage from PostGIS
ogr2ogr -f GPKG corridor_v2.1.0.gpkg \
  PG:"host=db dbname=project user=reader" \
  -sql "SELECT id, name, status, geom FROM corridor_v2_1" \
  -nln corridor -a_srs EPSG:2154

Generate the signed URL at request time (AWS S3 presign, Cloudflare R2, or GCS signurl) with an expiry of a few hours. The portal frontend never embeds the bucket path directly.

Web map: PMTiles, no tile server

For visual layers, the cheapest robust option is a single PMTiles archive on object storage behind a CDN. Build it with tippecanoe:

tippecanoe -o corridor.pmtiles \
  -zg --drop-densest-as-needed \
  -l corridor corridor_v2.1.0.geojson

MapLibre GL JS reads PMTiles via HTTP range requests, pulling only the tiles in view. There is no tile server to provision, patch, or pay for — a portal serving a handful of clients can run for cents per month. Reproject the GeoJSON to EPSG:4326 first, because web tiles assume Web Mercator (EPSG:3857) tiling over WGS84 coordinates.

Live API: OGC API Features when querying matters

Add an API only when clients need to query rather than download — pull features by bounding box, filter by attribute, or sync into their own systems. OGC API Features is the modern, REST/JSON standard for this; pg_featureserv or QGIS Server can expose a PostGIS table as a conformant endpoint with minimal config. The benefit over a raw download is that the client fetches a 200-feature subset for their area of interest instead of a 2 GB national dataset.

Worked example: publishing version 2.1.0 of a corridor study

  1. Cleaned alignment lands in PostGIS table corridor_v2_1 (EPSG:2154), GiST-indexed.
  2. ogr2ogr exports corridor_v2.1.0.gpkg (metric CRS) for desktop clients and corridor_v2.1.0.geojson (EPSG:4326) for the web build.
  3. tippecanoe builds corridor.pmtiles; both files upload to s3://client-acme/corridor/2.1.0/.
  4. Compute and record checksums: sha256sum corridor_v2.1.0.gpkg.
  5. Update metadata.json (see below) and append a changelog entry.
  6. The static MapLibre site points its source at the new PMTiles URL; the download button calls a function that returns a signed URL for the GeoPackage.

Because 2.1.0/ is a new immutable prefix, version 2.0.0 still resolves for anyone who cited it.

Metadata and versioning discipline

Versioning is the feature that makes a portal trustworthy. Adopt three rules:

  • Semantic versions, immutable releases. MAJOR.MINOR.PATCH. Bump MAJOR when the schema or methodology changes, MINOR for new data, PATCH for corrections. Never overwrite a released file path.
  • A sidecar metadata record per layer. A small JSON or ISO 19115-style record stating source, extraction date, CRS (as an EPSG code), schema, licence, and the processing parameters used.
  • Checksums and a changelog. A SHA-256 per published artifact and a human-readable changelog answer "what changed and can I prove this is the file you sent."
{
  "dataset": "corridor",
  "version": "2.1.0",
  "crs": "EPSG:2154",
  "source": "ACME survey 2026-05; IGN BD TOPO 2026",
  "extracted": "2026-06-10",
  "licence": "Client-confidential, no redistribution",
  "sha256": "9f2c…",
  "changelog": "Realigned segment 4 after field correction"
}

Common pitfalls and why they happen

  • Calling a folder of files a portal. Without version tags and metadata the client cannot tell a draft from a final, and you cannot reconstruct what you sent. The fix is the metadata record, not more files.
  • Running a tile server for three users. Tegola/GeoServer setups carry patching and uptime cost that a static PMTiles archive avoids entirely. Reach for a live server only when content changes faster than you can re-tile.
  • Reprojecting at the wrong moment. Storing web layers in EPSG:4326 and then measuring length on them gives wrong distances because degrees are not metres. Measure in the metric CRS; serve in 4326.
  • Ignoring licence and provenance. Mixing a client's confidential survey with a redistribution-restricted public layer in one open download creates real liability. Track licence per layer in metadata.
  • Mutable "latest" links. A single overwritten corridor_latest.gpkg makes every past citation ambiguous. Keep immutable versioned paths and let "latest" be a pointer, never the only copy.

Validation before you publish

  • Open the exported GeoPackage in a clean QGIS session and confirm CRS, attribute names, encoding, and feature count match the database.
  • Load the PMTiles in MapLibre and check that geometry aligns with a known basemap at zoom levels the client will use.
  • Verify the signed-URL flow downloads the correct version and expires as expected.
  • Diff the new metadata record against the previous version so the changelog is accurate.

Bathyl perspective

We treat a delivery portal as the long-lived form of a study, not a marketing wrapper around a PDF. The same PostGIS source feeds the download, the map, and the API, so there is one version of the truth and one place to audit it. When a client comes back a year later asking which dataset produced a figure, a version tag and a checksum answer the question in seconds.

Related reading

Sources