#!/usr/bin/env bash # Second half of tools/geodata/offroad-osm/build (runs in the geotools image; no database): the `routes` layer, then # one tileset. Planetiler's YAML schemas can't read route relations, so route=atv|snowmobile|mtb relations (GDAL's # `multilinestrings`) and ways tagged with such a route or piste:type=snowmobile (`lines`) are read with GDAL's OSM # driver, tiled with tippecanoe and joined with Planetiler's `ways` (tile-join). The result stays one ODbL tileset, # stamped with the licence, the 4.6 offer and the extract date (pmtiles_meta.py), and never holds non-OSM data. # # HG_OSM_PBF=/data/downloads/osm/.osm.pbf HG_WAYS=/data/build/offroad-osm-ways.pmtiles tools/geodata/run offroad-osm/routes set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" log() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } PBF="${HG_OSM_PBF:?HG_OSM_PBF is required}" WAYS="${HG_WAYS:?HG_WAYS is required}" [[ -f "$PBF" ]] || die "no OSM file at $PBF" [[ -f "$WAYS" ]] || die "no ways tileset at $WAYS" BUILD_DIR="${HG_BUILD_DIR:-/tmp/hg-build}/offroad-osm" rm -rf "$BUILD_DIR" && mkdir -p "$BUILD_DIR" # The extract's date: its replication timestamp as Planetiler read it, else the file's date (curl -R keeps the server's). extract_day="$(python3 -I -c 'import sys; sys.path.insert(0, sys.argv[2]); import pmtiles_meta print(pmtiles_meta.read_metadata(sys.argv[1]).get("planetiler:osm:osmosisreplicationtime", "")[:10])' "$WAYS" "$HERE/../lib")" VERSION="OpenStreetMap ${extract_day:-$(date -u -r "$PBF" +%Y-%m-%d)}" ROUTES="'atv','snowmobile','mtb'" # Offroad's republishing register (R0.2a-b): refuses the run if OpenStreetMap's entry were ever blocked or taken down. python3 -I "$HERE/../republishing/republishing.py" require dataset:osm-offroad # One pass per GDAL layer; the node index goes to the container's own disk and any GDAL ERROR fails the run. read_layer() { local layer="$1" where="$2" ogr2ogr -f GeoJSONSeq "$BUILD_DIR/$layer.geojsonl" "$PBF" -oo CONFIG_FILE="$HERE/osmconf-offroad.ini" \ -dialect SQLite -sql "select case when route in ($ROUTES) then route else 'snowmobile' end as route, name, ref, network, operator, geometry from $layer where $where" \ --config CPL_TMPDIR "$BUILD_DIR" --config OSM_MAX_TMPFILE_SIZE 4096 --config OSM_COMPRESS_NODES YES \ 2> "$BUILD_DIR/$layer.err" || { cat "$BUILD_DIR/$layer.err" >&2; die "ogr2ogr ($layer) failed"; } if grep -q '^ERROR' "$BUILD_DIR/$layer.err"; then cat "$BUILD_DIR/$layer.err" >&2; die "GDAL reported errors ($layer)"; fi } log "Route relations and snowmobile pistes from $(basename "$PBF")" read_layer multilinestrings "route in ($ROUTES)" read_layer lines "route in ($ROUTES) or piste_type = 'snowmobile'" features="$(cat "$BUILD_DIR"/*.geojsonl | wc -l)" log "$features route features" cp "$WAYS" "$BUILD_DIR/ways.pmtiles" inputs=("$BUILD_DIR/ways.pmtiles") # A region with no ATV, snowmobile or MTB routes in OpenStreetMap (Prince Edward Island) gets no routes layer: # tippecanoe refuses an empty input. if (( features > 0 )); then tippecanoe -o "$BUILD_DIR/routes.pmtiles" -t "$BUILD_DIR" --force --quiet -l routes -Z10 -z14 \ --drop-densest-as-needed "$BUILD_DIR/multilinestrings.geojsonl" "$BUILD_DIR/lines.geojsonl" inputs+=("$BUILD_DIR/routes.pmtiles") fi tile-join -o "$BUILD_DIR/offroad-osm.pmtiles" -f -q -pk -n "HuntGrid Offroad OSM" \ -A '© OpenStreetMap contributors' \ "${inputs[@]}" # The ODbL notice, licence URI, 4.6 offer and extract date in the file's own metadata (F20.15). python3 "$HERE/../lib/pmtiles_meta.py" odbl "$BUILD_DIR/offroad-osm.pmtiles" offroad-osm "$VERSION" mkdir -p /data/tiles cp "$BUILD_DIR/offroad-osm.pmtiles" /data/tiles/.offroad-osm.pmtiles.part mv -f /data/tiles/.offroad-osm.pmtiles.part /data/tiles/offroad-osm.pmtiles rm -rf "$BUILD_DIR" log "Done: data/tiles/offroad-osm.pmtiles ($VERSION)"