Skip to content
Development
Command

/portaljs-add-geo

Auto-ingest a geospatial file (GeoJSON, Shapefile, GeoPackage, KML/KMZ, FlatGeobuf, CSV-with-geometry) into a PortalJS portal — on your own machine, no server. Normalizes CRS to EPSG:4326, derives a PMTiles render tier + a GeoParquet query tier, pushes all three to R2 via Git

From plugin
portaljs
2.3k25 skills25 commands
Install
> /plugin marketplace add datopian/portaljs
> /plugin install portaljs@datopian-portaljs

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/portaljs-add-geo

Context preview

What this command does when you run it.

Auto-ingest a geospatial file (GeoJSON, Shapefile, GeoPackage, KML/KMZ, FlatGeobuf, CSV-with-geometry) into a PortalJS portal — on your own machine, no server. Normalizes CRS to EPSG:4326, derives a PMTiles render tier + a GeoParquet query tier, pushes all three to R2 via Git

Command definition

portaljs-add-geo.md
description: Auto-ingest a geospatial file (GeoJSON, Shapefile, GeoPackage, KML/KMZ, FlatGeobuf, CSV-with-geometry) into a PortalJS portal — on your own machine, no server. Normalizes CRS to EPSG:4326, derives a PMTiles render tier + a GeoParquet query tier, pushes all three to R2 via Git LFS, and emits one dual-tier datasets.json entry the showcase renders with <MapPreview> + <GeoQuery>.
allowed-tools: Read, Write, Edit, Bash, WebFetch

/portaljs-add-geo

Turn one geospatial upload into a **dual-tier** PortalJS dataset entirely on the user's machine — **no server-side container compute**, keeping the zero-backend bet end-to-end. From a single source file this produces two derivatives plus the preserved original:

  • **PMTiles** — the **render tier**. A single vector-tile archive `<MapPreview>` renders

with MapLibre GL over HTTP range requests: any dataset size pans/zooms with no tile server.

  • **GeoParquet** — the **query tier**. A GeoParquet 1.1 file (covering `bbox` column,

Hilbert-sorted) `<GeoQuery>` runs spatial SQL over in place via DuckDB-Wasm.

  • **Original** — the untouched upload in its native CRS, kept downloadable as the source

artifact.

All three land on Cloudflare R2 (via Git LFS → Giftless), and the skill appends **one** `datasets.json` entry whose `resources[]` are exactly the shape the showcase auto-renders (PR #1647 / the `@reference/world-boundaries` demo): a `pmtiles` resource → `<MapPreview>`, a `geoparquet` resource → `<GeoQuery>`, and the original as a download. No page edits — the dynamic showcase route `pages/[owner]/[slug].tsx` renders each resource by its `format`.

This automates the manual `tippecanoe` / `duckdb` recipes documented in the template README (the interim ingest path). Use it whenever the source is a **vector geo format**; for plain tabular data use `/portaljs-add-dataset`, and for a Leaflet view over small GeoJSON use `/portaljs-add-map`.

Runtime — native shell-out, detect-or-instruct

This runs **native CLIs on the user's machine** (locked direction, 2026-07-08 — overrides po-6sr §7's server-compute sketch). No container, no cloud job. tippecanoe has no mature WASM build, so the PMTiles tier fundamentally needs the native binary; skills already shell out to git-lfs and npm, so native geo deps fit the model. Three tools:

| Tool | Role | Why this tool | |------|------|---------------| | **GDAL** (`ogr2ogr`, `ogrinfo`) | Read **every** vector input; reproject → EPSG:4326; emit the normalized GeoJSON intermediate | Widest driver set (SHP, GPKG, KML/KMZ, FGB, CSV-geom) | | **tippecanoe** | normalized GeoJSON → PMTiles (render tier) | The de-facto tiler; no WASM equivalent | | **duckdb** (+ `spatial`) | normalized GeoJSON → GeoParquet 1.1 with covering `bbox` + Hilbert sort (query tier) | The **proven** writer — PR #1647 verified `<GeoQuery>` against duckdb output; ogr2ogr's Parquet driver won't cleanly emit the covering-bbox column + Hilbert ordering the bbox-first query needs to prune |

Required input — ask, don't error

  • **Source** — a local geo file path or a public URL. Supported: `.geojson`/`.json`

(GeoJSON), `.zip` (zipped Shapefile), `.gpkg`, `.kml`/`.kmz`, `.fgb`, `.csv` with a geometry/lat-lon column. Raster (`.tif`/`.tiff`) is **out of scope** (COG is a later phase).

  • **Portal directory** — path to the portal project (defaults to current directory).
  • **Namespace** — the dataset's namespace value (subject for `'theme'` portals, publisher

for `'owner'` portals).

**If the source is missing, ask for it — never dead-end.** The user can say "use defaults".

Steps

1. Gather input from `$ARGUMENTS` (interview if thin)

Extract:

  • `SOURCE` — geo file path or URL
  • `PORTAL_DIR` — portal directory (default: `.`)
  • `DATASET_NAME` — human name (default: derived from filename)
  • `DATASET_SLUG` — URL slug (default: lowercase hyphenated filename without extension)
  • `DESCRIPTION` — optional one-line description
  • `NAMESPACE` — namespace value (default: read the first existing entry's `namespace`

from `datasets.json`, else `reference`)

If `SOURCE` is missing, ask (one focused prompt) and wait:

To add a geospatial dataset I need:
1. Source: a local geo file path or public URL (required) —
   GeoJSON, zipped Shapefile (.zip), GeoPackage (.gpkg), KML/KMZ, FlatGeobuf (.fgb),
   or a CSV with a geometry/lat-lon column.
2. Portal directory (Enter for current directory)
3. Dataset name (Enter to use the filename)
4. Namespace value — the group this dataset belongs to
   (subject if the portal is "theme" mode, publisher if "owner" mode; Enter to reuse the catalog's existing namespace)

Read `NAMESPACE_TYPE` from `PORTAL_DIR/lib/datasets.ts` so you can phrase the namespace question correctly ("subject" vs "publisher").

2. Check the native tools (detect-or-instruct — hard gate)

Do this **before touching data**. Missing tools stop the run with a one-line install — never silently skip a tier.

MISSING=""
for t in ogr2ogr tippecanoe duckdb; do
  command -v "$t" >/dev/null 2>&1 || MISSING="$MISSING $t"
done

If `MISSING` is non-empty, print the install for the user's OS and STOP:

ERROR: [add-geo] MISSING_TOOLS Missing:<MISSING>. Install then re-run:
  macOS:         brew install gdal tippecanoe duckdb
  Debian/Ubuntu: sudo apt-get install -y gdal-bin duckdb    # tippecanoe: apt where available,
                 else build from source — https://github.com/felt/tippecanoe
  Windows:       not supported natively — use WSL (Ubuntu) and follow the Debian steps.

Detect Windows-native (`$OSTYPE` = `msys`/`win32`, or `uname` shows MINGW/MSYS) and print the WSL note prominently — this is a documented first-cut limitation, not a bug.

Verify the duckdb `spatial` extension can load (it auto-installs on first use, needs network once): `duckdb -c "INSTALL spatial; LOAD spatial; SELECT 1;"` — if this fails, report it as `MISSING_TOOLS` (duckdb spatial) with the same stop behavior.

3. Validate the po

Read more
Ships withportaljs

🌀 AI-native framework for building data portals. Scaffold a full portal from a brief and load datasets in minutes with agentic skills — any backend (CKAN, GitHub, Frictionless).

Get the whole plugin