{{ theme.skipToContentLabel || 'Skip to content' }}

Static Map Georeferencing & Deployment Guide

End-to-end SOP for calibrating a static map image (e.g. supplier-provided mine site map) and deploying it as a tile overlay on the Dashboard heatmap.

Pipeline Overview

Raw Image (PNG/JPG/TIF)
  → QGIS Georeferencer (calibrate with GCPs)
  → GeoTIFF (.tif with embedded coordinates)
  → gdal2tiles.py (generate TMS tile pyramid)
  → convert-tiles-to-pmtiles.sh (PNG → WebP → MBTiles → PMTiles)
  → Cloudflare R2 upload
  → overlayConfig.ts (register in Dashboard)

Prerequisites

ToolInstallPurpose
QGISqgis.org (latest stable)Georeferencing
GDALBundled with QGIS, or brew install gdalTile generation (gdal2tiles.py)
cwebpbrew install webpPNG → WebP compression
mb-utilpip install mbutilWebP tiles → MBTiles
pmtilesbrew install pmtilesMBTiles → PMTiles
wranglernpm install -g wranglerCloudflare R2 upload

QGIS plugin: QuickMapServices (for satellite reference basemap).

Step 1: QGIS Project Setup

  1. Open QGIS
  2. Set project CRS to EPSG:3857 (bottom-right corner → click CRS → search 3857 → OK)

Load a Reference Basemap

  1. PluginsManage and Install Plugins → search QuickMapServices → Install
  2. WebQuickMapServicesSettingsMore servicesGet contributed pack
  3. WebQuickMapServicesGoogleGoogle Satellite

The satellite imagery is now visible in the main map canvas.

Step 2: Georeferencing

Open Georeferencer

RasterGeoreferencer… (opens a separate window)

Load the Source Image

In the Georeferencer window: Open Raster → select your image (PNG/JPG/TIF).

If prompted for CRS: select EPSG:3857 or Unknown — doesn't matter, the GCPs determine the actual position.

Add Ground Control Points (GCPs)

This is the most critical step. Each GCP maps a pixel on your image to a real-world coordinate.

Per-point workflow:

  1. Click Add Point in the Georeferencer toolbar
  2. Click a clear feature on your image (road intersection, building corner, pit edge)
  3. In the popup, select From Map Canvas
  4. Switch to the main QGIS window → click the same feature on the satellite basemap
  5. Confirm — the point appears in the GCP table

Point selection rules:

  • Minimum 6 points, recommended 10–15
  • Distribute evenly: four corners + center + key inflection points
  • Use stable features: road intersections, building corners, bridge ends, pit edges
  • Avoid: tree shadows, water edges, temporary stockpile boundaries

Strategy: Start with 4 corner points to anchor the overall extent, then add interior points to reduce local distortion.

Save GCP Points

Before running the transform — save your points so you can reload and adjust later:

Georeferencer toolbar → Save GCP Points as… → e.g. site-name_v1.points

To reload: Load GCP Points… after opening the same source image.

Transformation Settings

Georeferencer → gear icon (or SettingsTransformation Settings):

SettingRecommended ValueNotes
Transformation typePolynomial 1 (affine)Start here. Handles translation, rotation, non-uniform scaling
Target SRSEPSG:3857Matches project CRS
Resampling methodNearest neighbourSharper edges for line maps. Use Bilinear for photo imagery
Output rastersite_georef.tifGeoTIFF output path
Load in QGIS when doneAuto-loads result for verification

If edges don't align after running:

  • Try Polynomial 2 (needs more points, handles moderate distortion)
  • Or Thin Plate Spline (rubber-sheet, handles severe distortion but can warp locally)
  • Never upgrade transform type without enough well-distributed points

Check Residuals

The GCP table shows a Residual column for each point.

  • If one point has a significantly higher residual than others → you likely clicked the wrong spot
  • Right-click → Delete, then re-add at a more precise location
  • Goal: uniform residuals across all points. A few bad points are worse than fewer good points.

Run Georeferencing

Click Start Georeferencing in the toolbar.

Output: a GeoTIFF with embedded coordinate information.

Step 3: Verify Alignment

Back in the main QGIS window:

  1. The output GeoTIFF should appear as a layer (if you checked "Load in QGIS when done")
  2. Right-click the layer → PropertiesTransparency → set global opacity to 40–60%
  3. Zoom in and check road intersections, boundaries, buildings against the satellite basemap

Troubleshooting:

SymptomFix
Center accurate, edges driftAdd more edge GCPs, or upgrade to Polynomial 2 / TPS
Entire image is rotated/offsetA few GCPs are wrong — check residuals, delete outliers
Local warping/distortionTPS + unstable points — switch back to Polynomial 1 or remove bad points

Iterate: adjust points → re-run → verify until satisfied.

Step 4: Generate TMS Tiles

Convert the GeoTIFF into a TMS tile directory using gdal2tiles.py:

bash
gdal2tiles.py -z 14-20 -w none -r bilinear --processes=4 site_georef.tif ./tiles
FlagPurpose
-z 14-20Zoom levels to generate (14 = overview, 20 = max detail)
-w noneSkip HTML viewer generation
-r bilinearResampling method (smooth interpolation)
--processes=4Parallel processing

Output: ./tiles/{z}/{x}/{y}.png in TMS scheme.

This can take a while for high-zoom large areas. Marandoo (zoom 14–20) produced ~138K tiles.

Step 5: Convert to PMTiles

bash
./scripts/convert-tiles-to-pmtiles.sh ./tiles site-name

Pipeline: PNG → WebP (quality 80) → MBTiles → PMTiles

Output: build/pmtiles/site-name.pmtiles

The script is idempotent — delete intermediate outputs (build/pmtiles/site-name-webp/, .mbtiles) to reconvert.

Step 6: Upload to Cloudflare R2

bash
npx wrangler r2 object put map-tiles/site-name.pmtiles \
  --file=build/pmtiles/site-name.pmtiles \
  --remote

No Worker changes needed — the Worker automatically maps {site} to {site}.pmtiles in the R2 bucket.

Step 7: Register in Dashboard

Add an entry in src/features/heatmap/overlayConfig.ts:

typescript
{
  type: "tile",
  id: "site-name-overlay",
  name: "Site Name",
  mineSiteId: "uuid-from-cfg_mine_sites",
  tileBasePath: "https://map-tiles.fudong.workers.dev/site-name",
  minZoom: 14,
  maxZoom: 20,
  bounds: { north: ..., south: ..., east: ..., west: ... },
}

Get bounds from tilemapresource.xml in the tiles output directory, or from the GeoTIFF extent in QGIS (right-click layer → Properties → Information → Extent).

Deploy the Dashboard with the updated config.

Quick Reference

Full command sequence after QGIS georeferencing is done:

bash
# Generate tiles
gdal2tiles.py -z 14-20 -w none -r bilinear --processes=4 site_georef.tif ./tiles

# Convert to PMTiles
./scripts/convert-tiles-to-pmtiles.sh ./tiles site-name

# Upload to R2
npx wrangler r2 object put map-tiles/site-name.pmtiles \
  --file=build/pmtiles/site-name.pmtiles \
  --remote

Then update overlayConfig.ts and deploy.