Operational scripts only (no service code, no tests). - backend/scripts/mvum_backfill.py: cleaned, repo-ready version of the P2/P3 NFS-centerline backfill. argparse --db-path (default /mnt/nav/navi.db) / --nfs-gdb (default the EDW Trans_Trail_NFS_Publish.gdb) / --dry-run; no /tmp, no test-DB paths, no prod-refusing guard (intended for prod, gated by --dry-run + the README snapshot step). Extracts NFS geometry from the .gdb via ogr2ogr into a cache beside it (no GDAL Python bindings here), matches null mvum_trails rows by TRAIL_NO+TRAIL_NAME within the forest ADMIN_ORG prefix, merges segments, writes WKB. Idempotent (UPDATE WHERE shape IS NULL). Prints rows_attempted/rows_updated/rows_skipped_no_match/rows_skipped_nfs_null_geom. - backend/scripts/README-mvum-ingest.md: source URLs, initial ingest (ogr2ogr shapes), refresh, repair (the NULL-shape gap), snapshot-first + stop-service guardrails, and the produce-vs-consume pointer to mvum.py / mvum_annotate.py / mvum_exclude.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.1 KiB
MVUM ingest pipeline
How the mvum_roads / mvum_trails tables in navi.db are produced from USFS data.
The navi-offroute service code only consumes these tables — see
backend/services/navi_offroute/mvum.py (access logic + MVUMSpatialIndex),
mvum_annotate.py (Layer 1 per-edge annotation), and mvum_exclude.py (Layer 2c
exclude_polygons). The scripts here are the producers.
Source datasets (USFS EDW)
Published as File Geodatabases (national rollups):
- Roads: https://data.fs.usda.gov/geodata/edw/edw_resources/fc/Trans_MVUM_Road.gdb.zip
- Trails (MVUM legal access): https://data.fs.usda.gov/geodata/edw/edw_resources/fc/Trans_MVUM_Trail.gdb.zip
- Trail centerlines (geometry donor for the backfill): https://data.fs.usda.gov/geodata/edw/edw_resources/fc/Trans_Trail_NFS_Publish.gdb.zip
Keep the unzipped .gdbs under /mnt/nav/sources/mvum/. Both MVUM and NFS data are
NAD83 / EPSG:4269, and ogr2ogr is loaded with SPATIALITE=NO so geometry is stored as
plain WKB blobs that shapely and MVUMSpatialIndex read directly.
Always, before any write to
navi.db: snapshot to a named, dated, persistent file (never/tmp) and stop the service:sudo systemctl stop navi-offroute sqlite3 /mnt/nav/navi.db ".backup /mnt/nav/navi-pre-<change>-<YYYY-MM-DD>.db" # ... run the ingest/backfill ... sudo systemctl start navi-offrouteRollback = restore the snapshot over
navi.dband restart.
1. Initial ingest (one-time, building navi.db from scratch)
Load each MVUM GDB layer into its table. ogr2ogr shape (run from /mnt/nav/sources/mvum/):
# roads -> mvum_roads
ogr2ogr -f SQLite /mnt/nav/navi.db Trans_MVUM_Road.gdb Trans_MVUM_Road \
-dsco SPATIALITE=NO -nln mvum_roads -update -overwrite
# trails -> mvum_trails
ogr2ogr -f SQLite /mnt/nav/navi.db Trans_MVUM_Trail.gdb Trans_MVUM_Trail \
-dsco SPATIALITE=NO -nln mvum_trails -update -overwrite
-update appends to the existing navi.db (it holds other navi tables); -overwrite
replaces just the named table. This yields ogc_fid, a shape WKB blob, and the MVUM
attribute columns (id, name, forestname, symbol, atv, highclearancevehicle,
e_bike_class1, the *_datesopen fields, etc.). Then run the trail backfill (step 3) to
fill the geometry-sparse trail rows.
2. Refresh (USFS published new MVUM data)
cd /mnt/nav/sources/mvum
for f in Trans_MVUM_Road Trans_MVUM_Trail Trans_Trail_NFS_Publish; do
curl -L --fail -o "$f.gdb.zip" \
"https://data.fs.usda.gov/geodata/edw/edw_resources/fc/$f.gdb.zip"
rm -rf "$f.gdb" && unzip -o "$f.gdb.zip"
done
# (snapshot + stop service as above)
ogr2ogr -f SQLite /mnt/nav/navi.db Trans_MVUM_Road.gdb Trans_MVUM_Road -dsco SPATIALITE=NO -nln mvum_roads -update -overwrite
ogr2ogr -f SQLite /mnt/nav/navi.db Trans_MVUM_Trail.gdb Trans_MVUM_Trail -dsco SPATIALITE=NO -nln mvum_trails -update -overwrite
python backend/scripts/mvum_backfill.py --db-path /mnt/nav/navi.db # then restart service
3. Repair (mvum_trails has NULL shapes — the gap fixed in P1–P3)
When only the trail geometry is missing (no full re-ingest needed), just run the NFS backfill. Preview first, then apply:
# preview (no writes)
python backend/scripts/mvum_backfill.py --db-path /mnt/nav/navi.db --dry-run
# apply (after snapshot + stopping the service)
python backend/scripts/mvum_backfill.py --db-path /mnt/nav/navi.db
mvum_backfill.py extracts the NFS centerlines from --nfs-gdb (default
/mnt/nav/sources/mvum/Trans_Trail_NFS_Publish.gdb) into a _nfs_centerline_extract.sqlite
cache beside the .gdb, matches each null mvum_trails row by TRAIL_NO+TRAIL_NAME
within the forest's ADMIN_ORG prefix, merges the matched segments, and writes WKB.
It is idempotent (UPDATE ... WHERE shape IS NULL) and prints counters
(rows_attempted, rows_updated, rows_skipped_no_match, rows_skipped_nfs_null_geom).
Typical full-population recovery is ~97%; the residual is rows with no join key or trails
absent from NFS / null in NFS too.
After any write: sudo systemctl start navi-offroute (each worker rebuilds
MVUMSpatialIndex at boot, ~8 s).