194 lines
8.2 KiB
Bash
194 lines
8.2 KiB
Bash
|
|
#!/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>"
|