ublox-gps-data-processing/setup.sh

194 lines
8.2 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# setup.sh - One-time environment bootstrap for the GPS processing pipeline.
#
# Installs (all local to this directory, no sudo required):
# venv/ Python env: pyubx2, pygnssutils, pyserial, numpy, pandas, pyproj
# tools/bin/ RTKLIB demo5 v2.5.1 (convbin, rnx2rtkp, pos2kml) [stock apt fallback]
# tools/antex/ igs20.atx + ngs20.atx antenna models; tools/antenna.json (rover antenna)
# tools/bin/CRX2RNX Hatanaka RINEX decompressor (fallback for .d CORS files)
# tools/cache/ NGS CORS station catalog
#
# Idempotent: re-running skips anything already installed.
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
TOOLS="$SCRIPT_DIR/tools"
VENV="$SCRIPT_DIR/venv"
DEMO5_TAG="v2.5.1"
DEMO5_REPO="https://github.com/rtklibexplorer/RTKLIB"
PASS=()
WARN=()
say() { printf '\n=== %s ===\n' "$*"; }
ok() { printf ' [ok] %s\n' "$*"; PASS+=("$*"); }
warn() { printf ' [WARN] %s\n' "$*"; WARN+=("$*"); }
skip() { printf ' [skip] %s (already present)\n' "$*"; }
fetch() { # fetch <url> <dest>
curl -fsSL --max-time 300 -o "$2" "$1"
}
# ---------------------------------------------------------------- 1. Python venv
say "1/6 Python virtual environment"
if [ -x "$VENV/bin/python" ]; then
skip "venv"
else
python3 -m venv "$VENV" || { echo "FATAL: venv creation failed"; exit 1; }
fi
if "$VENV/bin/python" -c "import pyubx2, pygnssutils, serial, numpy, pandas, pyproj, matplotlib" 2>/dev/null; then
skip "python packages"
else
"$VENV/bin/pip" install --quiet --upgrade pip
"$VENV/bin/pip" install --quiet pyubx2 pygnssutils pyserial numpy pandas pyproj matplotlib \
|| { echo "FATAL: pip install failed"; exit 1; }
fi
"$VENV/bin/python" -c "import pyubx2, pygnssutils, serial, numpy, pandas, pyproj, matplotlib" \
&& ok "venv with pyubx2/pygnssutils/pyserial/numpy/pandas/pyproj/matplotlib" \
|| { echo "FATAL: package import check failed"; exit 1; }
mkdir -p "$TOOLS/bin" "$TOOLS/antex" "$TOOLS/cache" "$TOOLS/src"
# ---------------------------------------------------------------- 2. RTKLIB demo5
say "2/6 RTKLIB demo5 $DEMO5_TAG (convbin, rnx2rtkp, pos2kml)"
build_demo5_app() { # build_demo5_app <appname>
local app="$1"
local gccdir="$TOOLS/src/RTKLIB/app/consapp/$app/gcc"
[ -d "$gccdir" ] || return 1
# -lgfortran in some makefiles is vestigial; try without it first.
if ! make -C "$gccdir" -j"$(nproc)" LDLIBS="-lm" >"$TOOLS/src/build_$app.log" 2>&1; then
make -C "$gccdir" clean >/dev/null 2>&1
make -C "$gccdir" -j"$(nproc)" >>"$TOOLS/src/build_$app.log" 2>&1 || return 1
fi
cp "$gccdir/$app" "$TOOLS/bin/$app"
}
if [ -x "$TOOLS/bin/convbin" ] && [ -x "$TOOLS/bin/rnx2rtkp" ] && [ -x "$TOOLS/bin/pos2kml" ]; then
skip "RTKLIB binaries"
[ -f "$TOOLS/STOCK_RTKLIB" ] && warn "using STOCK RTKLIB (BDS/L5 signals will be missing from RINEX)"
else
if [ ! -d "$TOOLS/src/RTKLIB" ]; then
git clone --depth 1 --branch "$DEMO5_TAG" "$DEMO5_REPO" "$TOOLS/src/RTKLIB" \
|| warn "git clone of demo5 RTKLIB failed"
fi
built=true
for app in convbin rnx2rtkp pos2kml; do
if build_demo5_app "$app"; then
printf ' built %s\n' "$app"
else
warn "demo5 build failed for $app (see tools/src/build_$app.log)"
built=false
fi
done
if $built; then
rm -f "$TOOLS/STOCK_RTKLIB"
ok "RTKLIB demo5 $DEMO5_TAG built from source"
else
# Fallback: stock RTKLIB 2.4.3 from the Ubuntu archive (works, but silently
# drops BeiDou + L5/E5 observations from this receiver's raw data).
echo " falling back to stock RTKLIB 2.4.3 from apt archive..."
tmpd="$(mktemp -d)"
if (cd "$tmpd" && apt-get download rtklib >/dev/null 2>&1 && dpkg -x rtklib_*.deb x); then
cp "$tmpd"/x/usr/bin/{convbin,rnx2rtkp,pos2kml} "$TOOLS/bin/"
touch "$TOOLS/STOCK_RTKLIB"
warn "STOCK RTKLIB installed - BDS/L5 will be MISSING from RINEX output"
else
warn "stock RTKLIB fallback also failed - pipeline cannot run"
fi
rm -rf "$tmpd"
fi
fi
for app in convbin rnx2rtkp pos2kml; do
[ -x "$TOOLS/bin/$app" ] || warn "$app missing from tools/bin"
done
# ---------------------------------------------------------------- 3. Antenna models
say "3/6 Antenna calibration models (ANTEX)"
if [ -s "$TOOLS/antex/igs20.atx" ]; then skip "igs20.atx"; else
fetch "https://files.igs.org/pub/station/general/igs20.atx" "$TOOLS/antex/igs20.atx" \
&& ok "igs20.atx (IGS antenna models incl. CORS base antennas)" \
|| warn "igs20.atx download failed"
fi
if [ -s "$TOOLS/antex/ngs20.atx" ]; then skip "ngs20.atx"; else
fetch "https://geodesy.noaa.gov/ANTCAL/LoadFile?file=ngs20.atx" "$TOOLS/antex/ngs20.atx" \
&& ok "ngs20.atx (NGS absolute calibrations incl. ArduSimple)" \
|| warn "ngs20.atx download failed"
fi
# Resolve the rover antenna (NGS calibration AS-ANT3BCAL01, ArduSimple) to its
# exact 20-char ANTEX type string, consumed by process_gps.py as ant1-anttype.
"$VENV/bin/python" - "$TOOLS" <<'EOF'
import json, re, sys, pathlib
tools = pathlib.Path(sys.argv[1])
result = {"antex_name": None, "source": None, "candidates": []}
for atx in ("ngs20.atx", "igs20.atx"):
p = tools / "antex" / atx
if not p.is_file():
continue
for line in p.open(encoding="utf-8", errors="replace"):
if "TYPE / SERIAL" in line and "ANT3B" in line[:20].upper():
name = line[:20].rstrip()
result["candidates"].append({"name": name, "file": atx})
if result["antex_name"] is None:
result["antex_name"], result["source"] = name, atx
(tools / "antenna.json").write_text(json.dumps(result, indent=2))
if result["antex_name"]:
print(f' [ok] rover antenna resolved: "{result["antex_name"]}" ({result["source"]})')
else:
print(" [WARN] no ANT3B antenna found in ANTEX files - PPK will run without rover PCO/PCV")
EOF
# ---------------------------------------------------------------- 4. CRX2RNX
say "4/6 CRX2RNX (Hatanaka RINEX decompressor, fallback for .d CORS files)"
if [ -x "$TOOLS/bin/CRX2RNX" ]; then
skip "CRX2RNX"
else
tmpd="$(mktemp -d)"
if fetch "https://terras.gsi.go.jp/ja/crx2rnx/RNXCMP_4.2.0_Linux_gcc_64bit.tar.gz" "$tmpd/rnxcmp.tar.gz" \
&& tar -xzf "$tmpd/rnxcmp.tar.gz" -C "$tmpd" \
&& cp "$tmpd"/RNXCMP_*/bin/CRX2RNX "$TOOLS/bin/CRX2RNX" 2>/dev/null \
|| cp "$tmpd"/RNXCMP_*/CRX2RNX "$TOOLS/bin/CRX2RNX" 2>/dev/null; then
chmod +x "$TOOLS/bin/CRX2RNX"
ok "CRX2RNX 4.2.0"
else
warn "CRX2RNX install failed (only needed for CORS stations without plain .o files)"
fi
rm -rf "$tmpd"
fi
# ---------------------------------------------------------------- 5. CORS catalog
say "5/6 NGS CORS station catalog"
if [ -s "$TOOLS/cache/itrf2020_geo.comp.txt" ]; then
skip "station catalog"
else
fetch "https://geodesy.noaa.gov/corsdata/coord/coord_20/itrf2020_geo.comp.txt" \
"$TOOLS/cache/itrf2020_geo.comp.txt" \
&& ok "CORS station catalog (ITRF2020 coordinates)" \
|| warn "CORS catalog download failed (process_gps.py will retry at run time)"
fi
# ---------------------------------------------------------------- 6. Self-check
say "6/6 Self-check summary"
for app in convbin rnx2rtkp pos2kml; do
if [ -x "$TOOLS/bin/$app" ]; then
printf ' %-10s %s\n' "$app" "$("$TOOLS/bin/$app" -h 2>&1 | grep -m1 -iE 'synops|usage|version' || echo present)"
fi
done
[ -x "$TOOLS/bin/CRX2RNX" ] && printf ' %-10s %s\n' "CRX2RNX" "present"
[ -s "$TOOLS/cache/itrf2020_geo.comp.txt" ] && printf ' %-10s %d stations\n' "catalog" "$(wc -l < "$TOOLS/cache/itrf2020_geo.comp.txt")"
[ -s "$TOOLS/antenna.json" ] && printf ' %-10s %s\n' "antenna" "$(cat "$TOOLS/antenna.json" | "$VENV/bin/python" -c 'import json,sys; print(json.load(sys.stdin)["antex_name"])')"
echo
if [ ${#WARN[@]} -gt 0 ]; then
echo "Completed with ${#WARN[@]} warning(s):"
printf ' - %s\n' "${WARN[@]}"
else
echo "Setup complete - no warnings."
fi
echo
echo "Mode 1 (real-time RTK) additionally requires free registration at"
echo " http://rtn.usm.edu/RegisterAccount.aspx (Mississippi GCGC RTN)"
echo "then: export GCGC_USER=<user> GCGC_PASS=<pass>"