Dataset credit is mandatory. Before acquiring or publishing data, read the authoritative data-source and citation register. Every run must retain source-specific identifiers, licences, URIs, checksums, scientific conventions, and transformation lineage; every published result must visibly credit each source that actually contributed cells or features.
This guide builds a deterministic Gulf of Naples research/recreational chart from freely
available geospatial sources. It creates the complete acquisition, normalization,
sequential-reference, cartographic, MBTiles, Signal K, and provenance chain under a
user-selected DATA_ROOT. The commands below default to the repository-local data/
directory. Raw downloads are immutable; every transformation writes a new artifact.
The result is not an official ENC, an ECDIS product, or suitable for safety of navigation. Open bathymetric compilations have heterogeneous survey dates, horizontal accuracy, vertical datums, and measurement density. A visually dense chart cannot correct those limitations. In particular, JammeGaia22 provides excellent multibeam coverage from approximately 50 m depth downward but does not cover the shallowest 0–50 m nearshore zone. The default workflow therefore stops at zoom 13. Zoom 14 or greater is permitted only when a real, freely reusable raster of at most 40 m native resolution contains a non-trivial set of measurements shallower than 50 m.
All commands assume:
- the repository is checked out locally;
- Python 3.11 or newer is available;
- the filesystem selected for
DATA_ROOThas at least 40 GB free space; curl,unzip,jq,sqlite3, and GDAL command-line tools are installed;- outbound HTTPS access is available.
The workflow was designed for Linux or macOS. On macOS with Homebrew, install the system utilities with:
brew install python@3.12 gdal jq sqliteOn Debian or Ubuntu:
sudo apt-get update
sudo apt-get install -y python3 python3-venv gdal-bin jq sqlite3 unzip curlThe build uses actual finite measurements at each location before considering nominal resolution. A finer raster's rectangular extent is never interpreted as scientific coverage when its sampled cell is nodata.
| Priority | Source | Role | Principal limitation |
|---|---|---|---|
| 1 | JammeGaia22, DOI 10.60521/331667, CC BY 4.0 |
2/5/10/15/20/30/40 m multibeam depth bands | Starts near 50 m depth |
| 2 | Validated ISPRA PNRR MER shallow-water export | Optional z14+ nearshore source | Requires authorized raster, licence, datum, validation status, Gulf coverage, and ≤40 m cells |
| 3 | GMRT maximum-resolution regional grid, CC BY 4.0 | Regional gap filling | Resolution and contributing surveys vary spatially |
| 4 | EMODnet Bathymetry DTM, CC BY 4.0/source terms | Regional context | Approximately 1/8 arc-minute; not harbour resolution |
| 5 | GEBCO 2026, public domain with attribution | Broad offshore fallback | 15 arc-second information product; not a sounding survey |
| — | NOAA/NCEI GSHHG 2.3.7, LGPL | Topology-complete full-resolution L1 land polygons | June 2017 global compilation, not a contemporary HWL |
| — | S2Coast-2023, CC BY 4.0 | Sentinel-2-derived 2023 high-water-line profile | Nominal 10 m imagery; reported global RMSE 17.4 m |
| — | OpenStreetMap/OpenSeaMap, ODbL | Places, major roads, inland water, harbours, structures, and aids | Community-maintained context and seamarks |
Authoritative source information:
- MGDS JammeGaia22 DOI
- EMODnet web-service documentation
- GMRT GridServer
- GEBCO 2026 grid and terms
- NOAA/NCEI shoreline resources and GSHHG
- S2Coast-2023 description and licence
- S2Coast-2023 frozen Zenodo record
- ISPRA PNRR MER preliminary coastal-habitat and bathymetry announcement
- OpenStreetMap copyright and ODbL terms
Before a publication or long-lived deployment, recheck provider editions, licences, and service metadata. Rolling OSM/GMRT endpoints must be frozen by date and SHA-256 in the experiment record.
Run the following from the repository root. Replace the first path with the absolute path of the checkout; do not use a symbolic link if the experiment must be independently audited.
export PROJECT_ROOT=/absolute/path/to/mediterranean-chart-builder
export DATA_ROOT=${PROJECT_ROOT}/data
export RUN_ID=gulf-of-naples-2026-08-24
export RUN_ROOT="${DATA_ROOT}/runs/${RUN_ID}"
if command -v sha256sum >/dev/null 2>&1; then
:
elif command -v gsha256sum >/dev/null 2>&1; then
sha256sum() { gsha256sum "$@"; }
else
sha256sum() { shasum -a 256 "$@"; }
fi
mkdir -p "${DATA_ROOT}/venvs"
mkdir -p "${DATA_ROOT}/raw"
mkdir -p "${DATA_ROOT}/manifests"
mkdir -p "${DATA_ROOT}/discovery"
mkdir -p "${DATA_ROOT}/snapshots"
mkdir -p "${DATA_ROOT}/sources"
mkdir -p "${RUN_ROOT}"Keep this shell open: later sections depend on these exported variables. If the checkout
does not have 40 GB free, set DATA_ROOT to an absolute path on a suitable volume before
creating the directories. Before downloading anything, verify the resolved locations:
printf 'PROJECT_ROOT=%s\nDATA_ROOT=%s\nRUN_ROOT=%s\n' \
"${PROJECT_ROOT}" "${DATA_ROOT}" "${RUN_ROOT}"
test -d "${PROJECT_ROOT}/packages/medchart-preprocess"
test ! -e "${RUN_ROOT}/production-z13"
df -h "${DATA_ROOT}"Record the exact software revision before installing it:
git -C "${PROJECT_ROOT}" status --short
git -C "${PROJECT_ROOT}" rev-parse HEAD | tee "${RUN_ROOT}/software-git-commit.txt"
git -C "${PROJECT_ROOT}" diff --binary > "${RUN_ROOT}/software-working-tree.patch"For a normative run, git status --short should be empty. If it is not, retain the patch
and explain why the modified tree was used.
Separate environments prevent acquisition dependencies from silently changing the scientific preprocessing runtime.
python3 -m venv "${DATA_ROOT}/venvs/medchart-data"
"${DATA_ROOT}/venvs/medchart-data/bin/python" -m pip install --upgrade pip
"${DATA_ROOT}/venvs/medchart-data/bin/python" -m pip install \
-e "${PROJECT_ROOT}/packages/medchart-data[dev]"
python3 -m venv "${DATA_ROOT}/venvs/medchart-preprocess"
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" -m pip install --upgrade pip
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" -m pip install \
-e "${PROJECT_ROOT}/packages/medchart-preprocess[dev,osm,viz]"
"${DATA_ROOT}/venvs/medchart-data/bin/python" -m pip freeze \
> "${RUN_ROOT}/medchart-data-requirements.txt"
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" -m pip freeze \
> "${RUN_ROOT}/medchart-preprocess-requirements.txt"Verify the installed source tree before downloading data:
cd "${PROJECT_ROOT}/packages/medchart-data"
"${DATA_ROOT}/venvs/medchart-data/bin/python" -m pytest --no-cov -q
cd "${PROJECT_ROOT}/packages/medchart-preprocess"
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" -m pytest --no-cov -qThe production domain is EPSG:4326, west/south/east/north =
13.70,39.90,15.00,41.20, with a 0.05° acquisition halo.
cp "${PROJECT_ROOT}/packages/medchart-data/configs/regions/gulf-of-naples.yaml" \
"${RUN_ROOT}/region.yaml"
cp "${PROJECT_ROOT}/packages/medchart-preprocess/configs/regions/gulf-of-naples.yaml" \
"${RUN_ROOT}/preprocess-region.yaml"
sha256sum "${RUN_ROOT}/region.yaml" "${RUN_ROOT}/preprocess-region.yaml" \
> "${RUN_ROOT}/region.sha256"The compatibility function in Section 3 maps sha256sum to Homebrew's
gsha256sum or macOS shasum -a 256 when GNU sha256sum is unavailable. Keep the
same shell session for the remainder of the guide.
The acquisition package writes provider bytes under
${DATA_ROOT}/raw/<dataset-id>/ and a JSON manifest under ${DATA_ROOT}/manifests/.
It never rewrites an existing download.
"${DATA_ROOT}/venvs/medchart-data/bin/medchart-data" download \
--dataset "${PROJECT_ROOT}/packages/medchart-data/configs/datasets/gmrt-bathymetry-max.yaml" \
--region "${RUN_ROOT}/region.yaml" \
--output "${DATA_ROOT}"Expected payload:
/data/raw/gmrt-bathymetry-max/gmrt-gulf-of-naples-max.tif
The configured adapter uses the official WCS 1.0 GetCoverage request, EPSG:4326,
nearest-neighbour sampling, and the provider's 1/8 arc-minute grid spacing.
"${DATA_ROOT}/venvs/medchart-data/bin/medchart-data" download \
--dataset "${PROJECT_ROOT}/packages/medchart-data/configs/datasets/emodnet-bathymetry.yaml" \
--region "${RUN_ROOT}/region.yaml" \
--output "${DATA_ROOT}"Expected payload:
/data/raw/emodnet-bathymetry/emodnet-bathymetry-gulf-of-naples.tif
GEBCO's interactive application does not guarantee a stable scripted subset URL. For a reproducible experiment, download a Gulf of Naples NetCDF subset from the official GEBCO download application using the same bounding box, save the exact provider bytes, and record their digest:
mkdir -p "${DATA_ROOT}/raw/gebco-2026"
mv /path/from/browser/gebco_2026_n13.70_s39.90_w13.70_e15.00.nc \
"${DATA_ROOT}/raw/gebco-2026/gebco-2026-gulf-of-naples.nc"
sha256sum "${DATA_ROOT}/raw/gebco-2026/gebco-2026-gulf-of-naples.nc" \
> "${DATA_ROOT}/raw/gebco-2026/gebco-2026-gulf-of-naples.nc.sha256"Also download the matching GEBCO Type Identifier grid when available. The TID codes distinguish direct measurements, interpolated values, predicted values, and unknown sources. The current renderer does not consume TID, but retaining it is essential for a future source-quality audit.
Do not proceed when a raster has no CRS, has a nonsensical extent, or contains no finite water values.
gdalinfo "${DATA_ROOT}/raw/gmrt-bathymetry-max/gmrt-gulf-of-naples-max.tif"
gdalinfo "${DATA_ROOT}/raw/emodnet-bathymetry/emodnet-bathymetry-gulf-of-naples.tif"
gdalinfo "${DATA_ROOT}/raw/gebco-2026/gebco-2026-gulf-of-naples.nc"
sha256sum \
"${DATA_ROOT}/raw/gmrt-bathymetry-max/gmrt-gulf-of-naples-max.tif" \
"${DATA_ROOT}/raw/emodnet-bathymetry/emodnet-bathymetry-gulf-of-naples.tif" \
"${DATA_ROOT}/raw/gebco-2026/gebco-2026-gulf-of-naples.nc" \
> "${RUN_ROOT}/regional-fallbacks.sha256""${DATA_ROOT}/venvs/medchart-data/bin/python" \
"${PROJECT_ROOT}/packages/medchart-data/scripts/resolve-jamme-gaia22.py" \
--west 13.55 --south 39.95 --east 15.05 --north 41.20 \
--output "${DATA_ROOT}/discovery/jamme-gaia22.json"
jq -r '.records[] | [.data_uid, .filename, (.direct_url // "NO_DIRECT_URL")] | @tsv' \
"${DATA_ROOT}/discovery/jamme-gaia22.json"Inspect the output and select the seven published GeoTIFFs at 2, 5, 10, 15, 20, 30, and 40 m. Do not infer scientific resolution from file size or visual appearance.
An MGDS request may instead be fulfilled through a temporary get.marine-geo.org/outgoing/
directory. In that case, record the request UID, dataset UID, every data UID, the exact
temporary URL, receipt date, and provider filenames in the discovery JSON before the
directory expires. Download the .tif.gz files with resume enabled, preserve those
compressed provider bytes unchanged, verify them with gzip -t, and decompress to new
.tif artifacts. Record SHA-256 for both representations. Do not replace the DOI or
dataset UID with the temporary delivery URL in a long-lived citation.
If the discovery record contains direct_url, download that URL while preserving the
provider filename:
mkdir -p "${DATA_ROOT}/raw/jamme-gaia22"
jq -r '.records[] | select(.direct_url != null) | [.direct_url, .filename] | @tsv' \
"${DATA_ROOT}/discovery/jamme-gaia22.json" \
| while IFS=$'\t' read -r source_url source_name; do
curl --fail --location --continue-at - \
--output "${DATA_ROOT}/raw/jamme-gaia22/${source_name}" \
"${source_url}"
doneIf MGDS omits a direct URL, use the DOI landing page to download the same file identified
by data_uid, then place it unchanged in /data/raw/jamme-gaia22/. This is an explicit
manual acquisition step because guessing undocumented provider URLs would make the guide
less reproducible, not more.
Confirm that the seven files are GeoTIFFs and inspect their native transforms:
find "${DATA_ROOT}/raw/jamme-gaia22" -type f -name '*.tif' -print -exec gdalinfo {} \;Replace the seven filenames below with the exact names reported by discovery. The numeric resolution on the left is an explicit scientific assertion and is recorded in the lock.
"${DATA_ROOT}/venvs/medchart-data/bin/python" \
"${PROJECT_ROOT}/packages/medchart-data/scripts/freeze-jamme-gaia22.py" \
--discovery "${DATA_ROOT}/discovery/jamme-gaia22.json" \
--asset "2=${DATA_ROOT}/raw/jamme-gaia22/MGDS_Download/Jamme_Gaia22/JG22_50-120_2m.tif" \
--asset "5=${DATA_ROOT}/raw/jamme-gaia22/MGDS_Download/Jamme_Gaia22/JG22_100-200_5m.tif" \
--asset "10=${DATA_ROOT}/raw/jamme-gaia22/MGDS_Download/Jamme_Gaia22/JG22_180-500_10m.tif" \
--asset "15=${DATA_ROOT}/raw/jamme-gaia22/MGDS_Download/Jamme_Gaia22/JG22_480-700_15m.tif" \
--asset "20=${DATA_ROOT}/raw/jamme-gaia22/MGDS_Download/Jamme_Gaia22/JG22_680-1000_20m.tif" \
--asset "30=${DATA_ROOT}/raw/jamme-gaia22/MGDS_Download/Jamme_Gaia22/JG22_980-1300_30m.tif" \
--asset "40=${DATA_ROOT}/raw/jamme-gaia22/MGDS_Download/Jamme_Gaia22/JG22_1280-2800_40m.tif" \
--output "${DATA_ROOT}/snapshots/jamme-gaia22.lock.json"
jq '{schema, doi, license, complete, assets: [.assets[] | {resolution_m, filename, sha256}]}' \
"${DATA_ROOT}/snapshots/jamme-gaia22.lock.json"Never use --allow-partial for a final demonstration covering the complete region.
The pipeline deliberately separates a topology-complete baseline land mask from a contemporary high-water-line indicator. GSHHG and S2Coast represent different editions, methods, and coastline definitions; they are not averaged, snapped, or silently repaired.
Download GSHHG 2.3.7 ESRI shapefiles and select the full-resolution level-1 land/ocean
polygons (GSHHS_f_L1). These closed WGS84 polygons supply topology, not current coastal
position.
"${DATA_ROOT}/venvs/medchart-data/bin/medchart-data" download \
--dataset "${PROJECT_ROOT}/packages/medchart-data/configs/datasets/noaa-gshhg-2.3.7.yaml" \
--region "${RUN_ROOT}/region.yaml" \
--output "${DATA_ROOT}"
mkdir -p "${DATA_ROOT}/sources/gshhg-2.3.7"
unzip -q "${DATA_ROOT}/raw/noaa-gshhg-2.3.7/gshhg-shp-2.3.7.zip" \
-d "${DATA_ROOT}/sources/gshhg-2.3.7"
export LAND_SHP="$(find "${DATA_ROOT}/sources/gshhg-2.3.7" \
-type f -name 'GSHHS_f_L1.shp' -print -quit)"
test -n "${LAND_SHP}"
ogrinfo -so "${LAND_SHP}" GSHHS_f_L1The 1.54 GB Zenodo archive is content-addressed by record DOI and includes global polygon and polyline Shapefiles. Use the polyline product as the visible coastline stroke. Its nominal 10 m source imagery and reported global 17.4 m RMSE do not imply official hydrographic accuracy.
"${DATA_ROOT}/venvs/medchart-data/bin/medchart-data" download \
--dataset "${PROJECT_ROOT}/packages/medchart-data/configs/datasets/s2coast-2023.yaml" \
--region "${RUN_ROOT}/region.yaml" \
--output "${DATA_ROOT}"
mkdir -p "${DATA_ROOT}/sources/s2coast-2023"
unzip -q \
"${DATA_ROOT}/raw/s2coast-2023/S2Coast2023_ERSIShapeFile_vector.zip" \
-d "${DATA_ROOT}/sources/s2coast-2023"
export COAST_SHP="$(find "${DATA_ROOT}/sources/s2coast-2023" \
-type f -iname '*polyline*.shp' -print -quit)"
test -n "${COAST_SHP}"
ogrinfo "${COAST_SHP}"Before continuing, confirm that COAST_SHP is the global polyline layer, not an
example, validation, OSM-gap-fill, or polygon layer. Record both archive checksums in the
experiment manifest. The production reader clips source geometry to the Gulf region and
does not simplify it.
The restricted Overpass snapshot includes complete geometry for seamarks, named places, harbours, marinas, lighthouses, coastal structures, protected/cable/pipeline areas, major roads, rivers/canals, and inland water. It is contextual community data, not official navigation data.
"${DATA_ROOT}/venvs/medchart-data/bin/medchart-data" download \
--dataset "${PROJECT_ROOT}/packages/medchart-data/configs/datasets/osm-navigation-context.yaml" \
--region "${RUN_ROOT}/region.yaml" \
--output "${DATA_ROOT}"
export NAV_JSON="${DATA_ROOT}/raw/osm-navigation-context/osm-navigation-context-gulf-of-naples.overpass.json"
jq '.elements | length' "${NAV_JSON}"
sha256sum "${NAV_JSON}" > "${NAV_JSON}.sha256"If Overpass returns a timeout or truncated response, wait and retry; do not replace missing features with invented geometry. For a publication snapshot, record the download date and retain the exact JSON bytes indefinitely.
This step is required only for z14+. The cited October 2024 ISPRA release describes preliminary Satellite Derived Bathymetry for Liguria, together with bathymetric LiDAR and AUV acquisition. It does not publish a downloadable, licensed, validated Gulf-of-Naples raster or vertical datum. Therefore the public WebGIS layer cannot be used to unlock Naples detail. Obtain an authorized provider export covering the production region, together with its licence and validation metadata. A WMS image, habitat map, Maxar image, screenshot, digitized contour image, interpolated coastline buffer, or upsampled regional grid does not qualify.
Place each unchanged source raster under /data/raw/shallow-water/, preserve provider
metadata and licence text beside it, and verify:
mkdir -p "${DATA_ROOT}/raw/ispra-pnrr-mer"
cp /path/from/authorized/ispra-export.tif \
"${DATA_ROOT}/raw/ispra-pnrr-mer/gulf-of-naples-shallow-bathymetry.tif"
export ISPRA_RASTER="${DATA_ROOT}/raw/ispra-pnrr-mer/gulf-of-naples-shallow-bathymetry.tif"
export ISPRA_SHA="$(sha256sum "${ISPRA_RASTER}" | awk '{print $1}')"
gdalinfo "${ISPRA_RASTER}"
jq -n \
--arg sha256 "${ISPRA_SHA}" \
'{
schema: "medchart-shallow-bathymetry-source-v1",
source_id: "ispra-pnrr-mer-gulf-of-naples",
provider: "ISPRA",
product: "PNRR MER validated shallow-water bathymetry",
source_url: "REPLACE_WITH_STABLE_ISPRA_EXPORT_OR_METADATA_URL",
license: "REPLACE_WITH_EXACT_EXPORT_LICENCE",
horizontal_crs: "EPSG:4326",
vertical_datum: "REPLACE_WITH_DOCUMENTED_VERTICAL_DATUM",
acquisition_date: "REPLACE_WITH_YYYY-MM-DD",
validation_status: "validated",
method: "REPLACE_WITH_satellite-derived-bathymetry_OR_bathymetric-lidar",
native_resolution_m: 10.0,
bbox: [13.70, 39.90, 15.00, 41.20],
sha256: $sha256
}' > "${DATA_ROOT}/snapshots/ispra-pnrr-mer-gulf-of-naples.json"Replace every placeholder with provider metadata. The sidecar is rejected when its
checksum does not match, coverage does not intersect the Gulf, method is unsupported,
resolution is non-positive, or required provenance is absent. preliminary is a valid
archival status but cannot authorize final z14 detail. The raster must be an unrotated
EPSG:4326 rectilinear GeoTIFF or a supported rectilinear NetCDF. Reproject only into a new
derived artifact with the source checksum, CRS transformation, resampling method, nodata,
and GDAL version recorded.
The default command uses the complete JammeGaia22 lock, GSHHG baseline topology, S2Coast high-water-line profile, OSM navigation context, and regional fallback rasters. Native sources remain separate through MCB generation; the postprocessor selects the finest finite value at each location.
export PRODUCTION_RUN="${RUN_ROOT}/production-z13"
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" \
"${PROJECT_ROOT}/packages/medchart-preprocess/scripts/run-gulf-of-naples-production.py" \
--jamme-lock "${DATA_ROOT}/snapshots/jamme-gaia22.lock.json" \
--region "${RUN_ROOT}/preprocess-region.yaml" \
--land-topology "${LAND_SHP}" \
--coastline-profile "${COAST_SHP}" \
--land-manifest \
"${DATA_ROOT}/manifests/noaa-gshhg-2.3.7-gulf-of-naples.json" \
--coastline-manifest \
"${DATA_ROOT}/manifests/s2coast-2023-gulf-of-naples.json" \
--navigation "${NAV_JSON}" \
--additional-bathymetry \
"${DATA_ROOT}/raw/gmrt-bathymetry-max/gmrt-gulf-of-naples-max.tif" \
--additional-bathymetry \
"${DATA_ROOT}/raw/emodnet-bathymetry/emodnet-bathymetry-gulf-of-naples.tif" \
--additional-bathymetry \
"${DATA_ROOT}/raw/gebco-2026/gebco-2026-gulf-of-naples.nc" \
--safety-depth 5 \
--min-zoom 9 \
--max-zoom 13 \
--name "Open Mediterranean — Gulf of Naples 2026" \
--output "${PRODUCTION_RUN}"The output directory must be absent or empty. The runner deliberately refuses a non-empty
directory so a retry cannot combine artifacts from two experiments; change RUN_ID or
the production-z13 suffix for every rerun.
Do not add --allow-incomplete-nearshore to a final demonstration. That option is for
engineering diagnosis and places an INCOMPLETE NEARSHORE DATA — ENGINEERING DIAGNOSTIC
banner on every output tile.
After completing Section 10, append the source and request the higher zoom:
export PRODUCTION_RUN="${RUN_ROOT}/production-z14"
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" \
"${PROJECT_ROOT}/packages/medchart-preprocess/scripts/run-gulf-of-naples-production.py" \
--jamme-lock "${DATA_ROOT}/snapshots/jamme-gaia22.lock.json" \
--region "${RUN_ROOT}/preprocess-region.yaml" \
--land-topology "${LAND_SHP}" \
--coastline-profile "${COAST_SHP}" \
--land-manifest \
"${DATA_ROOT}/manifests/noaa-gshhg-2.3.7-gulf-of-naples.json" \
--coastline-manifest \
"${DATA_ROOT}/manifests/s2coast-2023-gulf-of-naples.json" \
--navigation "${NAV_JSON}" \
--shallow-bathymetry "${ISPRA_RASTER}" \
--shallow-metadata \
"${DATA_ROOT}/snapshots/ispra-pnrr-mer-gulf-of-naples.json" \
--additional-bathymetry \
"${DATA_ROOT}/raw/gmrt-bathymetry-max/gmrt-gulf-of-naples-max.tif" \
--additional-bathymetry \
"${DATA_ROOT}/raw/emodnet-bathymetry/emodnet-bathymetry-gulf-of-naples.tif" \
--additional-bathymetry \
"${DATA_ROOT}/raw/gebco-2026/gebco-2026-gulf-of-naples.nc" \
--safety-depth 5 \
--min-zoom 9 \
--max-zoom 14 \
--name "Open Mediterranean — Gulf of Naples 2026 HR" \
--output "${PRODUCTION_RUN}"The run fails unless the shallow source has estimated native resolution ≤40 m and at least 100 actual water cells shallower than 50 m. Passing this preliminary gate does not prove continuous coastline coverage; inspect nodata and survey footprint boundaries.
The runner creates all intermediate and final artifacts beneath /data/runs/<run-id>/:
production-z13/
├── normalized/
│ ├── bathymetry/
│ │ ├── jamme-2m/ ... jamme-40m/
│ │ └── additional-00/ ... additional-02/
│ ├── vectors/
│ │ ├── land/
│ │ ├── coastline/
│ │ └── navigation/
│ └── combined-vectors/
├── compute/
│ └── source-*/p000000.mcb
├── final/
│ ├── Open-Mediterranean-Gulf-of-Naples-2026.mbtiles
│ ├── Open-Mediterranean-Gulf-of-Naples-2026.tilejson.json
│ ├── Open-Mediterranean-Gulf-of-Naples-2026.signalk.json
│ └── Open-Mediterranean-Gulf-of-Naples-2026.postprocess.json
├── production-input-quality.json
└── production-manifest.json
Normalized .f32 and WKBPACK files are deterministic HPC inputs. MCB v2 files are the
serialized output of the sequential Python correctness oracle. Final PNG tiles are read
from those MCB files; no generative image step or proprietary map layer participates.
jq . "${PRODUCTION_RUN}/production-input-quality.json"
jq . "${PRODUCTION_RUN}/production-manifest.json"For a z14+ build, require all of the following:
jq -e '.nearshore_high_resolution == true' \
"${PRODUCTION_RUN}/production-input-quality.json"
jq -e '.nearshore_evidence.passes == true' \
"${PRODUCTION_RUN}/production-input-quality.json"
jq -e '.nearshore_evidence.shallow_water_cells_lt_50m >= 100' \
"${PRODUCTION_RUN}/production-input-quality.json"export MBTILES="$(find "${PRODUCTION_RUN}/final" -maxdepth 1 -name '*.mbtiles' -print -quit)"
sqlite3 "${MBTILES}" 'PRAGMA integrity_check;'
sqlite3 "${MBTILES}" 'SELECT name, value FROM metadata ORDER BY name;'
sqlite3 "${MBTILES}" \
'SELECT zoom_level, COUNT(*) FROM tiles GROUP BY zoom_level ORDER BY zoom_level;'
sqlite3 "${MBTILES}" 'SELECT COUNT(*) FROM tiles WHERE length(tile_data) = 0;'Expected conditions:
PRAGMA integrity_checkreturnsok;medchart:styleismedchart-nautical-day-v8;medchart:compute_formatismcb-v2;medchart:diagnostic_warningis empty for a final chart;- every requested zoom contains tiles;
- zero tile payloads are empty.
Verify the MBTiles checksum against the post-processing and production manifests:
sha256sum "${MBTILES}"
jq -r '.outputs.mbtiles_sha256' "${PRODUCTION_RUN}/final/"*.postprocess.json
jq -r '.output_mbtiles_sha256' "${PRODUCTION_RUN}/production-manifest.json"All three digests must be identical.
mkdir -p "${PRODUCTION_RUN}/inspection"
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" \
"${PROJECT_ROOT}/packages/medchart-preprocess/scripts/render_bathymetry.py" \
"${PRODUCTION_RUN}/normalized/bathymetry/jamme-2m/p000000.f32" \
--output "${PRODUCTION_RUN}/inspection/jamme-2m.png" \
--no-show
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" \
"${PROJECT_ROOT}/packages/medchart-preprocess/scripts/render_wkbpack.py" \
"${PRODUCTION_RUN}/normalized/combined-vectors/p000000.wkbpack" \
--output "${PRODUCTION_RUN}/inspection/vector-context.png" \
--no-show
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" \
"${PROJECT_ROOT}/packages/medchart-preprocess/scripts/render_compute_block.py" \
"${PRODUCTION_RUN}/compute/source-00/p000000.mcb" \
--output-prefix "${PRODUCTION_RUN}/inspection/source-00" \
--no-showCheck for nodata holes, coordinate inversion, raster stair-stepping, clipped coastline, empty land polygons, duplicate linework, and unreasonably dense derived annotations. Visual inspection is diagnostic only and must never modify or repair source data.
The publication renderer reads the PNG bytes physically stored in MBTiles. It does not rerender MCB data, so the overview shows exactly what a Signal K client receives.
"${DATA_ROOT}/venvs/medchart-preprocess/bin/python" \
"${PROJECT_ROOT}/packages/medchart-preprocess/scripts/figure_final_results.py" \
--mbtiles "${MBTILES}" \
--output "${PRODUCTION_RUN}/final/final-overview" \
--zoom 13For the verified z14 profile, use --zoom 14. The script writes PNG, PDF, SVG, and a
machine-readable figure-provenance sidecar with source and output checksums.
Copy the immutable MBTiles file to the local chart directory used by the Signal K Charts plugin. Preserve the production directory as the authoritative experiment record.
mkdir -p "${DATA_ROOT}/signalk-charts"
cp "${MBTILES}" "${DATA_ROOT}/signalk-charts/"
sha256sum "${DATA_ROOT}/signalk-charts/$(basename "${MBTILES}")"Configure @signalk/charts-plugin (displayed as SK Charts) to read
/data/signalk-charts, restart or rescan the
plugin, and verify that the published bounds and zoom range match the MBTiles metadata.
Do not archive only the final image. Preserve the software identity, frozen raw data, locks, manifests, normalized descriptors, MCB outputs, quality report, final MBTiles, and figure provenance.
find "${RUN_ROOT}" -type f -print \
| LC_ALL=C sort \
| while IFS= read -r artifact; do sha256sum "${artifact}"; done \
> "${RUN_ROOT}/experiment-files.sha256"
tar --create --file "${DATA_ROOT}/${RUN_ID}.tar" \
--directory "${DATA_ROOT}/runs" \
"${RUN_ID}"
sha256sum "${DATA_ROOT}/${RUN_ID}.tar" \
> "${DATA_ROOT}/${RUN_ID}.tar.sha256"Raw third-party assets retain their upstream licences. Review redistribution obligations before sharing the archive; in particular, OSM-derived databases remain subject to ODbL, and scientific source licences are not replaced by MedChart's MIT software licence.
A final result is acceptable only when:
- the Git commit and any working-tree patch are archived;
- every raw provider asset has a SHA-256 digest and licence/provenance record;
- the complete seven-resolution JammeGaia22 lock is present;
- GSHHG L1 land polygons and S2Coast polyline blocks contain records and retain distinct provenance;
- fallback priority is finest valid sample, not bounding-box overlap;
- z14+ has an authorized, validated ISPRA or equivalent ≤40 m shallow-water raster, a complete metadata sidecar, and passes the native-cell evidence gate;
- incomplete diagnostic warnings are absent from a claimed final product;
- MBTiles integrity, zoom counts, metadata, and checksums pass;
- final figures are generated from stored MBTiles tile bytes;
- the chart is labelled non-official and not suitable for safety of navigation.
If any condition fails, retain the run as a diagnostic artifact, correct the source or configuration, and execute a new run directory. Never edit raw data or overwrite a prior experiment to make the chart appear more complete.
C-MAP Chart Explorer is an observational quality benchmark, not a source dataset or presentation library. Compare the final MBTiles at the same geographic extent and approximately the same displayed scale. Do not copy screenshots, tiles, fonts, symbols, geometry, or proprietary feature data into this project.
A successful comparison requires the same functional information hierarchy: warm land must separate immediately from pale water; the high-water-line/coastline stroke must be crisper and darker than ordinary context; shallow depth areas must progress through legible blue tints; the configured safety contour must dominate secondary contours; magenta restricted/cable/pipeline context must remain conspicuous without masking depth; and names, navigation aids, and derived grid-depth labels must increase with zoom while remaining collision-controlled. Inspect at z9, z11, z13, and, only after the shallow-data gate passes, z14. A chart that is attractive only at one overview scale has not met the benchmark.
Comparable style does not mean comparable hydrographic content. C-MAP can contain licensed measurements and controlled navigation objects absent from the open inputs. If the MedChart source-resolution gate suppresses detail, the correct result is sparse, neutral water—not interpolated contours, fabricated soundings, or copied proprietary content. Record every visible gap as a source-coverage limitation in the production report.