Compare commits
1 commit
master
...
geo-improv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9f2f4f262 |
12
.github/workflows/ccpp.yml
vendored
|
|
@ -4,15 +4,15 @@ on: [push]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: "recursive"
|
||||
- uses: actions/checkout@v1
|
||||
- name: deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev libncurses5-dev libeigen3-dev libzstd-dev libfec-dev libfmt-dev
|
||||
run: sudo apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev
|
||||
- name: submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: config
|
||||
run: echo WSLAY=-lwslay > Makefile.local
|
||||
- name: make
|
||||
|
|
|
|||
32
.github/workflows/docker.yml
vendored
|
|
@ -1,32 +0,0 @@
|
|||
name: Build and publish Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout submodules
|
||||
run: git submodule update --init --recursive
|
||||
- name: Set up docker buildx
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v3
|
||||
with:
|
||||
buildx-version: latest
|
||||
qemu-version: latest
|
||||
- name: Login to docker registry
|
||||
run: |
|
||||
docker login --username ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_TOKEN }}
|
||||
- name: Run buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--tag berthubert/galmon \
|
||||
--platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 \
|
||||
--output "type=registry" \
|
||||
--build-arg MAKE_FLAGS=-j1 \
|
||||
--file Dockerfile \
|
||||
.
|
||||
19
.gitignore
vendored
|
|
@ -1,24 +1,5 @@
|
|||
# - created by protoc
|
||||
navmon.pb.cc
|
||||
navmon.pb.h
|
||||
# - created by Makefile
|
||||
navcat
|
||||
navdisplay
|
||||
navdump
|
||||
navnexus
|
||||
navrecv
|
||||
testrunner
|
||||
tlecatch
|
||||
ubxtool
|
||||
# - created by update-tles
|
||||
active.txt
|
||||
beidou.txt
|
||||
galileo.txt
|
||||
glo-ops.txt
|
||||
gps-ops.txt
|
||||
# - git version internals
|
||||
githash.h
|
||||
#
|
||||
*.csv
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
|
|
|||
1
.gitmodules
vendored
|
|
@ -4,4 +4,3 @@
|
|||
[submodule "ext/sgp4"]
|
||||
path = ext/sgp4
|
||||
url = https://github.com/dnwrnr/sgp4.git
|
||||
ignore = untracked
|
||||
|
|
|
|||
19
Building.md
|
|
@ -1,19 +0,0 @@
|
|||
Raspberry Pi 3B+
|
||||
---------------
|
||||
|
||||
Building more than ubxtool, navdump, and navparse will require quite a bit of available RAM.
|
||||
In the Raspbian image, the default swap file size for _/var/swap_ is only 100MB.
|
||||
More swap space will be needed if you want the compiling to finish without it seeming
|
||||
to take a geologic age.
|
||||
|
||||
The swap file is configured in _/etc/dphys-swapfile_ and managed using the __/sbin/dphys-swapfile__ utility.
|
||||
|
||||
Assuming the output of _df -h_ and _free_ indicate you have sufficient free file space and
|
||||
the swap file is indeed 100MB (respectively), and you do not have Chromium running (it eats
|
||||
RAM), you edit the config file (suggest changing 100 to 960) and then (as root):
|
||||
```
|
||||
/sbin/dphys-swapfile swapoff
|
||||
echo Be Patient. This could take a while
|
||||
/sbin/dphys-swapfile setup
|
||||
/sbin/dphys-swapfile swapon
|
||||
```
|
||||
38
Dockerfile
|
|
@ -1,38 +0,0 @@
|
|||
#
|
||||
# First stage - builder
|
||||
#
|
||||
FROM debian:10-slim AS builder
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
# This allows you to use a local Debian mirror
|
||||
ARG APT_URL=http://deb.debian.org/debian/
|
||||
ARG MAKE_FLAGS=-j2
|
||||
|
||||
RUN sed -i "s%http://deb.debian.org/debian/%${APT_URL}%" /etc/apt/sources.list \
|
||||
&& apt-get update && apt-get -y upgrade \
|
||||
&& apt-get install -y protobuf-compiler libh2o-dev libcurl4-openssl-dev \
|
||||
libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev \
|
||||
libeigen3-dev libzstd-dev libfmt-dev libncurses-dev \
|
||||
make gcc g++ git build-essential curl autoconf automake help2man
|
||||
|
||||
# Build
|
||||
ADD . /galmon-src/
|
||||
RUN cd /galmon-src/ \
|
||||
&& make $MAKE_FLAGS \
|
||||
&& prefix=/galmon make install
|
||||
|
||||
#
|
||||
# Second stage - contains just the binaries
|
||||
#
|
||||
FROM debian:10-slim
|
||||
RUN apt-get update && apt-get -y upgrade \
|
||||
&& apt-get install -y libcurl4 libssl1.1 libprotobuf17 libh2o-evloop0.13 \
|
||||
libncurses6 \
|
||||
&& apt-get -y clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=builder /galmon/ /galmon/
|
||||
ENV PATH=/galmon/bin:${PATH}
|
||||
ENV LC_ALL C.UTF-8
|
||||
WORKDIR /galmon/bin
|
||||
147
Makefile
|
|
@ -1,30 +1,12 @@
|
|||
CFLAGS = -O3 -Wall -ggdb
|
||||
|
||||
CXXFLAGS:= -std=gnu++17 -Wall -O3 -ggdb -MMD -MP -fno-omit-frame-pointer -Iext/CLI11 \
|
||||
-Iext/powerblog/ext/simplesocket -Iext/powerblog/ext/ \
|
||||
CXXFLAGS:= -std=gnu++17 -Wall -O3 -MMD -MP -ggdb -fno-omit-frame-pointer -Iext/CLI11 \
|
||||
-Iext/fmt-5.2.1/include/ -Iext/powerblog/ext/simplesocket -Iext/powerblog/ext/ \
|
||||
-I/usr/local/opt/openssl/include/ \
|
||||
-Iext/sgp4/libsgp4/ \
|
||||
-I/usr/local/include
|
||||
|
||||
# CXXFLAGS += -Wno-delete-non-virtual-dtor
|
||||
|
||||
# If unset, create a variable for the path or binary to use as "install" for debuild.
|
||||
INSTALL ?= install
|
||||
# If unset, create a variable with the path used by "make install"
|
||||
prefix ?= /usr/local/ubxtool
|
||||
# If unset, create a variable for a path underneath $prefix that stores html files
|
||||
htdocs ?= /share/package
|
||||
|
||||
ifneq (,$(wildcard ubxsec.c))
|
||||
EXTRADEP = ubxsec.o
|
||||
else ifneq (,$(wildcard ubxsec.o))
|
||||
EXTRADEP = ubxsec.o
|
||||
endif
|
||||
|
||||
CHEAT_ARG := $(shell ./update-git-hash-if-necessary)
|
||||
|
||||
PROGRAMS = navparse ubxtool navnexus navcat navrecv navdump testrunner navdisplay tlecatch reporter sp3feed \
|
||||
galmonmon rinreport rinjoin rtcmtool gndate septool navmerge
|
||||
PROGRAMS = navparse ubxtool navnexus navrecv navdump testrunner navdisplay tlecatch
|
||||
|
||||
all: navmon.pb.cc $(PROGRAMS)
|
||||
|
||||
|
|
@ -32,106 +14,39 @@ all: navmon.pb.cc $(PROGRAMS)
|
|||
|
||||
-include *.d
|
||||
|
||||
navmon.pb.h: navmon.proto
|
||||
protoc --cpp_out=./ navmon.proto
|
||||
clean:
|
||||
rm -f *~ *.o *.d ext/*/*.o $(PROGRAMS) navmon.pb.h navmon.pb.cc
|
||||
|
||||
H2OPP=ext/powerblog/h2o-pp.o
|
||||
SIMPLESOCKETS=ext/powerblog/ext/simplesocket/swrappers.o ext/powerblog/ext/simplesocket/sclasses.o ext/powerblog/ext/simplesocket/comboaddress.o
|
||||
|
||||
navparse: navparse.o ext/fmt-5.2.1/src/format.o $(H2OPP) $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -L/usr/local/opt/openssl/lib/ -lh2o-evloop -lssl -lcrypto -lz -lcurl -lprotobuf $(WSLAY)
|
||||
|
||||
navdump: navdump.o ext/fmt-5.2.1/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o navmon.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o sp3.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
navdisplay: navdisplay.o ext/fmt-5.2.1/src/format.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o ephemeris.o navmon.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lncurses
|
||||
|
||||
|
||||
navnexus: navnexus.o ext/fmt-5.2.1/src/format.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
navrecv: navrecv.o ext/fmt-5.2.1/src/format.o $(SIMPLESOCKETS) navmon.pb.o storage.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc))
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
navmon.pb.cc: navmon.proto
|
||||
protoc --cpp_out=./ navmon.proto
|
||||
|
||||
ubxtool: navmon.pb.o ubxtool.o ubx.o bits.o ext/fmt-5.2.1/src/format.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS)
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread
|
||||
|
||||
SIMPLESOCKETS=ext/powerblog/ext/simplesocket/swrappers.o ext/powerblog/ext/simplesocket/sclasses.o ext/powerblog/ext/simplesocket/comboaddress.o
|
||||
|
||||
|
||||
clean:
|
||||
rm -f *~ *.o *.d ext/*/*.o ext/*/*.d $(PROGRAMS) navmon.pb.h navmon.pb.cc $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) $(SIMPLESOCKETS)
|
||||
rm -f ext/sgp4/libsgp4/*.d ext/powerblog/ext/simplesocket/*.d
|
||||
|
||||
help2man:
|
||||
$(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/share/man/man1
|
||||
HELP2MAN_DESCRIPTION="Open-source GNSS Monitoring Project"
|
||||
$(foreach binaryfile,$(PROGRAMS),help2man -N -n "$(HELP2MAN_DESCRIPTION)" ./$(binaryfile) | gzip > $(DESTDIR)$(prefix)/share/man/man1/$(binaryfile).1.gz;)
|
||||
@echo until these binaries support --help and --version remove the broken output
|
||||
rm -f $(DESTDIR)$(prefix)/share/man/man1/rinreport.1.gz
|
||||
rm -f $(DESTDIR)$(prefix)/share/man/man1/rtcmtool.1.gz
|
||||
rm -f $(DESTDIR)$(prefix)/share/man/man1/testrunner.1.gz
|
||||
|
||||
install: $(PROGRAMS) help2man
|
||||
$(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/bin
|
||||
$(foreach binaryfile,$(PROGRAMS),$(INSTALL) -s -m 755 -D ./$(binaryfile) $(DESTDIR)$(prefix)/bin/$(binaryfile);)
|
||||
@echo "using cp instead of install because recursive directories of ascii"
|
||||
mkdir -p $(DESTDIR)$(prefix)$(htdocs)/galmon
|
||||
cp -a html $(DESTDIR)$(prefix)$(htdocs)/galmon/
|
||||
|
||||
decrypt: decrypt.o bits.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -lfmt
|
||||
|
||||
navparse: navparse.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o trkmeas.o influxpush.o ${EXTRADEP} githash.o sbas.o rtcm.o galileo.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -L/usr/local/opt/openssl/lib/ -lz -lcurl -lprotobuf -lfmt
|
||||
|
||||
reporter: reporter.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o influxpush.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
|
||||
|
||||
sp3feed: sp3feed.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o influxpush.o githash.o sp3.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
|
||||
|
||||
|
||||
tracker: tracker.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
|
||||
|
||||
|
||||
galmonmon: galmonmon.o $(SIMPLESOCKETS) minicurl.o ubx.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o navmon.o coverage.o osen.o githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -pthread -L/usr/local/lib -lprotobuf -lcurl -lfmt
|
||||
|
||||
|
||||
# rs.o fixhunter.o
|
||||
navdump: navdump.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o navmon.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) tle.o sp3.o osen.o trkmeas.o githash.o rinex.o sbas.o rtcm.o galileo.o ${EXTRADEP}
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lz -lfmt
|
||||
# -lfec
|
||||
|
||||
navdisplay: navdisplay.o bits.o navmon.pb.o gps.o ephemeris.o beidou.o glonass.o ephemeris.o navmon.o osen.o githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lncurses -lfmt
|
||||
|
||||
|
||||
navnexus: navnexus.o $(SIMPLESOCKETS) bits.o navmon.pb.o storage.o githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lfmt
|
||||
|
||||
navcat: navcat.o $(SIMPLESOCKETS) ubx.o bits.o navmon.pb.o storage.o navmon.o githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lfmt
|
||||
|
||||
|
||||
navrecv: navrecv.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o zstdwrap.o navmon.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lzstd -lfmt
|
||||
|
||||
navmerge: navmerge.o $(SIMPLESOCKETS) navmon.pb.o storage.o githash.o zstdwrap.o navmon.o nmmsender.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf -lzstd -lfmt
|
||||
|
||||
|
||||
tlecatch: tlecatch.o $(patsubst %.cc,%.o,$(wildcard ext/sgp4/libsgp4/*.cc)) githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -pthread -lprotobuf
|
||||
|
||||
rinreport: rinreport.o rinex.o githash.o navmon.o ephemeris.o osen.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lfmt
|
||||
|
||||
rinjoin: rinjoin.o rinex.o githash.o navmon.o ephemeris.o osen.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -lz -pthread -lfmt
|
||||
|
||||
|
||||
rtcmtool: rtcmtool.o navmon.pb.o githash.o bits.o nmmsender.o $(SIMPLESOCKETS) navmon.o rtcm.o zstdwrap.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lz -pthread -lprotobuf -lzstd -lfmt
|
||||
|
||||
|
||||
ubxtool: navmon.pb.o ubxtool.o ubx.o bits.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd -lfmt
|
||||
|
||||
septool: navmon.pb.o septool.o bits.o galileo.o gps.o beidou.o navmon.o ephemeris.o $(SIMPLESOCKETS) osen.o githash.o nmmsender.o zstdwrap.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -pthread -lzstd -lfmt
|
||||
|
||||
|
||||
testrunner: navmon.pb.o testrunner.o ubx.o bits.o galileo.o gps.o beidou.o ephemeris.o sp3.o osen.o navmon.o rinex.o githash.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf -lz -pthread -lfmt
|
||||
|
||||
gndate: gndate.o githash.o navmon.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lfmt
|
||||
testrunner: navmon.pb.o testrunner.o ubx.o bits.o ext/fmt-5.2.1/src/format.o galileo.o gps.o beidou.o ephemeris.o sp3.o
|
||||
$(CXX) -std=gnu++17 $^ -o $@ -L/usr/local/lib -lprotobuf
|
||||
|
||||
check: testrunner
|
||||
./testrunner
|
||||
|
|
|
|||
54
Operator.md
|
|
@ -1,54 +0,0 @@
|
|||
Hi!
|
||||
|
||||
Many thanks for joining the galmon.eu project as an observer and station operator!
|
||||
|
||||
Here are some ground rules to follow once you have locally tested your galmon station:
|
||||
|
||||
* Please ask bert@hubertnet.nl or [ahu on
|
||||
IRC](https://webchat.oftc.net/?channels=galileo) or
|
||||
[@PowerDNS_Bert](https://twitter.com/PowerDNS_Bert) for a station ID and receiver
|
||||
hostname.
|
||||
* Please do not randomly pick a station ID!! Project data quality and integrity
|
||||
depends on your cooperation.
|
||||
* The rule is: one station ID per receiver. Do not under any circumstances
|
||||
use the same ID on multiple receivers, even if you think it should be
|
||||
fine. We have 2^64 IDs available, just ask for more IDs.
|
||||
* Even if you think it should be fine to reuse the same ID, for example
|
||||
because your observer page on galmon.eu will look nicer, DO NOT DO IT!
|
||||
It messes up our algorithms which track the clock of your receiver.
|
||||
* Please set the --owner and --remark fields - they help with administration.
|
||||
These fields will show up on https://galmon.eu/observers.html
|
||||
* Ublox8 based receivers support (GPS AND Galileo) + (BeiDou OR GLONASS). They can't
|
||||
process all four systems at the same time. GPS and Galileo share a slot. BeiDou or GLONASS
|
||||
share a second slot. See
|
||||
[here](https://www.geospatialworld.net/blogs/gnss-frequency-bands-for-constellations/)
|
||||
for some more information.
|
||||
* If you want to know if you should add BeiDou or GLONASS, you can either use your own
|
||||
preference or ask us what we are missing in your region.
|
||||
|
||||
Downtime
|
||||
--------
|
||||
The project is grateful for every bit you can provide. Don't feel bad about
|
||||
downtime either because your family needs the power plug for Christmas
|
||||
decorations (this happened) or for whatever reason.
|
||||
|
||||
A good way to increase uptime is to use the Systemd file to ensure automatic
|
||||
startup on reboots. Some stations with bad power, bad hardware or bad cables
|
||||
have had great success enabling the Raspberry Pi watchdog.
|
||||
|
||||
(need instruction here how to do that for various Raspberries, it differs)
|
||||
|
||||
Upgrades
|
||||
--------
|
||||
From time to time we upgrade the ubxtool receiver software. Old data also
|
||||
works but typically if you upgrade, the value to the network increases.
|
||||
|
||||
Improving reception
|
||||
-------------------
|
||||
A good view of the sky can help tremendously, although as noted, we are
|
||||
happy with every bit we receive. It helps to put your receiver on a "ground
|
||||
plate" which is a fancy word for a piece of metal (or mesh even).
|
||||
|
||||
In addition, if you are in a northerly region, your view to the south is
|
||||
most important. Conversely, if you are in the south, more action will be to
|
||||
the north.
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
# .deb Package Overview
|
||||
|
||||
2020-01-20: Initial Commit
|
||||
|
||||
## Important Information
|
||||
|
||||
This section is a step-by-step tutorial.
|
||||
|
||||
### Basic Installation
|
||||
|
||||
Pick either debian (almost everything) or raspbian (special armv6 build for older models) and create the source file.
|
||||
```sh
|
||||
echo "deb https://ota.bike/raspbian/ buster main" > /etc/apt/sources.list.d/galmon.list
|
||||
echo "deb https://ota.bike/debian/ buster main" > /etc/apt/sources.list.d/galmon.list
|
||||
```
|
||||
|
||||
Install the file used to ensure the software is verified.
|
||||
```sh
|
||||
apt-key adv --fetch-keys https://ota.bike/public-package-signing-keys/86E7F51C04FBAAB0.asc
|
||||
```
|
||||
|
||||
Update your package list and install galmon. Then create a configuration file and start the daemon.
|
||||
If you have a typical device using the onboard USB at /dev/ttyACM0, drop the directory element
|
||||
and refer to ttyACM0 in both the default variable file and the unit name.
|
||||
```sh
|
||||
apt-get update && apt-get install -y galmon
|
||||
cp /etc/default/galmon /etc/default/ubxtool-ttyACM0
|
||||
systemctl enable --now ubxtool@ttyACM0
|
||||
```
|
||||
|
||||
Alternate or multiple devices just repeats that, updating the device name:
|
||||
```sh
|
||||
cp /etc/default/galmon /etc/default/ubxtool-ttyACM3
|
||||
systemctl enable --now ubxtool@ttyACM3
|
||||
```
|
||||
Both the ubxtool-ttyXYZn file and /dev/ttyXYZn device must exist for the unit file conditions to pass.
|
||||
|
||||
### Automatic Updates
|
||||
|
||||
Armbian and Raspbian have apt-daily timers enabled by default.
|
||||
However, most configurations for unattended installs require customization.
|
||||
|
||||
A simple timer is included that will apply any galmon upgrades every three days:
|
||||
```sh
|
||||
systemctl enable --now galmon-upgrade.timer
|
||||
```
|
||||
|
||||
You can perform an immediate update by hand:
|
||||
```sh
|
||||
apt-get update && apt-get -y install galmon && systemctl restart ubxtool@*
|
||||
```
|
||||
|
||||
## Reference Information
|
||||
|
||||
You can stop reading here if your interest was limited to installing a compiled package.
|
||||
|
||||
### One time steps for bootstrapping package build on a fresh git repo
|
||||
|
||||
Run debmake in the source directory. It tries to autocreate 90% of everything you need in the debian folder.
|
||||
Key files: copyright, changelog, control, and anything else that looks interesting to cat. Once they exist, we're done.
|
||||
Refer to the manual's [tutorial](https://www.debian.org/doc/manuals/debmake-doc/ch04.en.html).
|
||||
|
||||
### One time steps for creating package-specific files and scripts
|
||||
|
||||
Inside the debian directory are files that begin with galmon, the name of the package as defined in the control file.
|
||||
- galmon.postinst: this script is run after installation to verify a system account exists.
|
||||
- galmon.default: this file is installed as /etc/default/galmon
|
||||
- galmon.ubxtool@.service: this unit file uses %i as a reference to the device for computers with multiple inputs.
|
||||
|
||||
### How to build the package locally
|
||||
|
||||
In short you need to set some variables, refer to profile-debuild.sh in the debian/ directory.
|
||||
After that, use debuild to install the package. Signing of the end result may fail and creating
|
||||
GPG key pairs is beyond the scope of this document but just make sure you match the email in the changelog.
|
||||
```sh
|
||||
apt-get install -y build-essential devscripts lintian diffutils patch patchutils
|
||||
git clone $flags galmon.git ; cd galmon
|
||||
./make-githash.h
|
||||
# create and source variables in /etc/profile.d/debuild.sh
|
||||
debuild
|
||||
dpkg -i ../*.deb
|
||||
```
|
||||
|
||||
### Future maintenance considerations
|
||||
|
||||
The githash.h files cannot change after the debuild process has started.
|
||||
For now, the Makefile used by debuild does not run that script and
|
||||
the files must be created before starting the debuild process.
|
||||
|
||||
### Real World Build Results in January 2020
|
||||
|
||||
Avoid compiling on arm6 computers, it is slow. The arm6 used in cheap Raspberry Pi models is an expensive model to support
|
||||
relative to the much faster arm7 and arm8 computers available. Compiling approaches 90 minutes at O3.
|
||||
|
||||
The arm7 and arm8 both compile in 20 to 30 minutes at -j1 -O3 but the 64 bit arm8 has approximately
|
||||
double the RAM requirements during compilation. To avoid swapping, increasing compile time 150%,
|
||||
use hardware with at least 1GB of RAM. The NanoPi Neo2 and NanoPi ZeroPi models with 512MB of RAM
|
||||
are perfect clients, but the OrangePi PCs with 1GB of RAM and the Allwinner H3 or H5 are
|
||||
better suited for smooth building. For comparison, a VM on a low-end AMD Ryzen 3 2200G builds the package at -j1 in about two minutes.
|
||||
|
||||
These are fast multi-core computers but we turn off parallel compiles because of limited RAM.
|
||||
Limiting optimizations to -O0 cuts the compile time in half approximately.
|
||||
|
||||
### Why do this?
|
||||
|
||||
Convenience, uniformity, and scalability:
|
||||
Hand-compiling software is fun, but vendor package management solutions
|
||||
exist to give us reliable unattended installations for free.
|
||||
|
||||
### Signing key
|
||||
|
||||
GPG Public Key [86E7F51C04FBAAB0](debian/86E7F51C04FBAAB0.asc)
|
||||
402
README.md
|
|
@ -1,62 +1,22 @@
|
|||
# galmon
|
||||
galileo/GPS/GLONASS/BeiDou open source monitoring. GPL3 licensed.
|
||||
(C) AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
(C) AHU Holding BV - bert@hubertnet.nl - https://ds9a.nl/
|
||||
|
||||
Live website: https://galmon.eu/
|
||||
|
||||
Multi-vendor, with support for U-blox 8 and 9 chipsets and many Septentrio
|
||||
devices. Navilock NL-8012U receiver works really well, as does the U-blox
|
||||
evaluation kit for the 8MT. In addition, many stations have reported
|
||||
success with this very cheap [AliExpress sourced
|
||||
device](https://www.aliexpress.com/item/32816656706.html).
|
||||
Theoretically multi-vendor, although currently only the U-blox 8 chipset is
|
||||
supported. Navilock NL-8012U receiver works really well, as does the U-blox evaluation kit for the 8MT.
|
||||
|
||||
For ublox, there is good support for the F9P, several of us use the
|
||||
[ArdusimpleRTK2B](https://www.ardusimple.com/simplertk2b/) board. It adds
|
||||
the Galileo E5b band.
|
||||
|
||||
Septentrio devices support even more bands.
|
||||
|
||||
An annotated presentation about our project aimed at GNSS professionals can
|
||||
be found [here](https://berthub.eu/galileo/The%20galmon.eu%20project.pdf).
|
||||
|
||||
> NOTE: One of our programs is called 'ubxtool'. Sadly, we did not do our
|
||||
> research, and there is another '[ubxtool](https://gpsd.io/ubxtool.html)' already, part of
|
||||
> [gpsd](https://gpsd.io). You might have ended up on our page by mistake.
|
||||
> Sorry!
|
||||
|
||||
To deliver data to the project, please read
|
||||
[The Galmon GNSS Monitoring Project](https://berthub.eu/articles/posts/galmon-project/)
|
||||
and consult the rules outlined in [the operator
|
||||
guidelines](https://github.com/berthubert/galmon/blob/master/Operator.md).
|
||||
|
||||
Highlights
|
||||
----------
|
||||
|
||||
* Support for Septentrio and U-blox.
|
||||
Highlights:
|
||||
* Processes raw frames/strings/words from GPS, GLONASS, BeiDou and Galileo
|
||||
* All-band support (E1, E5a, E5b, B1I, B2I, Glonass L1, Glonass L2, GPS L1C/A)
|
||||
so far.
|
||||
* Calculate ephemeris positions
|
||||
* Comparison of ephemerides to independent SP3 data to determine SISE
|
||||
* Globally, locally, worst user location
|
||||
* Record discontinuities between subsequent ephemerides (in time and space)
|
||||
* Record discontinuities between subsequent ephemerides
|
||||
* Compare doppler shift as reported by receiver with that expected from ephemeris
|
||||
* Track atomic clock & report jumps
|
||||
* Coverage maps (number of satellites >5, >10, >20 elevation)
|
||||
* HDOP/VDOP/PDOP maps
|
||||
* Compare orbit to TLE, match up to best matching satellite
|
||||
* Tear out every bit that tells us how well an SV is doing
|
||||
* Full almanac processing to see what _should_ be transmitting
|
||||
* Distributed receivers, combined into a single source of all messages
|
||||
* Ready to detect/report spoofing/jamming
|
||||
|
||||
Data is made available as JSON, as a user-friendly website and as a
|
||||
time-series database. This time-series database is easily mated to the
|
||||
industry standard Matplotlib/Pandas/Jupyter combination (details
|
||||
[here](https://github.com/berthubert/galmon/blob/master/influxdb.md).
|
||||
|
||||
There is also tooling to extract raw frames/strings/words from specific
|
||||
timeframes.
|
||||
|
||||
Goals:
|
||||
|
||||
|
|
@ -70,75 +30,30 @@ Goals:
|
|||
5) Populate an InfluxDB timeseries database with raw measurements and higher
|
||||
order calculations
|
||||
|
||||
Works on Linux (including Raspbian Buster on Pi Zero W), OSX and OpenBSD.
|
||||
Works on Linux (including Raspbian on Pi Zero W), OSX and OpenBSD.
|
||||
|
||||
Build locally (Linux, Debian, Ubuntu)
|
||||
-------------------------------------
|
||||
To get started, make sure you have a C++17 compiler, git, protobuf-compiler.
|
||||
Then run 'make ubxtool navdump' to build the receiver-only tools.
|
||||
|
||||
To get started, make sure you have a C++17 compiler (like g++ 8 or higher),
|
||||
git, protobuf-compiler. Then run 'make ubxtool navdump' to build the
|
||||
receiver-only tools.
|
||||
To also run the webserver locally, intall libh2o-dev and run 'make'.
|
||||
|
||||
To build everything, including the webserver, try:
|
||||
To build everything, try:
|
||||
|
||||
```
|
||||
apt-get install protobuf-compiler libcurl4-openssl-dev libssl-dev libprotobuf-dev \
|
||||
libncurses5-dev libeigen3-dev libzstd-dev g++ libfmt-dev
|
||||
git clone https://github.com/berthubert/galmon.git --recursive
|
||||
apt-get install protobuf-compiler libh2o-dev libcurl4-openssl-dev libssl-dev libprotobuf-dev libh2o-evloop-dev libwslay-dev
|
||||
git clone https://github.com/ahupowerdns/galmon.git --recursive
|
||||
cd galmon
|
||||
make
|
||||
```
|
||||
|
||||
Building on OSX
|
||||
---------------
|
||||
With thanks to a contributor from Prague. First make sure you've installed
|
||||
brew, which you can get [here](https://brew.sh/). Then do:
|
||||
If this doesn't succeed with an error about h2o, make sure you have this
|
||||
library installed. If you get an error about 'wslay', do the following, and run make again:
|
||||
|
||||
```
|
||||
brew install protobuf lzlib zstd eigen
|
||||
echo WSLAY=-lwslay > Makefile.local
|
||||
```
|
||||
|
||||
And then:
|
||||
```
|
||||
git clone https://github.com/berthubert/galmon.git --recursive
|
||||
cd galmon
|
||||
make
|
||||
```
|
||||
|
||||
Running in Docker
|
||||
-----------------
|
||||
|
||||
We publish official Docker images for galmon on
|
||||
[docker hub](https://hub.docker.com/r/berthubert/galmon) for multiple architectures.
|
||||
|
||||
To run a container with a shell in there (this will also expose a port so you
|
||||
can view the UI too and assumes a ublox GPS device too -
|
||||
you may need to tweak as necessary):
|
||||
|
||||
```
|
||||
docker run -it --rm --device=/dev/ttyACM0 -p 10000:10000 berthubert/galmon
|
||||
```
|
||||
|
||||
Running a daemonized docker container reporting data to a remote server
|
||||
might look like:
|
||||
|
||||
```
|
||||
docker run -d --restart=always --device=/dev/ttyACM0 --name=galmon berthubert/galmon ubxtool --wait --port /dev/ttyACM0 --gps --galileo --glonass --destination [server] --station [station-id] --owner [owner]
|
||||
```
|
||||
|
||||
To make your docker container update automatically you could use a tool such as
|
||||
[watchtower](https://containrrr.github.io/watchtower/).
|
||||
|
||||
Running
|
||||
-------
|
||||
On u-blox:
|
||||
Once compiled, run for example `./ubxtool --wait --port /dev/ttyACM0
|
||||
--station 1 --stdout --galileo | ./navparse --bind [::1]:10000`
|
||||
|
||||
For Septentrio, try: `nc 192.168.1.1 29000 | ./septool --station x --stdout |
|
||||
./navparse --bind [::1]:10000`, assuming your Septentrio can be reached on
|
||||
192.168.1.1.1 and you have defined an SBF stream on port 29000. For more
|
||||
details, please see below.
|
||||
Once compiled, run for example `./ubxtool --wait /dev/ttyACM0 1 | ./ubxparse 10000 html null`
|
||||
|
||||
Next up, browse to http://[::1]:10000 (or try http://localhost:10000/ and
|
||||
you should be in business. ubxtool changes (non-permanently) the
|
||||
|
|
@ -146,41 +61,20 @@ configuration of your u-blox receiver so it emits the required frames for
|
|||
GPS and Galileo. If you have a u-blox timing receiver it will also enable
|
||||
the doppler frames.
|
||||
|
||||
By default the ublox receiver module will be configured to use the USB port,
|
||||
if you want to use a different interface port on the ublox module then add
|
||||
the `--ubxport <id>` option using one of the following numeric IDs:
|
||||
|
||||
0 : DDC (aka. I2C)
|
||||
1 : UART[1]
|
||||
2 : UART2
|
||||
3 : USB (default)
|
||||
4 : SPI
|
||||
|
||||
To see what is going on, try:
|
||||
|
||||
```
|
||||
./ubxtool --wait --port /dev/ttyACM0 --station 1 --stdout --galileo | ./navdump
|
||||
./ubxtool --wait /dev/ttyACM0 1 | ./navdump
|
||||
```
|
||||
|
||||
To distribute data to a remote `navrecv`, use:
|
||||
|
||||
```
|
||||
./ubxtool --wait --port /dev/ttyACM0 --galileo --station 255 --destination 127.0.0.1
|
||||
```
|
||||
|
||||
This will send protobuf to 127.0.0.1:29603. You can add as many destinations
|
||||
as you want, they will buffer and automatically reconnect. To also send data
|
||||
to stdout, add `--stdout`.
|
||||
Setting up a distributed setup is slightly more complicated & may still
|
||||
change.
|
||||
|
||||
Tooling:
|
||||
|
||||
* ubxtool: can configure a u-blox 8/9/10 chipset, parses its output & will
|
||||
* ubxtool: can configure a u-blox 8 chipset, parses its output & will
|
||||
convert it into a protbuf stream of GNSS NAV frames + metadata
|
||||
Adds 64-bit timestamps plus origin information to each message
|
||||
* septool: ingests the Septentrio binary format (SBF) and converts it to our
|
||||
protobuf format. Supports same protocol as ubxtool.
|
||||
* rtcmtool: ingest ntripclient output, decodes RTCM messages and converts
|
||||
them to our protobuf format
|
||||
* xtool: if you have another chipset, build something that extracts NAV
|
||||
frames & metadata. Not done yet.
|
||||
* navrecv: receives GNSS NAV frames and stores them on disk, split out per
|
||||
|
|
@ -193,109 +87,17 @@ Tooling:
|
|||
computations on ephemerides.
|
||||
* grafana dashboard: makes pretty graphs
|
||||
|
||||
Per device notes
|
||||
----------------
|
||||
The "SparkFun GNSS L1/L5 Breakout - NEO-F10N, SMA" needs '-u1 -b 38400
|
||||
--wait'.
|
||||
|
||||
Linux Systemd
|
||||
-------------
|
||||
First make sure 'ubxtool' has been compiled (run: make ubxtool). Then, as
|
||||
root:
|
||||
```
|
||||
mkdir /usr/local/ubxtool
|
||||
cp ubxtool ubxtool.sh /usr/local/ubxtool/
|
||||
cp ubxtool.service /etc/systemd/system/
|
||||
```
|
||||
|
||||
Then please reach out as indicated in [Operator.md](Operator.md) to obtain your
|
||||
station ID and the receiver hostname and run:
|
||||
|
||||
```
|
||||
echo RECEIVER-NAME > /usr/local/ubxtool/destination
|
||||
echo STATION-NUMBER > /usr/local/ubxtool/station
|
||||
```
|
||||
|
||||
Then start up the service (as root):
|
||||
```
|
||||
systemctl enable ubxtool
|
||||
systemctl start ubxtool
|
||||
```
|
||||
|
||||
To check if it is all working, do 'service ubxtool status'.
|
||||
|
||||
> NOTE! If you don't use one of the AliExpress or Navilock devices, it may
|
||||
> be that your U-blox is not connected to the USB-port of the U-blox chip
|
||||
> but to the UART1 or UART2 port. If so, you'll need to edit the script so
|
||||
> it finds your USB-to-serial adapter. At the very least you'll have to
|
||||
> update the DEVICE line. You'll likely also have to add --ubxport 1 at the
|
||||
> end, and likely also the baudrate (-b) and/or --rtscts=0.
|
||||
|
||||
To change the default constellations, create a file called
|
||||
/usr/local/ubxtool/constellations and set your favorites. To set all four
|
||||
constellations (which only F9-receivers support), do as root:
|
||||
|
||||
```
|
||||
echo --gps --glonass --beidou --galileo > /usr/local/ubxtool/constellations
|
||||
```
|
||||
|
||||
And then 'service ubxtool restart'.
|
||||
|
||||
Distributed setup
|
||||
-----------------
|
||||
Run `navrecv -b :: --storage ./storage` to receive frames on port 29603 of
|
||||
::, aka all your IPv6 addresses (and IPv4 too on Linux). This allows anyone
|
||||
to send you frames, so be aware.
|
||||
Run `navrecv :: ./storage` to receive frames on port 29603 of ::, aka all your IPv6 addresses (and IPv4 too on Linux).
|
||||
This allows anyone to send you frames, so be aware.
|
||||
|
||||
Next up, run `navnexus --storage ./storage -b ::`, which will serve your
|
||||
recorded data from port 29601. It will merge messages coming in from all
|
||||
sources and serve them in time order.
|
||||
Next up, run `navnexus ./storage ::`, which will serve your recorded data from port 29601. It will merge messages
|
||||
coming in from all sources and serve them in time order.
|
||||
|
||||
Finally, you can do `nc 127.0.0.1 29601 | ./navdump`, which will give you all messages over the past 24 hours, and stream you more.
|
||||
This also works for `navparse` for the pretty website and influx storage, `nc 127.0.0.1 29601 | ./navparse --influxdb=galileo`,
|
||||
Finally, you can do `nv 127.0.0.1 29601 | ./navdump`, which will give you all messages over the past 24 hours, and stream you more.
|
||||
This also works for `navparse` for the pretty website and influx storage, `nc 127.0.0.1 29601 | ./navparse 127.0.0.0:10000 html galileo`,
|
||||
if you have an influxdb running on localhost with a galileo database in there.
|
||||
The default URL is http://127.0.0.1:29599/
|
||||
|
||||
Septentrio specifics
|
||||
--------------------
|
||||
Unlike `ubxtool`, our `septool` does not (re)configure your Septentrio
|
||||
device. Instead, the tool expects Septentrio Binary Format (SBF) on input,
|
||||
and that this stream includes at least the following messages:
|
||||
|
||||
* MeasEpoch
|
||||
* PVTCartesian
|
||||
|
||||
We currently parse and understand:
|
||||
* GALRawFNAV
|
||||
* GALRawINAV
|
||||
|
||||
Support will be added soon for:
|
||||
* GPSRawCA
|
||||
* GPSRawL2C
|
||||
* GPSRawL5
|
||||
* GLORawCA
|
||||
* BDSRaw
|
||||
* BDSRawB1C
|
||||
* BDSRawB2a
|
||||
|
||||
A typical invocation of `septool` looks like this:
|
||||
|
||||
```
|
||||
nc 192.168.1.1 29000 | ./septool --station x --destination galmon-eu-server.example.com
|
||||
```
|
||||
|
||||
Or to test, try:
|
||||
|
||||
```
|
||||
nc 192.168.1.1 29000 | ./septool --station x --stdout | ./navdump
|
||||
```
|
||||
|
||||
This is assuming that you can reach your Septentrio on 192.168.1.1 and that
|
||||
you have defined a TCP server stream on port 29000.
|
||||
|
||||
Septool will also accept input from a serial port or basically anything that
|
||||
can provide SBF. Please let us know if our tooling can make your life
|
||||
easier.
|
||||
|
||||
Internals
|
||||
---------
|
||||
|
|
@ -321,146 +123,26 @@ Documents
|
|||
* [GLONASS CDMA](http://russianspacesystems.ru/wp-content/uploads/2016/08/ICD-GLONASS-CDMA-General.-Edition-1.0-2016.pdf)
|
||||
not actually relevant for the CDMA aspects, but has appendices on more
|
||||
precise orbit determinations.
|
||||
* [GPS](https://www.navcen.uscg.gov/sites/default/files/pdf/gps/IS-GPS-200N.pdf)
|
||||
* [U-blox 8 interface specification](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdf)
|
||||
* [GPS](https://www.gps.gov/technical/icwg/IS-GPS-200K.pdf)
|
||||
* [U-blox 8 interface specification](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdfhttps://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29_Public.pdf)
|
||||
* [U-blox 9 interface specification](https://www.u-blox.com/sites/default/files/u-blox_ZED-F9P_InterfaceDescription_%28UBX-18010854%29.pdf)
|
||||
* [U-blox 10 interface specification](https://content.u-blox.com/sites/default/files/documents/u-blox-F10-SPG-6.00_InterfaceDescription_UBX-23002975.pdf)
|
||||
* [U-blox 9 integration manual](https://www.u-blox.com/sites/default/files/ZED-F9P_IntegrationManual_%28UBX-18010802%29.pdf)
|
||||
|
||||
Data sources
|
||||
------------
|
||||
The software can interpret SP3 files, good sources:
|
||||
|
||||
* ESA/ESOC: http://navigation-office.esa.int/products/gnss-products/ - pick the
|
||||
relevant GPS week number, and then a series (.sp3 extension):
|
||||
* ESU = ultra rapid, 2-8h delay, only GPS and GLONASS
|
||||
* ESR = rapid, 2-26h delay, only GPS and GLONASS
|
||||
* ESM = finals, 6-13d delay, GPS, GLONASS, Galileo, BeiDou, QZSS
|
||||
* File format is esXWWWWD.sp3 - where X is U, R or M, WWWW is the
|
||||
(non-wrapping) GPS week number and D is day of week, Sunday is 0.
|
||||
* Further description: http://navigation-office.esa.int/GNSS_based_products.html
|
||||
* GFZ Potsdam: ftp://ftp.gfz-potsdam.de/GNSS/products/mgnss
|
||||
* The GBM series covers GPS, GLONASS, Galileo, BeiDou, QZSS and appears
|
||||
to have less of a delay than the ESA ESM series.
|
||||
* GBU = ultra rapid, still a few days delay, but much more recent.
|
||||
Big TODO
|
||||
--------
|
||||
|
||||
To get SP3 GBM from GFZ Potsdam for GPS week number 2111:
|
||||
|
||||
```
|
||||
WN=2111
|
||||
lftp -c "mget ftp://ftp.gfz-potsdam.de/GNSS/products/mgnss/${WN}/gbm*sp3.Z"
|
||||
gunzip gbm*sp3.Z
|
||||
```
|
||||
|
||||
To feed data, use:
|
||||
|
||||
```
|
||||
./sp3feed --sp3src=gbm --influxdb=galileo gbm*sp3
|
||||
```
|
||||
|
||||
This will populate the sp3 tables in the database. A subsequent run of
|
||||
`reporter`, while setting the `--sp3src` parameter, will provide SP3
|
||||
deviation statistics, and fill out the `sp3delta` table in there, which
|
||||
stores deviations from the SP3 provided position, per sp3src.
|
||||
|
||||
Further interesting (ephemeris) data is on http://mgex.igs.org/IGS_MGEX_Products.php
|
||||
|
||||
RTCM
|
||||
----
|
||||
RTCM is the Radio Technical Commission for Maritime Services, and
|
||||
confusingly, also the name of a protocol.
|
||||
|
||||
This protocol is proprietary, but search for a file called `RTCM3.2.pdf` or
|
||||
`104-2013-SC104-STD - Vers. 3.2.docx` and you might find a copy.
|
||||
|
||||
This project can parse RTCM 10403.1 messages, and currently processes State
|
||||
Space Representation (SSR) messages, specifically types 1057/1240
|
||||
(GPS/Galileo Orbit corrections to broadcast ephemeris) and 1058/1241
|
||||
(GPS/Galileo Clock corrections to broadcast ephemeris).
|
||||
|
||||
RTCM messages need to be converted to protobuf format, and the `rtcmtool` is
|
||||
provided for this purpose.
|
||||
|
||||
RTCM is frequently transmitted over the internet using 'ntrip', a typical
|
||||
commandline to process RTCM in our project is:
|
||||
```
|
||||
$ ntripclient ntrip:CLKA0_DEU1/user:password@navcast.spaceopal.com:2101 | ./rtcmtool --station x --destination y
|
||||
```
|
||||
|
||||
User and password can be obtained from https://spaceopal.com/navcast/ - the
|
||||
Galileo operating company.
|
||||
|
||||
The IGS also offers excellent streams, but without Galileo. Information is
|
||||
[here](http://www.igs.org/rts/products). A typical commandline is:
|
||||
|
||||
```
|
||||
$ ntripclient ntrip:IGS01/user:password@products.igs-ip.net:2101 | ./rtcmtool --station x --destination y
|
||||
```
|
||||
|
||||
User and password can be requested through http://www.igs.org/rts/access
|
||||
|
||||
An interesting list is here: http://products.igs-ip.net/
|
||||
|
||||
There are many other sources of RTCM but currently not many offer the SSR
|
||||
messages we can use.
|
||||
|
||||
Tooling
|
||||
-------
|
||||
|
||||
* ubxtool: Configure and use a Ublox chip to gather messages, and send as
|
||||
protobuf to standard output or a remote server (with buffering).
|
||||
* navdump: convert protobuf format data into a textual display of messages
|
||||
* navparse: consume protobuf and turn into a webserver with data, plus
|
||||
optionally fill an influxdb time-series database for graphing and analysis
|
||||
purposes.
|
||||
* navrecv: receive protobuf messages over the network and store them on
|
||||
disk
|
||||
* navnexus: serve protobuf messages from disk over the network
|
||||
* navcat: serve protobuf messages from disk directly to stdout
|
||||
* reporter: make "the galmon.eu weekly galileo report"
|
||||
* rinreport: rinex analysis tooling (not generically useful yet)
|
||||
* galmonmon: monitor a navparse instance for changes, tweet them out
|
||||
* navdisplay: some eye-candy that converts protobuf into a live display
|
||||
(not very good)
|
||||
* rtcmtool: accepts RTCM messages on standard input (for example coming
|
||||
from ntripclient) and transmits them as protobuf messages, either to
|
||||
stdout or to a navrecv server. This is the equivalent of 'ubxtool'
|
||||
except for submitting RTCM messages.
|
||||
|
||||
Sample command lines
|
||||
--------------------
|
||||
Look at old data:
|
||||
|
||||
```
|
||||
$ ./navcat storage "2020-01-01 00:00" "2020-01-02 00:00" | ./navdump
|
||||
```
|
||||
|
||||
Global coverage (via volunteers)
|
||||
--------------------------------
|
||||
|
||||
In alphabetical order:
|
||||
|
||||
* Austria (Vienna area)
|
||||
* Brazil
|
||||
* Holland (Nootdorp, Hilversum, etc)
|
||||
* India (New Delhi area)
|
||||
* Israel (Jerusalem)
|
||||
* Italy (Rome)
|
||||
* New Zealand (Auckland area)
|
||||
* Rusia (Moscow area)
|
||||
* Singapore
|
||||
* South Africa (Cape Town area)
|
||||
* Spain
|
||||
* Tonga
|
||||
* USA
|
||||
* Alaska (Anchorage)
|
||||
* California (Santa Cruz, Los Angeles area, etc)
|
||||
* Massachusetts (Boston area)
|
||||
* Uruguay
|
||||
|
||||
Additional sites are welcome (and encouraged) as the more data receiving sites that exist, then more accurate data and absolute coverage of each constellation can be had.
|
||||
|
||||
The galmon project is very grateful to all its volunteering receiving stations.
|
||||
* Dual goals: completeness, liveness, not the same
|
||||
For forensics, great if the packet is there
|
||||
For display, not that bad if we missed a message
|
||||
* In general, consider refeed strategy
|
||||
Raw serial
|
||||
Protobuf
|
||||
Influxdb
|
||||
".csv files"
|
||||
* Delivery needs to be bit more stateful (queue)
|
||||
|
||||
* Semantics definition for output of Navnexus
|
||||
"we'll never surprise you with old data"
|
||||
|
||||
ubxtool
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
Architecture
|
||||
------------
|
||||
|
||||
The galmon project consists of several components. Core of what we do are
|
||||
protobuf messages that are (mostly) device independent and contain satellite
|
||||
data and metadata.
|
||||
|
||||
All protobuf messages are 'NavMonMessages', of which there are 15 types.
|
||||
These messages are:
|
||||
|
||||
* Navigation messages from satellites, various types
|
||||
* Device / software metadata
|
||||
* Reception strength details
|
||||
* Doppler, carrier phase data
|
||||
* RTCM messages
|
||||
|
||||
For every supported receiver type, there is a tool to convert the device
|
||||
specific protocol to these protobuf messages. Currently we only have
|
||||
'ubxtool' that does this for many u-blox devices. In addition, 'rtcmtool'
|
||||
converts a RTCM to protobuf.
|
||||
|
||||
Components
|
||||
----------
|
||||
|
||||
Core components:
|
||||
* ubxtool/rtcmtool: generate protobuf messages, transmit them over network
|
||||
* navrecv: receive protobuf messages over the network and store them on
|
||||
disk
|
||||
* navnexus: serve protobuf messages from disk over the network
|
||||
* navparse: consume protobuf and turn into a webserver with data, plus
|
||||
optionally fill an influxdb time-series database for graphing and analysis
|
||||
purposes.
|
||||
|
||||
This offers a complete suite for:
|
||||
|
||||
* generating protobuf messages and transmitting them
|
||||
* reception & long-term storage
|
||||
* serving protobuf messages
|
||||
* analysing them for display & storing statistics for analysis
|
||||
|
||||
|
||||
Non-core tools:
|
||||
* navdump: convert protobuf format data into a textual display of messages
|
||||
* navcat: serve protobuf messages from disk directly to stdout
|
||||
* reporter: make "the galmon.eu weekly galileo report", based on the
|
||||
time-series database filled by navparse
|
||||
* rinreport: rinex analysis tooling (not generically useful yet)
|
||||
* galmonmon: monitor a navparse instance for changes, tweet them out
|
||||
* navdisplay: some eye-candy that converts protobuf into a live display
|
||||
(not very good)
|
||||
|
||||
Transmission details
|
||||
--------------------
|
||||
Messages are sent as protobuf messages with an intervening magic value and a
|
||||
length field.
|
||||
|
||||
Both rtcmtool and ubxtool can send data over TCP/IP, but also to standard
|
||||
output. This standard output mode makes it possible to pipe the output of
|
||||
these tools straight into navdump or navparse.
|
||||
|
||||
There are two transport protocols, one uncompressed, which consists of the 4
|
||||
byte magic value, a 2 byte length field, and then the message. This protocol
|
||||
has been deprecated for TCP, but it is still there for --stdout output.
|
||||
|
||||
The second protocol is zstd compressed, and features acknowledgements. The
|
||||
protocol is an initial 4 byte magic value "RNIE", followed by 8 reserved
|
||||
bytes which are currently ignored. Then zstd compression starts, each
|
||||
message consists of a 4 byte message number, followed by a two byte length
|
||||
field, followed by the message. There are no further magic values beyond the
|
||||
first one.
|
||||
|
||||
The receiver is expected to return the 4 byte message number for each
|
||||
message received. Messages which have not been acknowledged like this will
|
||||
be retransmitted on reconnect.
|
||||
|
||||
Storage details
|
||||
---------------
|
||||
Storage is very simplistic but robust. For every receiver, for every hour,
|
||||
there is a file with protobuf messages. That's it. The goal is for the
|
||||
receiver to never ever go down.
|
||||
|
||||
navnexus and navcat can consume data from this simplistic storage and serve
|
||||
it over TCP/IP or to standard output.
|
||||
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
#include "beidou.hh"
|
||||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "navmon.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
|
@ -31,7 +29,7 @@ static int checkbds(int bits)
|
|||
return 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& payload)
|
||||
std::basic_string<uint8_t> getCondensedBeidouMessage(std::basic_string_view<uint8_t> payload)
|
||||
{
|
||||
|
||||
// payload consists of 32 bit words where we have to ignore the first 2 bits of every word
|
||||
|
|
@ -58,7 +56,7 @@ std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& paylo
|
|||
setbitu(buffer, 26+22*(w-1), 22, getbitu(&payload[0], 2 + w*32, 22));
|
||||
}
|
||||
|
||||
return makeVec(buffer, 28);
|
||||
return std::basic_string<uint8_t>(buffer, 28);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
33
beidou.hh
|
|
@ -4,10 +4,9 @@
|
|||
#include "bits.hh"
|
||||
#include <math.h>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include "ephemeris.hh"
|
||||
|
||||
std::vector<uint8_t> getCondensedBeidouMessage(const std::vector<uint8_t>& payload);
|
||||
std::basic_string<uint8_t> getCondensedBeidouMessage(std::basic_string_view<uint8_t> payload);
|
||||
int beidouBitconv(int their);
|
||||
|
||||
/* Geostationary, so D2, so not to be parsed by this parser:
|
||||
|
|
@ -18,11 +17,11 @@ int beidouBitconv(int their);
|
|||
C05 (58.75E)
|
||||
*/
|
||||
|
||||
struct BeidouMessage : GPSLikeEphemeris
|
||||
struct BeidouMessage
|
||||
{
|
||||
uint8_t strtype;
|
||||
|
||||
std::vector<uint8_t> g_cond;
|
||||
std::basic_string_view<uint8_t> g_cond;
|
||||
int bbitu(int bit, int len)
|
||||
{
|
||||
return getbitu(&g_cond[0], beidouBitconv(bit), len);
|
||||
|
|
@ -35,7 +34,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
|
||||
int fraid{-1}, sow{-1}; // part of every message (thanks!)
|
||||
|
||||
int parse(const std::vector<uint8_t>& cond, uint8_t* pageno)
|
||||
int parse(std::basic_string_view<uint8_t> cond, uint8_t* pageno)
|
||||
{
|
||||
g_cond = cond;
|
||||
if(pageno)
|
||||
|
|
@ -73,7 +72,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
int wn{-1}, a0, a1, a2;
|
||||
// 2^17 2^30
|
||||
|
||||
uint32_t getT0c() const
|
||||
uint32_t getT0c()
|
||||
{
|
||||
return 8 * t0c;
|
||||
}
|
||||
|
|
@ -90,7 +89,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
return {factor * cur, factor * trend};
|
||||
}
|
||||
|
||||
void parse1(const std::vector<uint8_t>& cond)
|
||||
void parse1(std::basic_string_view<uint8_t> cond)
|
||||
{
|
||||
sath1 = bbitu(43,1);
|
||||
aodc = bbitu(31+13, 5);
|
||||
|
|
@ -119,13 +118,9 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
double getCrc() const { return ldexp(crc, -6); } // meters
|
||||
double getCrs() const { return ldexp(crs, -6); } // meters
|
||||
double getM0() const { return ldexp(m0 * M_PI, -31); } // radians
|
||||
|
||||
int getIOD() const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
void parse2(const std::vector<uint8_t>& cond)
|
||||
|
||||
void parse2(std::basic_string_view<uint8_t> cond)
|
||||
{
|
||||
deltan = bbits(43, 16);
|
||||
cuc = bbits(67, 18);
|
||||
|
|
@ -152,7 +147,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
double getOmega0() const { return ldexp(Omega0 * M_PI, -31); } // radians
|
||||
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
|
||||
double getOmega() const { return ldexp(omega * M_PI, -31); } // radians
|
||||
void parse3(const std::vector<uint8_t>& cond)
|
||||
void parse3(std::basic_string_view<uint8_t> cond)
|
||||
{
|
||||
t0eLSB = bbitu(43, 15);
|
||||
i0 = bbits(66, 32);
|
||||
|
|
@ -208,7 +203,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
} alma;
|
||||
|
||||
// 4 is all almanac
|
||||
int parse4(const std::vector<uint8_t>& cond)
|
||||
int parse4(std::basic_string_view<uint8_t> cond)
|
||||
{
|
||||
alma.sqrtA = bbitu(51, 24);
|
||||
alma.a1 = bbits(91, 11);
|
||||
|
|
@ -229,8 +224,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
|
||||
// 2^-30 2^-50
|
||||
int a0gps, a1gps, a0gal, a1gal, a0glo, a1glo, a0utc, a1utc;
|
||||
int8_t deltaTLS, deltaTLSF;
|
||||
uint8_t wnLSF, dn;
|
||||
int8_t deltaTLS;
|
||||
|
||||
// in Beidou the offset is a0utc + SOW * a1utc
|
||||
std::pair<double, double> getUTCOffset(int tow) const
|
||||
|
|
@ -255,7 +249,7 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
}
|
||||
|
||||
|
||||
int parse5(const std::vector<uint8_t>& cond)
|
||||
int parse5(std::basic_string_view<uint8_t> cond)
|
||||
{
|
||||
alma.pageno = bbitu(44, 7);
|
||||
if(alma.pageno == 9) {
|
||||
|
|
@ -270,9 +264,6 @@ struct BeidouMessage : GPSLikeEphemeris
|
|||
a0utc = bbits(91, 32);
|
||||
a1utc = bbits(131, 24);
|
||||
deltaTLS = bbits(31+12+1+7, 8);
|
||||
deltaTLSF = bbits(61+6, 8);
|
||||
wnLSF = bbits(61+6+8, 8);
|
||||
dn = bbits(151+12, 8);
|
||||
}
|
||||
else {
|
||||
alma.sqrtA = bbitu(51, 24);
|
||||
|
|
|
|||
143
coverage.cc
|
|
@ -1,143 +0,0 @@
|
|||
#include <map>
|
||||
#include "galileo.hh"
|
||||
#include "minivec.hh"
|
||||
#include "navmon.hh"
|
||||
#include "navparse.hh"
|
||||
#include "ephemeris.hh"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/printf.h"
|
||||
#include <fstream>
|
||||
|
||||
#include <eigen3/Eigen/Dense>
|
||||
using Eigen::MatrixXd;
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
xDOP getDOP(Point& us, vector<Point> sats)
|
||||
{
|
||||
xDOP ret;
|
||||
if(sats.size() < 4) {
|
||||
return ret;
|
||||
}
|
||||
MatrixXd G(sats.size(), 4); // 4 columns
|
||||
|
||||
// (x1 - x)/R1 (y1 -y)/R1 (z1 - z)/R1 -1
|
||||
|
||||
for(size_t n = 0 ; n < sats.size(); ++n) {
|
||||
const auto& s = sats[n];
|
||||
auto R = Vector(us, s).length();
|
||||
|
||||
G(n, 0) = (s.x - us.x)/R;
|
||||
G(n, 1) = (s.y - us.y)/R;
|
||||
G(n, 2) = (s.z - us.z)/R;
|
||||
G(n, 3) = -1;
|
||||
}
|
||||
|
||||
// cout<<"Matrix: "<<endl;
|
||||
// cout<<G<<endl;
|
||||
MatrixXd Q = (G.transpose() * G).inverse();
|
||||
|
||||
auto [lambda, phi] = getLongLat(us.x, us.y, us.z);
|
||||
|
||||
// https://gssc.esa.int/navipedia/index.php/Positioning_Error
|
||||
Eigen::Matrix3d Renu;
|
||||
Renu <<
|
||||
(-sin(lambda)) , (-sin(phi)*cos(lambda)) , (cos(phi)*cos(lambda)),
|
||||
(cos(lambda)) , (-sin(phi)*sin(lambda)) , (cos(phi)*sin(lambda)),
|
||||
(0.0) , (cos(phi)) , (sin(phi));
|
||||
|
||||
Eigen::Matrix3d Qxyz;
|
||||
for(int x=0; x<3; ++x) // feels like there should be a better way for this, but not sure
|
||||
for(int y=0; y<3; ++y)
|
||||
Qxyz(x,y) = Q(x,y);
|
||||
|
||||
Eigen::Matrix3d Qenu = Renu.transpose()*Qxyz*Renu;
|
||||
// if(Qenu(0,0) < 0 || Qenu(1,1) < 0 || Qenu(2,2) < 0)
|
||||
// cout << "Original: \n"<<Qxyz<<"\nRotated: \n"<<Qenu<<endl;
|
||||
|
||||
ret.pdop = sqrt(Q(0,0) + Q(1,1) + Q(2,2)); // already squared
|
||||
// ret.pdop = sqrt(Qenu(0,0) + Qenu(1,1) + Qenu(2,2)); // already squared
|
||||
|
||||
ret.tdop = sqrt(Q(3,3));
|
||||
ret.gdop = sqrt(ret.pdop*ret.pdop + ret.tdop*ret.tdop);
|
||||
if(Qenu(0,0) >=0 && Qenu(1,1) >=0)
|
||||
ret.hdop = sqrt(Qenu(0,0) + Qenu(1,1));
|
||||
if(Qenu(2,2)>=0)
|
||||
ret.vdop = sqrt(Qenu(2,2));
|
||||
return ret;
|
||||
};
|
||||
|
||||
// in covmap:
|
||||
// 0
|
||||
// lon,
|
||||
// 1 2 3
|
||||
// numsats5, numsats10, numsats20
|
||||
// 4 5 6
|
||||
// pdop5, pdop10, pdop20
|
||||
// hdop5, hdop10, hdop10
|
||||
// vdop5, vdop10, vdop20
|
||||
covmap_t emitCoverage(const vector<Point>& sats)
|
||||
{
|
||||
covmap_t ret;
|
||||
// ofstream cmap("covmap.csv");
|
||||
// cmap<<"latitude longitude count5 count10 count20"<<endl;
|
||||
double R = 6371000;
|
||||
for(double latitude = 90 ; latitude > -90; latitude-=2) { // north-south
|
||||
double phi = M_PI* latitude / 180;
|
||||
double longsteps = 1 + 360.0 * cos(phi);
|
||||
double step = 4*180.0 / longsteps;
|
||||
// this does sorta equi-distanced measurements
|
||||
vector<tuple<double, int, int, int, double, double, double, double, double, double,double, double, double>> latvect;
|
||||
for(double longitude = -180; longitude < 180; longitude += step) { // east - west
|
||||
Point p;
|
||||
// phi = latitude, lambda = longitude
|
||||
|
||||
double lambda = M_PI* longitude / 180;
|
||||
p.x = R * cos(phi) * cos(lambda);
|
||||
p.y = R * cos(phi) * sin(lambda);
|
||||
p.z = R * sin(phi);
|
||||
|
||||
if(longitude == -180) {
|
||||
// auto longlat = getLongLat(p.x, p.y, p.z);
|
||||
// cout<<fmt::sprintf("%3.0f ", 180.0*longlat.second/M_PI);
|
||||
}
|
||||
|
||||
|
||||
int numsats5=0, numsats10=0, numsats20=0;
|
||||
vector<Point> satposs5, satposs10, satposs20;
|
||||
for(const auto& s : sats) {
|
||||
// double getElevationDeg(const Point& sat, const Point& our);
|
||||
double elev = getElevationDeg(s, p);
|
||||
if(elev > 5.0) {
|
||||
satposs5.push_back(s);
|
||||
numsats5++;
|
||||
}
|
||||
if(elev > 10.0) {
|
||||
satposs10.push_back(s);
|
||||
numsats10++;
|
||||
}
|
||||
if(elev > 20.0) {
|
||||
satposs20.push_back(s);
|
||||
numsats20++;
|
||||
}
|
||||
}
|
||||
latvect.push_back(make_tuple(longitude,
|
||||
numsats5, numsats10, numsats20,
|
||||
getDOP(p, satposs5).pdop,
|
||||
getDOP(p, satposs10).pdop,
|
||||
getDOP(p, satposs20).pdop,
|
||||
getDOP(p, satposs5).hdop,
|
||||
getDOP(p, satposs10).hdop,
|
||||
getDOP(p, satposs20).hdop,
|
||||
getDOP(p, satposs5).vdop,
|
||||
getDOP(p, satposs10).vdop,
|
||||
getDOP(p, satposs20).vdop
|
||||
|
||||
));
|
||||
// cmap << longitude <<" " <<latitude <<" " << numsats5 << " " <<numsats10<<" "<<numsats20<<endl;
|
||||
}
|
||||
ret.push_back(make_pair(latitude, latvect));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
87
debian/86E7F51C04FBAAB0.asc
vendored
|
|
@ -1,87 +0,0 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBF4kzeMBEAClLiZy741lfdoZHvlCbU1k4o9CglhVAPnSqTo7tH0AkdXmp/sG
|
||||
Sxk20miLlNNaxTYhHLyWIMhLoR3ogAJSoUsbMo4cpC5owdai06Q2nINTGiD1Vwak
|
||||
MmSStQl/aG01G1u9Ei/FVSr/K6Nd8u+dvUoU6tFSoXtXXaRB5pkyLOaZXq6UTwhw
|
||||
tI5f+uH2d8kgkoGNWl/jevn7K5AqCWmklfzYFCeogXemcNMdisrm2RzYzay70Qo+
|
||||
3qKHzQUN5S6Ne2yYKYN1UacGg+38+zhoSNGy8fB2XfCf8mbW2NnnZBdPEmpu1yos
|
||||
uvjj/oauZlLgkx/ZzK4PzmWqpe+aEiV7Z8lPO3Y2U/cwkU56aMIFQq2DU4FgQUdn
|
||||
6J+tLeMPh0sbIfYeOfF5IdFi85PdD4w7C8hmxu7aHl/B/GCnOFYAsSChpf4bpgWB
|
||||
G1Pne56evHeSNYXQCdE4rjyYGqB/F+EWn/NYcOdKSkOxxE5bL/aU/uqPss5E54nt
|
||||
z+qsN9ynGNWsnkPQECulM3svS3/nffnmgFSuDZyON4Y8/LFIkPizBiqOtrVEAKdK
|
||||
CqKz+A2fgSamM5IbJbCSkXWMcU6W0Sz0DzSpMbVf5kKER7WeDoeWr6yUHqAkPyHz
|
||||
bx/YLWwrAjuuN00/MuIl8XXHWBHsfRqIrqLSBbie5mbOv2MloBEdrvZqvwARAQAB
|
||||
tDRHYWxtb24gRGViaWFuIFByaW1hcnkgU2lnbmluZyBLZXkgPGRlYmlhbkBnYWxt
|
||||
b24uZXU+iQJUBBMBCAA+FiEEbe3qslh61wzGNEF6huf1HAT7qrAFAl4kzeMCGwMF
|
||||
CRaOagAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQhuf1HAT7qrCwxxAAj2fs
|
||||
9qcWKJSCPPpDNU6gzjuqFj2fUmvihf0Ki9LbBLMt5pA5q4RJLCMj/iSz31s8Cm8P
|
||||
+oWX3ZFxWzG5ZiWHWhpmgSYIPbfEwniwskLJXfZSEr+kcLoVnZsbWOxwLCmdanZl
|
||||
wWRNyFW4waRJaCf+jsPPoR5LEh5d0ustpP/2EtbP52OLY865bfr18SUhPllrX0ia
|
||||
L3zSCMC/S7H45xzOdSneYrYNA1W199hY/4lBafh7t8a07C2CFMqN/jaUJOFHQIzS
|
||||
VIsVHvpVQlpslxcPGOOZsh5tJwEIKuepHm6nqsZbKMuO6Qzi5hXfwsI3cjD+NZY8
|
||||
Hg2H0IDXfXKWD/mcxeAjgc3lzzchGZ56EOMN75Fzl/HLCBu6pYuTbOmwG9whnaYv
|
||||
VbNhDSzC+x39o4O2nMsfixOe4tx2eK04nzWKIEfYeIJZc+9xfJW16hbmErFx1VWR
|
||||
x2Pi55hbOCEeOVxnhc50rqFN+tXiLvVzZaLN7H/S7Bqk2qCA/b6HjxY/cZ+KyQSo
|
||||
gMc2Mz5/dwROBEuFryjpFzTOFRfnu8krZAaHim1TlDCNFI4+NO5xu1AkFpepYphQ
|
||||
+7qWjLPqXqHH54C79Qs5ey+q+iEh3ulK3u5yeyvL9+rjaPJOQxd3yTfcstHPpeoG
|
||||
ePCEhrpLvkfukrMhfgg8R4gtkST2LWQNrvSCnFa5Ag0EXiTN4wEQAMGv94CKgsE/
|
||||
H6V0YPxNFTfUFgZ+Rbse+DpGBUHSc2Bnp79ks9zq5zN6rRD8oztA2nyZAB39JJGq
|
||||
Y55yRRaQx/JqB1+WzSQywIdaAEt/D+PVfukfzjkNQ9/nELfBtx4W2xG5xiowzUqf
|
||||
XbWJvEmfOhKFYZrVayhVbc0DwZc1lBrO+1FX/z7NqyjgTkUusKIpjTjxl6afa79x
|
||||
8B8O8vcpwZO5I6LIVf75W5YcY0vJ0tXqYqF5/JOkWtRTGCcZQvovXsPg/LQtRz39
|
||||
61DuA0J02bCv/dUOPfwtk/PY4UzvIWmYH+xvfuJ4txX71VZVDVeSIMp7Iv9LVQL/
|
||||
pefdyIf4RTya30qHTLLJmXhQNTS7fE2EFoOfi9xlP8CE+vEnf5gxUsLQ98aoIaWu
|
||||
+DpZelPTUpEJ45RNFxizmhv+X/d1WHyFS5bU53guxDSrFOKNKilCEqO5ukxYhMSY
|
||||
va0REZQh4iwuPFO3R4D1NM3HSJa2YmgVp8tHNJBTQImYTCd+NfY/wLlGFfgrjOLh
|
||||
XkIO2mHzANiCmoPcc4yoXGpsAr8N8yVe5sOKsbUvfMpisJv500BJA33ONQU60wWk
|
||||
S1g/RVn/dzJffSC+WOgxLkLAGaA0clrmJVmD/mlYhv8P/Ptwh0zAnkZsWk+NI15T
|
||||
EW2efOEj5QBg5PdQFyxJuOQXs7B+vRF9ABEBAAGJAjwEGAEIACYWIQRt7eqyWHrX
|
||||
DMY0QXqG5/UcBPuqsAUCXiTN4wIbDAUJFo5qAAAKCRCG5/UcBPuqsAvVD/9v/91W
|
||||
QyRLH+0EU/1vJM9pw/oNAPmLWB1sVy8n9Fk0z6sPtZbDTpP9+t0K+ZHiGOoa0B2I
|
||||
cGhd/VWTpZo6IfaqEjPkXyC9iVRyazlJIdkzwl9QOEqqPhO75IxwXN7LuBV6Vx12
|
||||
3iDPh651EpN2NdbxmtMVymsWBjgfjKmTK8ye3r513F4I7Rp1EZOSNvmPutr3QaqT
|
||||
3lAqX9S6bMsRhQA0kDL7wtk8V++tIjko70/y/I/nrY3IQD61CTLkjOSw1Uj5FUGR
|
||||
Y61Yl+ANai4R9R/AkTuRinzpolFyPpOM9bGJ5xzG+O29eWH7fq68fk/c4t/uQu2g
|
||||
rULrNGBVTCkka0p6uNjjFUn+sJzkibNucHYtS1DEiV4SXZ+mANohLjaocVr7MZ6p
|
||||
nhAAKIYD8EWi8IwXXQXgUBNqr6qQB5yFnPcAhR5aCLFgh+RCMqVVXzZ7FO4oc0Fh
|
||||
IaVijpzACjLCZWTo++W9T0RPP/2KQpij2ZIOtB80hF8dfeWZeP0/OEaPkjUZEHla
|
||||
y5kwSAI6zkqnRtZRJpXPyvum8H6ACniMUHjRmtoFtNrlLZMM2i1Q4Qf8B4eqga6r
|
||||
+ehY3JUxfk3BtV0Y8DgKCBXSd3N2YvVn623Z546o/Bxio8Db+gkprgAW+5F3RRin
|
||||
iK7xxd0ZLzHgO0ASOgAKLbglZ2T+80NSMbWUbrkCDQReJM5qARAAuenyMXbUjin5
|
||||
2bntoz2I6b5dLMQlqRiggksMX1x/FFAO0cOaXxNtNpTzblN5Y2uZC6T+i5fnfXQ9
|
||||
yWkXIBLCXaaqziLx5wCg7rYsnS2/HYTVtd0ptYzqjKO++YAjHGcNnhUWdTJFRRMe
|
||||
CrYpRWtCH2YhdNPdfvsabLmF3LqI2GZp6x30q2oLxqLZFVA10d+AFCXEI5OcR9OW
|
||||
1N4okodA5HE9o3e+wYnRbJJUW4Lmxc8BFli7eqLNIhoI//tx276EKBhatBM6nAlZ
|
||||
AQJuEoQk08RnXPaMewC9HHjZQBjdM+ej3gl9PeYj6ptqcTqreN4AuRtcvvOgS80b
|
||||
TwixWYpDe58XUnT9JUF/C+n4AP+JdvSZHtK0FG9eNbLjcCSarsvOV98CkHthidJz
|
||||
mfUjP2txPZVxoB2CxXfUjYZ4Ib981CmdPON+HN/wtDpsWj5mOKeER9dWdXwr1d/a
|
||||
eRYZviTXEHMnEB/LnWzNx01RmyIUhw13fGr+6KBU3E4AogKwp/xFgoccvNR0LfSA
|
||||
HhNCRUPCh85bg9yTVroPmYeW6u0UWHflY6mVid08QDYFkHWGkGrEwsslpu9FpSBu
|
||||
J9lAuzVGlfWK2mFA8Lq8NmpuzJLUURYnXDfaQB2SxOjf9hjV7Z7GbNa95HBHZzQa
|
||||
vOExLxEUzspwR9d0isytL4Ytz9v3QpUAEQEAAYkEcgQYAQgAJhYhBG3t6rJYetcM
|
||||
xjRBeobn9RwE+6qwBQJeJM5qAhsCBQkFxJAAAkAJEIbn9RwE+6qwwXQgBBkBCAAd
|
||||
FiEEbws8jxbNPEKXXCt7wPFsNLeCUjAFAl4kzmoACgkQwPFsNLeCUjCbyRAAttzx
|
||||
zxca+YOlki8VQSwr+vktRBtwKRkzr3LCSJ7gY1SQeeJ9wQDXlBeNLxN1ev8+Xhce
|
||||
JNMv39gG3mfUUwnq+nlOA92mY1ZNWgdtN3ZUcJeQSTL+Xs2NC6tC/lMTBTm4kPlz
|
||||
c5f7GIPRURfoNFnytHuPvIEL+i1iAQfsLxCWHUWlJcsvbiFhv3mTKHhGVgpQlLDo
|
||||
kww1tJzBg+GfvBVNgUGoTBHLx6s1lbcODF6jrumYBQHYL93FwXBTKXeTcPdwypzt
|
||||
SHRioPh8aJ+EVfmt6n0LWMmNOAZVD0Ps5jKaq8ymrYABdS8wgrVZ6TRTDuAfmZwr
|
||||
4SY8gFqDLYEqXbl9iZZ3FFc8mWuBs7eqR5dxkVULgyjuYSdeppIhL2uuZmtsjAuJ
|
||||
8ewzuTGWTmrmZCDhD9AwGyqsrOOtqejgsE85yKsPiADUFHq7JNSIRfsb+HBU9j/a
|
||||
yi0fIwW4bzhL4xXrlsx1HtH2obCGyfeO4WXdZOCqYBfC21jzyJPY+KO7I79BBANd
|
||||
HamkAy1971SiPKvjFPtY93Fp8dKWywc6tq6oe8SMGvKk0SZ+OXSMeBomZWjbdjR7
|
||||
CUiyf/va5+v5QKLzApRGrhvBxgwAxgvK4zfLoKOC4WZQl8lYhgFYGZcFYNclcGvE
|
||||
wy9SsCpIIOT1z8NUl2K68qC/7A5eWU4MHfGkDaX8/g/+MdflD32Ldmwiv9lptDMm
|
||||
CtzE/uD/J902v7BuQeXX+pdHkgWeK8zTAJkIPEMB1kNFIoKVcGoic0msB+cN4RYy
|
||||
VZN56+kkhMRP+0m+xjPKyZ687VF88j8mEo/ybFxhwh58LydKNBqkdNeY9UbTT5bv
|
||||
WUlHWO5qClsCbRIPwjvsTgZk9qjAWrWbIFivXpPcC8z7NDCFVD5JTBxnkBbcXyvP
|
||||
W68+BvknNJ2Ilnww2aNNp5zmzGeDLSV28MTtUJXb3w6cXfeOazqcydgHqPO1gyTq
|
||||
oarCkgPEQlyLey/3qDmsmUskHQ8c81bQKPyGKH4dLiz0qdayRB/GICEIN7O+uk7/
|
||||
GdSem/sTXa/k9IpOm2eimdKG0y9C0k2kQb/IAr4cYMpbtN/gsyOr/UhqkzjawRCx
|
||||
wgHjKnXTNYtKcxuCpBpaPLQPFOE6IjG6Acpkh+LEdIYrY4hZmlaQqE3rXcye94rr
|
||||
F8ga+2XPNAwVH9zngrk1gZrTZmPkNkTG94kTfFwuAEmFWd6Dyf4IBoMacdpUyLTV
|
||||
99zduIWc4SOdzEawU0JdD50dkSlHDJsoNamhHe60MELbLOQjN43n3KqKHjIpgZcd
|
||||
2k2OFa/4uOXYoj3KmWxTKCV+BIcMuQfaaLJLHd1Uar59DExIatvw0aVWuj2M70tu
|
||||
J7VNNdmxD1RF3zsMFsPTQWY=
|
||||
=fqVo
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
9
debian/README.Debian
vendored
|
|
@ -1,9 +0,0 @@
|
|||
galmon for Debian
|
||||
|
||||
This directory contains files created by debmake and
|
||||
required by debuild to create Debian packages.
|
||||
|
||||
For further instruction, review the Guide for Debian Maintainers
|
||||
by Osamu Aoki: https:/www.debian.org/doc/manuals/debmake-doc/
|
||||
|
||||
-- Patrick Tudor <debian@ptudor.net> Sun, 19 Jan 2020 22:52:16 +0000
|
||||
5
debian/changelog
vendored
|
|
@ -1,5 +0,0 @@
|
|||
galmon (0.20191231-1) stable; urgency=low
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Patrick Tudor <debian@ptudor.net> Tue, 31 Dec 2019 00:00:00 +0000
|
||||
1
debian/compat
vendored
|
|
@ -1 +0,0 @@
|
|||
11
|
||||
24
debian/control
vendored
|
|
@ -1,24 +0,0 @@
|
|||
Source: galmon
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Patrick Tudor <debian@ptudor.net>
|
||||
Build-Depends: debhelper (>=11~), help2man, libzstd-dev, adduser
|
||||
Standards-Version: 4.3.0
|
||||
Homepage: https://github.com/ahupowerdns/galmon/
|
||||
|
||||
Package: galmon
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Conflicts: gpsd-clients
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Description: galmon GNSS Monitoring Project software
|
||||
88
|
||||
88
|
||||
88
|
||||
,adPPYb,d8 ,adPPYYba, 88 88,dPYba,,adPYba, ,adPPYba, 8b,dPPYba,
|
||||
a8" `Y88 "" `Y8 88 88P' "88" "8a a8" "8a 88P' `"8a
|
||||
8b 88 ,adPPPPP88 88 88 88 88 8b d8 88 88
|
||||
"8a, ,d88 88, ,88 88 88 88 88 "8a, ,a8" 88 88
|
||||
`"YbbdP"Y8 `"8bbdP"Y8 88 88 88 88 `"YbbdP"' 88 88
|
||||
aa, ,88
|
||||
"Y8bbdP"
|
||||
45
debian/copyright
vendored
|
|
@ -1,45 +0,0 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: galmon
|
||||
Source: https://github.com/ahupowerdns/galmon
|
||||
|
||||
Files: *
|
||||
Copyright: AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
License: GPL-3
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this package; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License can be found in `/usr/share/common-licenses/GPL-3'.
|
||||
|
||||
Files: minicurl.cc
|
||||
minicurl.hh
|
||||
Copyright: 2018-2019 powerdns.com bv
|
||||
License: Expat
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
32
debian/galmon.default
vendored
|
|
@ -1,32 +0,0 @@
|
|||
# Copyright 2020 AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
# https://galmon.eu - https://github.com/ahupowerdns/galmon
|
||||
# This package is free software: /usr/share/common-licenses/GPL-3
|
||||
#
|
||||
# INSTRUCTIONS:
|
||||
# (Pre-condition: "dmesg | tail" reports your GPS device appears on /dev/ttyACM0.)
|
||||
# Copy this file from /etc/default/galmon to /etc/default/ubxtool-ttyACM0
|
||||
# Please choose your constellations, update the owner and remark, and set your station and destination (ipv6 supported)
|
||||
# After customization, start the daemon:
|
||||
# systemctl enable --now ubxtool@ttyACM0 && journalctl -fu ubxtool
|
||||
# If you want, enable automatic upgrades at boot and every three days following:
|
||||
# systemctl enable --now galmon-upgrade.timer
|
||||
# If you have several devices, enable multiple services with their unique device names.
|
||||
#
|
||||
# FOR HELP:
|
||||
# Review the Operator.md file: https://github.com/ahupowerdns/galmon/blob/master/Operator.md
|
||||
# Download an IRC client like Textual (Mac) or HexChat (Windows). Join #galileo on the OFTC servers.
|
||||
# Thank you for contributing, it is nice to see data from your location on the map.
|
||||
#
|
||||
#
|
||||
# DO NOT SET THE "PORT" OPTION HERE.
|
||||
# The device is set by an argument to service, see above.
|
||||
#
|
||||
# uBlox M8-series and Aliexpress Specials support ( (gps AND galileo) AND ( beidou OR glonass) ) with optional SBAS.
|
||||
#
|
||||
DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --sbas --station 65000 --destination ::1"
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --beidou --sbas --station 65000 --destination ::1"
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --glonass --sbas --station 65000 --destination ::1"
|
||||
#
|
||||
# uBlox ZED F9T and F9P modules support all four major constellations simultaneously but not SBAS.
|
||||
#
|
||||
#DAEMON_OPTS="--owner 'OWNER' --remark 'REMARK' --gps --galileo --beidou --glonass --station 65000 --destination ::1"
|
||||
6
debian/galmon.docs
vendored
|
|
@ -1,6 +0,0 @@
|
|||
Building.md
|
||||
influxdb.md
|
||||
Operator.md
|
||||
PACKAGE-DEBIAN.md
|
||||
README.md
|
||||
|
||||
16
debian/galmon.galmon-upgrade.service
vendored
|
|
@ -1,16 +0,0 @@
|
|||
[Unit]
|
||||
Description=Upgrade Galmon Software
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the configuration exists
|
||||
ConditionPathExists=/etc/default/galmon
|
||||
|
||||
[Service]
|
||||
Environment=DEBIAN_FRONTEND=noninteractive
|
||||
Type=simple
|
||||
Restart=no
|
||||
ExecStartPre=+apt-get update
|
||||
ExecStart=+apt-get install -y galmon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
10
debian/galmon.galmon-upgrade.timer
vendored
|
|
@ -1,10 +0,0 @@
|
|||
[Unit]
|
||||
Description=Update galmon
|
||||
Documentation=https://github.com/ahupowerdns/galmon
|
||||
|
||||
[Timer]
|
||||
OnBootSec=3min
|
||||
OnUnitActiveSec=3d
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
20
debian/galmon.navnexus.service
vendored
|
|
@ -1,20 +0,0 @@
|
|||
[Unit]
|
||||
Description=galmon navnexus (serve recorded data on port 29601)
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the pre-installed configuration file exists
|
||||
ConditionPathExists=/etc/default/navstar
|
||||
|
||||
[Service]
|
||||
# Customize $DAEMON_OPTS_NAVNEXUS in /etc/default/navstar
|
||||
EnvironmentFile=-/etc/default/navstar
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=4
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=navnexus
|
||||
ExecStart=/usr/bin/navnexus $DAEMON_OPTS_NAVNEXUS
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
20
debian/galmon.navrecv.service
vendored
|
|
@ -1,20 +0,0 @@
|
|||
[Unit]
|
||||
Description=galmon navrecv (receive data on port 29603)
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that the pre-installed configuration file exists
|
||||
ConditionPathExists=/etc/default/navstar
|
||||
|
||||
[Service]
|
||||
# Customize $DAEMON_OPTS_NAVRECV in /etc/default/navstar
|
||||
EnvironmentFile=-/etc/default/navstar
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=navrecv
|
||||
ExecStart=/usr/bin/navrecv $DAEMON_OPTS_NAVRECV
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
9
debian/galmon.navstar.default
vendored
|
|
@ -1,9 +0,0 @@
|
|||
# Copyright 2020 AHU Holding BV - bert@hubertnet.nl - https://berthub.eu/
|
||||
# https://galmon.eu - https://github.com/ahupowerdns/galmon
|
||||
# This package is free software: /usr/share/common-licenses/GPL-3
|
||||
#
|
||||
# Here are daemon options used by the navrecv and navnexus services
|
||||
DAEMON_OPTS_NAVNEXUS="--storage /var/lib/galmon/storage --bind ::1"
|
||||
DAEMON_OPTS_NAVRECV="--bind ::1 --storage /var/lib/galmon/storage"
|
||||
#
|
||||
DAEMON_OPTS_NAVPARSE="--bind [::1]:10000 --html /usr/share/package/galmon/html --influxdb galileo"
|
||||
54
debian/galmon.postinst
vendored
|
|
@ -1,54 +0,0 @@
|
|||
#!/bin/sh
|
||||
# ptudor 20200120
|
||||
set -e
|
||||
|
||||
#. /usr/share/debconf/confmodule
|
||||
|
||||
setup_user() {
|
||||
|
||||
if getent group ubxtool > /dev/null ; then
|
||||
echo "galmon: ubxtool group exists, skipping"
|
||||
else
|
||||
echo "galmon: creating ubxtool system group"
|
||||
addgroup --system ubxtool
|
||||
fi
|
||||
|
||||
if getent passwd ubxtool > /dev/null ; then
|
||||
echo "galmon: ubxtool user exists, skipping"
|
||||
else
|
||||
echo "galmon: creating ubxtool system user"
|
||||
adduser --system ubxtool --no-create-home --home /run/ubxtool
|
||||
echo "galmon: adding ubxtool user to ubxtool group"
|
||||
adduser ubxtool ubxtool
|
||||
echo "galmon: adding ubxtool user to dialout group"
|
||||
adduser ubxtool dialout
|
||||
fi
|
||||
}
|
||||
|
||||
restart_ubxtool_daemon() {
|
||||
# I feel like this belongs in rules with dh_installsystemd but do not understand how to add the wildcard.
|
||||
if systemctl is-active 'ubxtool@*' > /dev/null ; then
|
||||
echo "galmon: restarting ubxtool."
|
||||
systemctl daemon-reload && systemctl restart 'ubxtool@*'
|
||||
else
|
||||
echo "galmon: ubxtool services are not currently enabled, not restarting."
|
||||
fi
|
||||
}
|
||||
|
||||
print_help_text() {
|
||||
echo "Galmon installation finished. If this is your first time, please:"
|
||||
echo " 1) Create a ubxtool configuration 2) Enable the service"
|
||||
echo " 3) Enable the timer for automatic upgrades if you want"
|
||||
echo "Replace ttyACM0 below with your device listed in /dev"
|
||||
echo "Example: cp /etc/default/galmon /etc/default/ubxtool-ttyACM0"
|
||||
echo "Example: vi /etc/default/ubxtool-ttyACM0"
|
||||
echo "Example: systemctl enable --now ubxtool@ttyACM0"
|
||||
echo "Example: (beta testing) systemctl enable --now galmon-upgrade.timer"
|
||||
}
|
||||
|
||||
setup_user
|
||||
print_help_text
|
||||
restart_ubxtool_daemon
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
3
debian/galmon.substvars
vendored
|
|
@ -1,3 +0,0 @@
|
|||
shlibs:Depends=libc6 (>= 2.28), libcurl4 (>= 7.16.2), libgcc1 (>= 1:3.5), libh2o-evloop0.13, libncurses6 (>= 6), libprotobuf17, libssl1.1 (>= 1.1.0), libstdc++6 (>= 6), libtinfo6 (>= 6), zlib1g (>= 1:1.1.4)
|
||||
misc:Depends=
|
||||
misc:Pre-Depends=
|
||||
20
debian/galmon.ubxtool@.service
vendored
|
|
@ -1,20 +0,0 @@
|
|||
[Unit]
|
||||
Description=galmon ubxtool on /dev/%I
|
||||
After=network.target nss-lookup.target
|
||||
StartLimitIntervalSec=0
|
||||
# require that both the device and configuration exist
|
||||
ConditionPathExists=/dev/%i
|
||||
ConditionPathExists=/etc/default/ubxtool-%i
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=-/etc/default/ubxtool-%i
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=4
|
||||
User=ubxtool
|
||||
Group=ubxtool
|
||||
RuntimeDirectory=ubxtool
|
||||
ExecStart=/usr/bin/ubxtool --port /dev/%i $DAEMON_OPTS
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
1
debian/patches/series
vendored
|
|
@ -1 +0,0 @@
|
|||
# You must remove unused comment lines for the released package.
|
||||
17
debian/profile-debuild.sh
vendored
|
|
@ -1,17 +0,0 @@
|
|||
# This file of environment variables is sourced via /etc/profile.d/debuild.sh
|
||||
#
|
||||
# It sets the email and name of the maintainer first.
|
||||
# Next we disable parallel builds for ram conservation, normally enabled.
|
||||
# Finally we add hardening to the build.
|
||||
|
||||
DEBEMAIL="debian@ptudor.net"
|
||||
DEBFULLNAME="Patrick Tudor"
|
||||
|
||||
# "nocheck" here because testrunner runs out of ram on most current arm hardware -pht
|
||||
# manual: dh_auto_test: If the DEB_BUILD_OPTIONS environment variable contains nocheck, no tests will be performed.
|
||||
DEB_BUILD_OPTIONS='parallel=1 nocheck'
|
||||
|
||||
# https://wiki.debian.org/Hardening
|
||||
DEB_BUILD_MAINT_OPTIONS='hardening=+all'
|
||||
|
||||
export DEBEMAIL DEBFULLNAME DEB_BUILD_OPTIONS DEB_BUILD_MAINT_OPTIONS
|
||||
22
debian/rules
vendored
|
|
@ -1,22 +0,0 @@
|
|||
#!/usr/bin/make -f
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||
export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_install:
|
||||
dh_auto_install -- prefix=/usr
|
||||
|
||||
# override to install /etc/default/navstar alongside /etc/default/galmon
|
||||
override_dh_installinit:
|
||||
dh_installinit --name=navstar
|
||||
dh_installinit
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --no-enable --no-start --name=galmon-upgrade galmon-upgrade.service
|
||||
dh_installsystemd --no-enable --no-start --name=galmon-upgrade galmon-upgrade.timer
|
||||
dh_installsystemd --no-enable --no-start --name=navnexus navnexus.service
|
||||
dh_installsystemd --no-enable --no-start --name=navrecv navrecv.service
|
||||
dh_installsystemd --no-enable --no-start --name=ubxtool@ ubxtool@.service
|
||||
1
debian/source/format
vendored
|
|
@ -1 +0,0 @@
|
|||
3.0 (quilt)
|
||||
2
debian/source/local-options
vendored
|
|
@ -1,2 +0,0 @@
|
|||
#abort-on-upstream-changes
|
||||
#unapply-patches
|
||||
1
debian/watch
vendored
|
|
@ -1 +0,0 @@
|
|||
version=3
|
||||
27
ephemeris.cc
|
|
@ -1,6 +1,5 @@
|
|||
#include "ephemeris.hh"
|
||||
#include "minivec.hh"
|
||||
#include <tuple>
|
||||
/* | t0e tow | - > tow - t0e, <3.5 days, so ok
|
||||
|
||||
| t0e tow | -> tow - t0e > 3.5 days, so
|
||||
|
|
@ -13,9 +12,9 @@
|
|||
*/
|
||||
|
||||
// positive age = t0e in the past
|
||||
double ephAge(double tow, int t0e)
|
||||
int ephAge(int tow, int t0e)
|
||||
{
|
||||
double diff = tow - t0e;
|
||||
int diff = tow - t0e;
|
||||
if(diff > 3.5*86400)
|
||||
diff -= 604800;
|
||||
if(diff < -3.5*86400)
|
||||
|
|
@ -23,8 +22,6 @@ double ephAge(double tow, int t0e)
|
|||
return diff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// all axes start at earth center of gravity
|
||||
// x-axis is on equator, 0 longitude
|
||||
// y-axis is on equator, 90 longitude
|
||||
|
|
@ -32,8 +29,24 @@ double ephAge(double tow, int t0e)
|
|||
// https://en.wikipedia.org/wiki/ECEF#/media/File:Ecef.png
|
||||
std::pair<double,double> getLongLat(double x, double y, double z)
|
||||
{
|
||||
auto ret = ecefToWGS84(x, y, z);
|
||||
return {std::get<1>(ret), std::get<0>(ret)};
|
||||
Point core{0,0,0};
|
||||
Point LatLon0{1,0,0};
|
||||
|
||||
Point pos{x, y, z};
|
||||
|
||||
Point proj{x, y, 0}; // in equatorial plane now
|
||||
Vector flat(core, proj);
|
||||
Vector toLatLon0{core, LatLon0};
|
||||
double longitude = acos( toLatLon0.inner(flat) / (toLatLon0.length() * flat.length()));
|
||||
if(y < 0)
|
||||
longitude *= -1;
|
||||
|
||||
Vector toUs{core, pos};
|
||||
double latitude = acos( flat.inner(toUs) / (toUs.length() * flat.length()));
|
||||
if(z < 0)
|
||||
latitude *= -1;
|
||||
|
||||
return std::make_pair(longitude, latitude);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
87
ephemeris.hh
|
|
@ -1,56 +1,11 @@
|
|||
#pragma once
|
||||
#include "minivec.hh"
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <stdint.h>
|
||||
struct GPSLikeEphemeris
|
||||
{
|
||||
virtual double getMu() const = 0;
|
||||
virtual double getOmegaE() const = 0;
|
||||
virtual double getE() const = 0;
|
||||
virtual uint32_t getT0e() const = 0;
|
||||
|
||||
virtual double getI0() const = 0;
|
||||
|
||||
virtual double getOmegadot() const = 0;
|
||||
|
||||
virtual double getSqrtA() const = 0;
|
||||
virtual double getOmega0() const = 0;
|
||||
virtual double getOmega() const = 0;
|
||||
|
||||
virtual double getM0() const = 0;
|
||||
virtual double getIdot() const = 0;
|
||||
virtual double getCic() const = 0;
|
||||
virtual double getCis() const = 0;
|
||||
virtual double getCuc() const = 0;
|
||||
virtual double getCus() const = 0;
|
||||
virtual double getCrc() const = 0;
|
||||
virtual double getCrs() const = 0;
|
||||
virtual double getDeltan()const = 0;
|
||||
|
||||
virtual int getIOD() const = 0;
|
||||
// maybe af0, af1, af2?
|
||||
// maybe getUTCOffset? getAtomicOffset etc
|
||||
|
||||
};
|
||||
|
||||
// lat, lon, height (rad, rad, meters)
|
||||
std::tuple<double, double, double> ecefToWGS84(double x, double y, double z);
|
||||
|
||||
// lat, lon, height (deg, deg, meters)
|
||||
inline std::tuple<double, double, double> ecefToWGS84Deg(double x, double y, double z)
|
||||
{
|
||||
auto ret = ecefToWGS84(x, y, z);
|
||||
std::get<0>(ret) /= (M_PI / 180);
|
||||
std::get<1>(ret) /= (M_PI / 180);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
double ephAge(double tow, int t0e);
|
||||
int ephAge(int tow, int t0e);
|
||||
|
||||
template<typename T>
|
||||
double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
||||
void getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
||||
{
|
||||
using namespace std;
|
||||
// here goes
|
||||
|
|
@ -89,7 +44,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
|
||||
cerr << "sqrtA = "<< sqrtA << endl;
|
||||
cerr << "deltan = "<< deltan << endl;
|
||||
cerr << "t0e = "<< t0e << "( rel "<<(tow - t0e)<<")"<<endl;
|
||||
cerr << "t0e = "<< t0e << endl;
|
||||
cerr << "m0 = "<< m0 << " ("<<todeg(m0)<<")"<<endl;
|
||||
cerr << "e = "<< e << endl;
|
||||
cerr << "omega = " << omega << " ("<<todeg(omega)<<")"<<endl;
|
||||
|
|
@ -114,7 +69,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
double A3 = pow(sqrtA, 6.0);
|
||||
|
||||
double n0 = sqrt(mu/A3);
|
||||
double tk = ephAge(tow, t0e);
|
||||
double tk = ephAge(tow, t0e); // in seconds, should do ephAge
|
||||
|
||||
double n = n0 + deltan;
|
||||
if(!quiet)
|
||||
|
|
@ -122,15 +77,14 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
|
||||
double M = m0 + n * tk;
|
||||
if(!quiet)
|
||||
cerr << " M = m0 + n * tk = "<<m0 << " + " << n << " * " << tk << " = " <<M <<endl;
|
||||
cerr << " M = m0 + n * tk = "<<m0 << " + " << n << " * " << tk <<endl;
|
||||
double E = M;
|
||||
double newE;
|
||||
for(int k =0 ; k < 10; ++k) {
|
||||
newE = M + e * sin(E);
|
||||
if(!quiet)
|
||||
cerr<<"k "<<k<<" M = "<<M<<", E = "<< E << ", delta: "<< (E-newE) << endl;
|
||||
|
||||
if(fabs(E-newE) < 0.0000001) {
|
||||
cerr<<"k "<<k<<" M = "<<M<<", E = "<< E << endl;
|
||||
newE = M + e * sin(E);
|
||||
if(fabs(E-newE) < 0.00001) {
|
||||
E = newE;
|
||||
break;
|
||||
}
|
||||
|
|
@ -150,28 +104,19 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
((cos(E) - e)/ (1-e*cos(E)))
|
||||
);
|
||||
|
||||
double nu2A = atan( (sqrt(1-e*e) * sin(E)) /
|
||||
double nu2 = atan( (sqrt(1-e*e) * sin(E)) /
|
||||
(cos(E) - e)
|
||||
);
|
||||
|
||||
|
||||
double nu2B = atan2( (sqrt(1-e*e) * sin(E)) ,
|
||||
(cos(E) - e)
|
||||
);
|
||||
|
||||
|
||||
double nu3 = 2* atan( sqrt((1+e)/(1-e)) * tan(E/2));
|
||||
cerr << "e: "<<e<<", M: "<< M<<", E: "<< E<<endl;
|
||||
cerr << "e: "<<e<<", M: "<< M<<endl;
|
||||
cerr <<" nu sis: "<<nu1<< " / +pi = " << nu1 +M_PI << endl;
|
||||
cerr <<" nu ?: "<<nu2A<< " / +pi = " << nu2A +M_PI << endl;
|
||||
cerr <<" nu ?: "<<nu2B<< " / +pi = " << nu2B +M_PI << endl;
|
||||
cerr <<"* nu fourier/esa: "<<nu2<< " + " << corr <<" = " << nu2 + corr<<" | "<< std::fixed<<nu2+corr-nu1<<endl;
|
||||
cerr <<" nu ?: "<<nu2<< " / +pi = " << nu2 +M_PI << endl;
|
||||
cerr <<" nu fourier/esa: "<<nu2<< " + " << corr <<" = " << nu2 + corr<<endl;
|
||||
cerr <<" nu wikipedia: "<<nu3<< " / +pi = " <<nu3 +M_PI << endl;
|
||||
}
|
||||
double nu = atan2( (sqrt(1-e*e) * sin(E)) ,
|
||||
(cos(E) - e)
|
||||
);
|
||||
|
||||
|
||||
double nu = nu2 + corr;
|
||||
|
||||
// https://en.wikipedia.org/wiki/True_anomaly is good
|
||||
|
||||
|
|
@ -187,7 +132,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
|
||||
double u = psi + deltau;
|
||||
|
||||
double r = A * (1 - e * cos(E)) + deltar;
|
||||
double r = A * (1- e * cos(E)) + deltar;
|
||||
|
||||
double xprime = r*cos(u), yprime = r*sin(u);
|
||||
if(!quiet) {
|
||||
|
|
@ -206,7 +151,7 @@ double getCoordinates(double tow, const T& iod, Point* p, bool quiet=true)
|
|||
Vector radius(core, *p);
|
||||
cerr << radius.length() << " calculated r "<<endl;
|
||||
}
|
||||
return E;
|
||||
|
||||
}
|
||||
|
||||
struct DopplerData
|
||||
|
|
|
|||
|
|
@ -1,36 +1,16 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
// CLI Library includes
|
||||
// Order is important for combiner script
|
||||
|
||||
#include "Version.hpp"
|
||||
|
||||
#include "Macros.hpp"
|
||||
|
||||
#include "StringTools.hpp"
|
||||
|
||||
#include "Error.hpp"
|
||||
|
||||
#include "TypeTools.hpp"
|
||||
|
||||
#include "Split.hpp"
|
||||
|
||||
#include "ConfigFwd.hpp"
|
||||
|
||||
#include "Validators.hpp"
|
||||
|
||||
#include "FormatterFwd.hpp"
|
||||
|
||||
#include "Option.hpp"
|
||||
|
||||
#include "App.hpp"
|
||||
|
||||
#include "Config.hpp"
|
||||
|
||||
#include "Formatter.hpp"
|
||||
#include "CLI/Error.hpp"
|
||||
#include "CLI/TypeTools.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
#include "CLI/Split.hpp"
|
||||
#include "CLI/Ini.hpp"
|
||||
#include "CLI/Validators.hpp"
|
||||
#include "CLI/Option.hpp"
|
||||
#include "CLI/App.hpp"
|
||||
|
|
|
|||
|
|
@ -1,346 +0,0 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "App.hpp"
|
||||
#include "ConfigFwd.hpp"
|
||||
#include "StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline std::string convert_arg_for_ini(const std::string &arg) {
|
||||
if(arg.empty()) {
|
||||
return std::string(2, '"');
|
||||
}
|
||||
// some specifically supported strings
|
||||
if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
|
||||
return arg;
|
||||
}
|
||||
// floating point conversion can convert some hex codes, but don't try that here
|
||||
if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
|
||||
double val;
|
||||
if(detail::lexical_cast(arg, val)) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
// just quote a single non numeric character
|
||||
if(arg.size() == 1) {
|
||||
return std::string("'") + arg + '\'';
|
||||
}
|
||||
// handle hex, binary or octal arguments
|
||||
if(arg.front() == '0') {
|
||||
if(arg[1] == 'x') {
|
||||
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
|
||||
return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
|
||||
})) {
|
||||
return arg;
|
||||
}
|
||||
} else if(arg[1] == 'o') {
|
||||
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
|
||||
return arg;
|
||||
}
|
||||
} else if(arg[1] == 'b') {
|
||||
if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(arg.find_first_of('"') == std::string::npos) {
|
||||
return std::string("\"") + arg + '"';
|
||||
} else {
|
||||
return std::string("'") + arg + '\'';
|
||||
}
|
||||
}
|
||||
|
||||
/// Comma separated join, adds quotes if needed
|
||||
inline std::string
|
||||
ini_join(const std::vector<std::string> &args, char sepChar = ',', char arrayStart = '[', char arrayEnd = ']') {
|
||||
std::string joined;
|
||||
if(args.size() > 1 && arrayStart != '\0') {
|
||||
joined.push_back(arrayStart);
|
||||
}
|
||||
std::size_t start = 0;
|
||||
for(const auto &arg : args) {
|
||||
if(start++ > 0) {
|
||||
joined.push_back(sepChar);
|
||||
if(isspace(sepChar) == 0) {
|
||||
joined.push_back(' ');
|
||||
}
|
||||
}
|
||||
joined.append(convert_arg_for_ini(arg));
|
||||
}
|
||||
if(args.size() > 1 && arrayEnd != '\0') {
|
||||
joined.push_back(arrayEnd);
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> generate_parents(const std::string §ion, std::string &name) {
|
||||
std::vector<std::string> parents;
|
||||
if(detail::to_lower(section) != "default") {
|
||||
if(section.find('.') != std::string::npos) {
|
||||
parents = detail::split(section, '.');
|
||||
} else {
|
||||
parents = {section};
|
||||
}
|
||||
}
|
||||
if(name.find('.') != std::string::npos) {
|
||||
std::vector<std::string> plist = detail::split(name, '.');
|
||||
name = plist.back();
|
||||
detail::remove_quotes(name);
|
||||
plist.pop_back();
|
||||
parents.insert(parents.end(), plist.begin(), plist.end());
|
||||
}
|
||||
|
||||
// clean up quotes on the parents
|
||||
for(auto &parent : parents) {
|
||||
detail::remove_quotes(parent);
|
||||
}
|
||||
return parents;
|
||||
}
|
||||
|
||||
/// assuming non default segments do a check on the close and open of the segments in a configItem structure
|
||||
inline void checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection) {
|
||||
|
||||
std::string estring;
|
||||
auto parents = detail::generate_parents(currentSection, estring);
|
||||
if(!output.empty() && output.back().name == "--") {
|
||||
std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
|
||||
while(output.back().parents.size() >= msize) {
|
||||
output.push_back(output.back());
|
||||
output.back().parents.pop_back();
|
||||
}
|
||||
|
||||
if(parents.size() > 1) {
|
||||
std::size_t common = 0;
|
||||
std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
|
||||
for(std::size_t ii = 0; ii < mpair; ++ii) {
|
||||
if(output.back().parents[ii] != parents[ii]) {
|
||||
break;
|
||||
}
|
||||
++common;
|
||||
}
|
||||
if(common == mpair) {
|
||||
output.pop_back();
|
||||
} else {
|
||||
while(output.back().parents.size() > common + 1) {
|
||||
output.push_back(output.back());
|
||||
output.back().parents.pop_back();
|
||||
}
|
||||
}
|
||||
for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
|
||||
output.emplace_back();
|
||||
output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
|
||||
output.back().name = "++";
|
||||
}
|
||||
}
|
||||
} else if(parents.size() > 1) {
|
||||
for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
|
||||
output.emplace_back();
|
||||
output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
|
||||
output.back().name = "++";
|
||||
}
|
||||
}
|
||||
|
||||
// insert a section end which is just an empty items_buffer
|
||||
output.emplace_back();
|
||||
output.back().parents = std::move(parents);
|
||||
output.back().name = "++";
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
|
||||
std::string line;
|
||||
std::string section = "default";
|
||||
|
||||
std::vector<ConfigItem> output;
|
||||
bool defaultArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
|
||||
char aStart = (defaultArray) ? '[' : arrayStart;
|
||||
char aEnd = (defaultArray) ? ']' : arrayEnd;
|
||||
char aSep = (defaultArray && arraySeparator == ' ') ? ',' : arraySeparator;
|
||||
|
||||
while(getline(input, line)) {
|
||||
std::vector<std::string> items_buffer;
|
||||
std::string name;
|
||||
|
||||
detail::trim(line);
|
||||
std::size_t len = line.length();
|
||||
if(len > 1 && line.front() == '[' && line.back() == ']') {
|
||||
if(section != "default") {
|
||||
// insert a section end which is just an empty items_buffer
|
||||
output.emplace_back();
|
||||
output.back().parents = detail::generate_parents(section, name);
|
||||
output.back().name = "--";
|
||||
}
|
||||
section = line.substr(1, len - 2);
|
||||
// deal with double brackets for TOML
|
||||
if(section.size() > 1 && section.front() == '[' && section.back() == ']') {
|
||||
section = section.substr(1, section.size() - 2);
|
||||
}
|
||||
if(detail::to_lower(section) == "default") {
|
||||
section = "default";
|
||||
} else {
|
||||
detail::checkParentSegments(output, section);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(len == 0) {
|
||||
continue;
|
||||
}
|
||||
// comment lines
|
||||
if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find = in string, split and recombine
|
||||
auto pos = line.find(valueDelimiter);
|
||||
if(pos != std::string::npos) {
|
||||
name = detail::trim_copy(line.substr(0, pos));
|
||||
std::string item = detail::trim_copy(line.substr(pos + 1));
|
||||
if(item.size() > 1 && item.front() == aStart && item.back() == aEnd) {
|
||||
items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
|
||||
} else if(defaultArray && item.find_first_of(aSep) != std::string::npos) {
|
||||
items_buffer = detail::split_up(item, aSep);
|
||||
} else if(defaultArray && item.find_first_of(' ') != std::string::npos) {
|
||||
items_buffer = detail::split_up(item);
|
||||
} else {
|
||||
items_buffer = {item};
|
||||
}
|
||||
} else {
|
||||
name = detail::trim_copy(line);
|
||||
items_buffer = {"true"};
|
||||
}
|
||||
if(name.find('.') == std::string::npos) {
|
||||
detail::remove_quotes(name);
|
||||
}
|
||||
// clean up quotes on the items
|
||||
for(auto &it : items_buffer) {
|
||||
detail::remove_quotes(it);
|
||||
}
|
||||
|
||||
std::vector<std::string> parents = detail::generate_parents(section, name);
|
||||
|
||||
if(!output.empty() && name == output.back().name && parents == output.back().parents) {
|
||||
output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end());
|
||||
} else {
|
||||
output.emplace_back();
|
||||
output.back().parents = std::move(parents);
|
||||
output.back().name = std::move(name);
|
||||
output.back().inputs = std::move(items_buffer);
|
||||
}
|
||||
}
|
||||
if(section != "default") {
|
||||
// insert a section end which is just an empty items_buffer
|
||||
std::string ename;
|
||||
output.emplace_back();
|
||||
output.back().parents = detail::generate_parents(section, ename);
|
||||
output.back().name = "--";
|
||||
while(output.back().parents.size() > 1) {
|
||||
output.push_back(output.back());
|
||||
output.back().parents.pop_back();
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
inline std::string
|
||||
ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
|
||||
std::stringstream out;
|
||||
std::string commentLead;
|
||||
commentLead.push_back(commentChar);
|
||||
commentLead.push_back(' ');
|
||||
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
bool defaultUsed = false;
|
||||
groups.insert(groups.begin(), std::string("Options"));
|
||||
if(write_description) {
|
||||
out << commentLead << app->get_description() << '\n';
|
||||
}
|
||||
for(auto &group : groups) {
|
||||
if(group == "Options" || group.empty()) {
|
||||
if(defaultUsed) {
|
||||
continue;
|
||||
}
|
||||
defaultUsed = true;
|
||||
}
|
||||
if(write_description && group != "Options" && !group.empty()) {
|
||||
out << '\n' << commentLead << group << " Options\n";
|
||||
}
|
||||
for(const Option *opt : app->get_options({})) {
|
||||
|
||||
// Only process option with a long-name and configurable
|
||||
if(!opt->get_lnames().empty() && opt->get_configurable()) {
|
||||
if(opt->get_group() != group) {
|
||||
if(!(group == "Options" && opt->get_group().empty())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
std::string name = prefix + opt->get_lnames()[0];
|
||||
std::string value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd);
|
||||
|
||||
if(value.empty() && default_also) {
|
||||
if(!opt->get_default_str().empty()) {
|
||||
value = detail::convert_arg_for_ini(opt->get_default_str());
|
||||
} else if(opt->get_expected_min() == 0) {
|
||||
value = "false";
|
||||
}
|
||||
}
|
||||
|
||||
if(!value.empty()) {
|
||||
if(write_description && opt->has_description()) {
|
||||
out << '\n';
|
||||
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
|
||||
}
|
||||
out << name << valueDelimiter << value << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto subcommands = app->get_subcommands({});
|
||||
for(const App *subcom : subcommands) {
|
||||
if(subcom->get_name().empty()) {
|
||||
if(write_description && !subcom->get_group().empty()) {
|
||||
out << '\n' << commentLead << subcom->get_group() << " Options\n";
|
||||
}
|
||||
out << to_config(subcom, default_also, write_description, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
for(const App *subcom : subcommands) {
|
||||
if(!subcom->get_name().empty()) {
|
||||
if(subcom->get_configurable() && app->got_subcommand(subcom)) {
|
||||
if(!prefix.empty() || app->get_parent() == nullptr) {
|
||||
out << '[' << prefix << subcom->get_name() << "]\n";
|
||||
} else {
|
||||
std::string subname = app->get_name() + "." + subcom->get_name();
|
||||
auto p = app->get_parent();
|
||||
while(p->get_parent() != nullptr) {
|
||||
subname = p->get_name() + "." + subname;
|
||||
p = p->get_parent();
|
||||
}
|
||||
out << '[' << subname << "]\n";
|
||||
}
|
||||
out << to_config(subcom, default_also, write_description, "");
|
||||
} else {
|
||||
out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // namespace CLI
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Error.hpp"
|
||||
#include "StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
class App;
|
||||
|
||||
/// Holds values to load into Options
|
||||
struct ConfigItem {
|
||||
/// This is the list of parents
|
||||
std::vector<std::string> parents{};
|
||||
|
||||
/// This is the name
|
||||
std::string name{};
|
||||
|
||||
/// Listing of inputs
|
||||
std::vector<std::string> inputs{};
|
||||
|
||||
/// The list of parents and name joined by "."
|
||||
std::string fullname() const {
|
||||
std::vector<std::string> tmp = parents;
|
||||
tmp.emplace_back(name);
|
||||
return detail::join(tmp, ".");
|
||||
}
|
||||
};
|
||||
|
||||
/// This class provides a converter for configuration files.
|
||||
class Config {
|
||||
protected:
|
||||
std::vector<ConfigItem> items{};
|
||||
|
||||
public:
|
||||
/// Convert an app into a configuration
|
||||
virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
|
||||
|
||||
/// Convert a configuration into an app
|
||||
virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
|
||||
|
||||
/// Get a flag value
|
||||
virtual std::string to_flag(const ConfigItem &item) const {
|
||||
if(item.inputs.size() == 1) {
|
||||
return item.inputs.at(0);
|
||||
}
|
||||
throw ConversionError::TooManyInputsFlag(item.fullname());
|
||||
}
|
||||
|
||||
/// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
|
||||
std::vector<ConfigItem> from_file(const std::string &name) {
|
||||
std::ifstream input{name};
|
||||
if(!input.good())
|
||||
throw FileError::Missing(name);
|
||||
|
||||
return from_config(input);
|
||||
}
|
||||
|
||||
/// Virtual destructor
|
||||
virtual ~Config() = default;
|
||||
};
|
||||
|
||||
/// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML
|
||||
class ConfigBase : public Config {
|
||||
protected:
|
||||
/// the character used for comments
|
||||
char commentChar = ';';
|
||||
/// the character used to start an array '\0' is a default to not use
|
||||
char arrayStart = '\0';
|
||||
/// the character used to end an array '\0' is a default to not use
|
||||
char arrayEnd = '\0';
|
||||
/// the character used to separate elements in an array
|
||||
char arraySeparator = ' ';
|
||||
/// the character used separate the name from the value
|
||||
char valueDelimiter = '=';
|
||||
|
||||
public:
|
||||
std::string
|
||||
to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override;
|
||||
|
||||
std::vector<ConfigItem> from_config(std::istream &input) const override;
|
||||
/// Specify the configuration for comment characters
|
||||
ConfigBase *comment(char cchar) {
|
||||
commentChar = cchar;
|
||||
return this;
|
||||
}
|
||||
/// Specify the start and end characters for an array
|
||||
ConfigBase *arrayBounds(char aStart, char aEnd) {
|
||||
arrayStart = aStart;
|
||||
arrayEnd = aEnd;
|
||||
return this;
|
||||
}
|
||||
/// Specify the delimiter character for an array
|
||||
ConfigBase *arrayDelimiter(char aSep) {
|
||||
arraySeparator = aSep;
|
||||
return this;
|
||||
}
|
||||
/// Specify the delimiter between a name and value
|
||||
ConfigBase *valueSeparator(char vSep) {
|
||||
valueDelimiter = vSep;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
/// the default Config is the INI file format
|
||||
using ConfigINI = ConfigBase;
|
||||
|
||||
/// ConfigTOML generates a TOML compliant output
|
||||
class ConfigTOML : public ConfigINI {
|
||||
|
||||
public:
|
||||
ConfigTOML() {
|
||||
commentChar = '#';
|
||||
arrayStart = '[';
|
||||
arrayEnd = ']';
|
||||
arraySeparator = ',';
|
||||
valueDelimiter = '=';
|
||||
}
|
||||
};
|
||||
} // namespace CLI
|
||||
|
|
@ -1,38 +1,14 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// CLI library includes
|
||||
#include "StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
// Use one of these on all error classes.
|
||||
// These are temporary and are undef'd at the end of this file.
|
||||
#define CLI11_ERROR_DEF(parent, name) \
|
||||
protected: \
|
||||
name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \
|
||||
name(std::string ename, std::string msg, ExitCodes exit_code) \
|
||||
: parent(std::move(ename), std::move(msg), exit_code) {} \
|
||||
\
|
||||
public: \
|
||||
name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \
|
||||
name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}
|
||||
|
||||
// This is added after the one above if a class is used directly and builds its own message
|
||||
#define CLI11_ERROR_SIMPLE(name) \
|
||||
explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {}
|
||||
|
||||
/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,
|
||||
/// int values from e.get_error_code().
|
||||
enum class ExitCodes {
|
||||
|
|
@ -40,19 +16,18 @@ enum class ExitCodes {
|
|||
IncorrectConstruction = 100,
|
||||
BadNameString,
|
||||
OptionAlreadyAdded,
|
||||
FileError,
|
||||
ConversionError,
|
||||
ValidationError,
|
||||
RequiredError,
|
||||
RequiresError,
|
||||
ExcludesError,
|
||||
ExtrasError,
|
||||
ConfigError,
|
||||
InvalidError,
|
||||
HorribleError,
|
||||
File,
|
||||
Conversion,
|
||||
Validation,
|
||||
Required,
|
||||
Requires,
|
||||
Excludes,
|
||||
Extras,
|
||||
ExtrasINI,
|
||||
Invalid,
|
||||
Horrible,
|
||||
OptionNotFound,
|
||||
ArgumentMismatch,
|
||||
BaseClass = 127
|
||||
BaseClass = 255
|
||||
};
|
||||
|
||||
// Error definitions
|
||||
|
|
@ -64,277 +39,127 @@ enum class ExitCodes {
|
|||
/// @{
|
||||
|
||||
/// All errors derive from this one
|
||||
class Error : public std::runtime_error {
|
||||
int actual_exit_code;
|
||||
std::string error_name{"Error"};
|
||||
|
||||
public:
|
||||
int get_exit_code() const { return actual_exit_code; }
|
||||
|
||||
std::string get_name() const { return error_name; }
|
||||
|
||||
Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
|
||||
: runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
|
||||
|
||||
Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
|
||||
struct Error : public std::runtime_error {
|
||||
int exit_code;
|
||||
bool print_help;
|
||||
int get_exit_code() const { return exit_code; }
|
||||
Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true)
|
||||
: runtime_error(parent + ": " + name), exit_code(static_cast<int>(exit_code)), print_help(print_help) {}
|
||||
Error(std::string parent,
|
||||
std::string name,
|
||||
int exit_code = static_cast<int>(ExitCodes::BaseClass),
|
||||
bool print_help = true)
|
||||
: runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {}
|
||||
};
|
||||
|
||||
// Note: Using Error::Error constructors does not work on GCC 4.7
|
||||
|
||||
/// Construction errors (not in parsing)
|
||||
class ConstructionError : public Error {
|
||||
CLI11_ERROR_DEF(Error, ConstructionError)
|
||||
struct ConstructionError : public Error {
|
||||
// Using Error::Error constructors seem to not work on GCC 4.7
|
||||
ConstructionError(std::string parent,
|
||||
std::string name,
|
||||
ExitCodes exit_code = ExitCodes::BaseClass,
|
||||
bool print_help = true)
|
||||
: Error(parent, name, exit_code, print_help) {}
|
||||
};
|
||||
|
||||
/// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
|
||||
class IncorrectConstruction : public ConstructionError {
|
||||
CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)
|
||||
CLI11_ERROR_SIMPLE(IncorrectConstruction)
|
||||
static IncorrectConstruction PositionalFlag(std::string name) {
|
||||
return IncorrectConstruction(name + ": Flags cannot be positional");
|
||||
}
|
||||
static IncorrectConstruction Set0Opt(std::string name) {
|
||||
return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");
|
||||
}
|
||||
static IncorrectConstruction SetFlag(std::string name) {
|
||||
return IncorrectConstruction(name + ": Cannot set an expected number for flags");
|
||||
}
|
||||
static IncorrectConstruction ChangeNotVector(std::string name) {
|
||||
return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");
|
||||
}
|
||||
static IncorrectConstruction AfterMultiOpt(std::string name) {
|
||||
return IncorrectConstruction(
|
||||
name + ": You can't change expected arguments after you've changed the multi option policy!");
|
||||
}
|
||||
static IncorrectConstruction MissingOption(std::string name) {
|
||||
return IncorrectConstruction("Option " + name + " is not defined");
|
||||
}
|
||||
static IncorrectConstruction MultiOptionPolicy(std::string name) {
|
||||
return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options");
|
||||
}
|
||||
struct IncorrectConstruction : public ConstructionError {
|
||||
IncorrectConstruction(std::string name)
|
||||
: ConstructionError("IncorrectConstruction", name, ExitCodes::IncorrectConstruction) {}
|
||||
};
|
||||
|
||||
/// Thrown on construction of a bad name
|
||||
class BadNameString : public ConstructionError {
|
||||
CLI11_ERROR_DEF(ConstructionError, BadNameString)
|
||||
CLI11_ERROR_SIMPLE(BadNameString)
|
||||
static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); }
|
||||
static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
|
||||
static BadNameString DashesOnly(std::string name) {
|
||||
return BadNameString("Must have a name, not just dashes: " + name);
|
||||
}
|
||||
static BadNameString MultiPositionalNames(std::string name) {
|
||||
return BadNameString("Only one positional name allowed, remove: " + name);
|
||||
}
|
||||
struct BadNameString : public ConstructionError {
|
||||
BadNameString(std::string name) : ConstructionError("BadNameString", name, ExitCodes::BadNameString) {}
|
||||
};
|
||||
|
||||
/// Thrown when an option already exists
|
||||
class OptionAlreadyAdded : public ConstructionError {
|
||||
CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)
|
||||
explicit OptionAlreadyAdded(std::string name)
|
||||
: OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {}
|
||||
static OptionAlreadyAdded Requires(std::string name, std::string other) {
|
||||
return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded);
|
||||
}
|
||||
static OptionAlreadyAdded Excludes(std::string name, std::string other) {
|
||||
return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded);
|
||||
}
|
||||
struct OptionAlreadyAdded : public ConstructionError {
|
||||
OptionAlreadyAdded(std::string name)
|
||||
: ConstructionError("OptionAlreadyAdded", name, ExitCodes::OptionAlreadyAdded) {}
|
||||
};
|
||||
|
||||
// Parsing errors
|
||||
|
||||
/// Anything that can error in Parse
|
||||
class ParseError : public Error {
|
||||
CLI11_ERROR_DEF(Error, ParseError)
|
||||
struct ParseError : public Error {
|
||||
ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass, bool print_help = true)
|
||||
: Error(parent, name, exit_code, print_help) {}
|
||||
};
|
||||
|
||||
// Not really "errors"
|
||||
|
||||
/// This is a successful completion on parsing, supposed to exit
|
||||
class Success : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, Success)
|
||||
Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
|
||||
struct Success : public ParseError {
|
||||
Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success, false) {}
|
||||
};
|
||||
|
||||
/// -h or --help on command line
|
||||
class CallForHelp : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, CallForHelp)
|
||||
CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
|
||||
};
|
||||
|
||||
/// Usually something like --help-all on command line
|
||||
class CallForAllHelp : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, CallForAllHelp)
|
||||
CallForAllHelp()
|
||||
: CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
|
||||
};
|
||||
|
||||
/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
|
||||
class RuntimeError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, RuntimeError)
|
||||
explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {}
|
||||
struct CallForHelp : public ParseError {
|
||||
CallForHelp()
|
||||
: ParseError("CallForHelp", "This should be caught in your main function, see examples", ExitCodes::Success) {}
|
||||
};
|
||||
|
||||
/// Thrown when parsing an INI file and it is missing
|
||||
class FileError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, FileError)
|
||||
CLI11_ERROR_SIMPLE(FileError)
|
||||
static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); }
|
||||
struct FileError : public ParseError {
|
||||
FileError(std::string name) : ParseError("FileError", name, ExitCodes::File) {}
|
||||
};
|
||||
|
||||
/// Thrown when conversion call back fails, such as when an int fails to coerce to a string
|
||||
class ConversionError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ConversionError)
|
||||
CLI11_ERROR_SIMPLE(ConversionError)
|
||||
ConversionError(std::string member, std::string name)
|
||||
: ConversionError("The value " + member + " is not an allowed value for " + name) {}
|
||||
ConversionError(std::string name, std::vector<std::string> results)
|
||||
: ConversionError("Could not convert: " + name + " = " + detail::join(results)) {}
|
||||
static ConversionError TooManyInputsFlag(std::string name) {
|
||||
return ConversionError(name + ": too many inputs for a flag");
|
||||
}
|
||||
static ConversionError TrueFalse(std::string name) {
|
||||
return ConversionError(name + ": Should be true/false or a number");
|
||||
}
|
||||
/// Thrown when conversion call back fails, such as when an int fails to coerse to a string
|
||||
struct ConversionError : public ParseError {
|
||||
ConversionError(std::string name) : ParseError("ConversionError", name, ExitCodes::Conversion) {}
|
||||
};
|
||||
|
||||
/// Thrown when validation of results fails
|
||||
class ValidationError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ValidationError)
|
||||
CLI11_ERROR_SIMPLE(ValidationError)
|
||||
explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {}
|
||||
struct ValidationError : public ParseError {
|
||||
ValidationError(std::string name) : ParseError("ValidationError", name, ExitCodes::Validation) {}
|
||||
};
|
||||
|
||||
/// Thrown when a required option is missing
|
||||
class RequiredError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, RequiredError)
|
||||
explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {}
|
||||
static RequiredError Subcommand(std::size_t min_subcom) {
|
||||
if(min_subcom == 1) {
|
||||
return RequiredError("A subcommand");
|
||||
}
|
||||
return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
|
||||
ExitCodes::RequiredError);
|
||||
}
|
||||
static RequiredError
|
||||
Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
|
||||
if((min_option == 1) && (max_option == 1) && (used == 0))
|
||||
return RequiredError("Exactly 1 option from [" + option_list + "]");
|
||||
if((min_option == 1) && (max_option == 1) && (used > 1)) {
|
||||
return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
|
||||
" were given",
|
||||
ExitCodes::RequiredError);
|
||||
}
|
||||
if((min_option == 1) && (used == 0))
|
||||
return RequiredError("At least 1 option from [" + option_list + "]");
|
||||
if(used < min_option) {
|
||||
return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
|
||||
std::to_string(used) + "were given from [" + option_list + "]",
|
||||
ExitCodes::RequiredError);
|
||||
}
|
||||
if(max_option == 1)
|
||||
return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
|
||||
ExitCodes::RequiredError);
|
||||
|
||||
return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
|
||||
std::to_string(used) + "were given from [" + option_list + "]",
|
||||
ExitCodes::RequiredError);
|
||||
}
|
||||
};
|
||||
|
||||
/// Thrown when the wrong number of arguments has been received
|
||||
class ArgumentMismatch : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ArgumentMismatch)
|
||||
CLI11_ERROR_SIMPLE(ArgumentMismatch)
|
||||
ArgumentMismatch(std::string name, int expected, std::size_t received)
|
||||
: ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name +
|
||||
", got " + std::to_string(received))
|
||||
: ("Expected at least " + std::to_string(-expected) + " arguments to " + name +
|
||||
", got " + std::to_string(received)),
|
||||
ExitCodes::ArgumentMismatch) {}
|
||||
|
||||
static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) {
|
||||
return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
|
||||
std::to_string(received));
|
||||
}
|
||||
static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) {
|
||||
return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
|
||||
std::to_string(received));
|
||||
}
|
||||
static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
|
||||
return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
|
||||
}
|
||||
static ArgumentMismatch FlagOverride(std::string name) {
|
||||
return ArgumentMismatch(name + " was given a disallowed flag override");
|
||||
}
|
||||
struct RequiredError : public ParseError {
|
||||
RequiredError(std::string name) : ParseError("RequiredError", name, ExitCodes::Required) {}
|
||||
};
|
||||
|
||||
/// Thrown when a requires option is missing
|
||||
class RequiresError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, RequiresError)
|
||||
RequiresError(std::string curname, std::string subname)
|
||||
: RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
|
||||
struct RequiresError : public ParseError {
|
||||
RequiresError(std::string name, std::string subname)
|
||||
: ParseError("RequiresError", name + " requires " + subname, ExitCodes::Requires) {}
|
||||
};
|
||||
|
||||
/// Thrown when an excludes option is present
|
||||
class ExcludesError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ExcludesError)
|
||||
ExcludesError(std::string curname, std::string subname)
|
||||
: ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
|
||||
/// Thrown when a exludes option is present
|
||||
struct ExcludesError : public ParseError {
|
||||
ExcludesError(std::string name, std::string subname)
|
||||
: ParseError("ExcludesError", name + " excludes " + subname, ExitCodes::Excludes) {}
|
||||
};
|
||||
|
||||
/// Thrown when too many positionals or options are found
|
||||
class ExtrasError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ExtrasError)
|
||||
explicit ExtrasError(std::vector<std::string> args)
|
||||
: ExtrasError((args.size() > 1 ? "The following arguments were not expected: "
|
||||
: "The following argument was not expected: ") +
|
||||
detail::rjoin(args, " "),
|
||||
ExitCodes::ExtrasError) {}
|
||||
ExtrasError(const std::string &name, std::vector<std::string> args)
|
||||
: ExtrasError(name,
|
||||
(args.size() > 1 ? "The following arguments were not expected: "
|
||||
: "The following argument was not expected: ") +
|
||||
detail::rjoin(args, " "),
|
||||
ExitCodes::ExtrasError) {}
|
||||
struct ExtrasError : public ParseError {
|
||||
ExtrasError(std::string name) : ParseError("ExtrasError", name, ExitCodes::Extras) {}
|
||||
};
|
||||
|
||||
/// Thrown when extra values are found in an INI file
|
||||
class ConfigError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, ConfigError)
|
||||
CLI11_ERROR_SIMPLE(ConfigError)
|
||||
static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
|
||||
static ConfigError NotConfigurable(std::string item) {
|
||||
return ConfigError(item + ": This option is not allowed in a configuration file");
|
||||
}
|
||||
struct ExtrasINIError : public ParseError {
|
||||
ExtrasINIError(std::string name) : ParseError("ExtrasINIError", name, ExitCodes::ExtrasINI) {}
|
||||
};
|
||||
|
||||
/// Thrown when validation fails before parsing
|
||||
class InvalidError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, InvalidError)
|
||||
explicit InvalidError(std::string name)
|
||||
: InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) {
|
||||
}
|
||||
struct InvalidError : public ParseError {
|
||||
InvalidError(std::string name) : ParseError("InvalidError", name, ExitCodes::Invalid) {}
|
||||
};
|
||||
|
||||
/// This is just a safety check to verify selection and parsing match - you should not ever see it
|
||||
/// Strings are directly added to this error, but again, it should never be seen.
|
||||
class HorribleError : public ParseError {
|
||||
CLI11_ERROR_DEF(ParseError, HorribleError)
|
||||
CLI11_ERROR_SIMPLE(HorribleError)
|
||||
/// This is just a safety check to verify selection and parsing match
|
||||
struct HorribleError : public ParseError {
|
||||
HorribleError(std::string name)
|
||||
: ParseError("HorribleError", "(You should never see this error) " + name, ExitCodes::Horrible) {}
|
||||
};
|
||||
|
||||
// After parsing
|
||||
|
||||
/// Thrown when counting a non-existent option
|
||||
class OptionNotFound : public Error {
|
||||
CLI11_ERROR_DEF(Error, OptionNotFound)
|
||||
explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {}
|
||||
struct OptionNotFound : public Error {
|
||||
OptionNotFound(std::string name) : Error("OptionNotFound", name, ExitCodes::OptionNotFound) {}
|
||||
};
|
||||
|
||||
#undef CLI11_ERROR_DEF
|
||||
#undef CLI11_ERROR_SIMPLE
|
||||
|
||||
/// @}
|
||||
|
||||
} // namespace CLI
|
||||
} // namespace CLI
|
||||
|
|
|
|||
|
|
@ -1,281 +0,0 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "App.hpp"
|
||||
#include "FormatterFwd.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
inline std::string
|
||||
Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
|
||||
std::stringstream out;
|
||||
|
||||
out << "\n" << group << ":\n";
|
||||
for(const Option *opt : opts) {
|
||||
out << make_option(opt, is_positional);
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_positionals(const App *app) const {
|
||||
std::vector<const Option *> opts =
|
||||
app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
|
||||
|
||||
if(opts.empty())
|
||||
return std::string();
|
||||
|
||||
return make_group(get_label("Positionals"), true, opts);
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
|
||||
// Options
|
||||
for(const std::string &group : groups) {
|
||||
std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
|
||||
return opt->get_group() == group // Must be in the right group
|
||||
&& opt->nonpositional() // Must not be a positional
|
||||
&& (mode != AppFormatMode::Sub // If mode is Sub, then
|
||||
|| (app->get_help_ptr() != opt // Ignore help pointer
|
||||
&& app->get_help_all_ptr() != opt)); // Ignore help all pointer
|
||||
});
|
||||
if(!group.empty() && !opts.empty()) {
|
||||
out << make_group(group, false, opts);
|
||||
|
||||
if(group != groups.back())
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_description(const App *app) const {
|
||||
std::string desc = app->get_description();
|
||||
auto min_options = app->get_require_option_min();
|
||||
auto max_options = app->get_require_option_max();
|
||||
if(app->get_required()) {
|
||||
desc += " REQUIRED ";
|
||||
}
|
||||
if((max_options == min_options) && (min_options > 0)) {
|
||||
if(min_options == 1) {
|
||||
desc += " \n[Exactly 1 of the following options is required]";
|
||||
} else {
|
||||
desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]";
|
||||
}
|
||||
} else if(max_options > 0) {
|
||||
if(min_options > 0) {
|
||||
desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
|
||||
" of the follow options are required]";
|
||||
} else {
|
||||
desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
|
||||
}
|
||||
} else if(min_options > 0) {
|
||||
desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
|
||||
}
|
||||
return (!desc.empty()) ? desc + "\n" : std::string{};
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_usage(const App *app, std::string name) const {
|
||||
std::stringstream out;
|
||||
|
||||
out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
|
||||
|
||||
std::vector<std::string> groups = app->get_groups();
|
||||
|
||||
// Print an Options badge if any options exist
|
||||
std::vector<const Option *> non_pos_options =
|
||||
app->get_options([](const Option *opt) { return opt->nonpositional(); });
|
||||
if(!non_pos_options.empty())
|
||||
out << " [" << get_label("OPTIONS") << "]";
|
||||
|
||||
// Positionals need to be listed here
|
||||
std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
|
||||
|
||||
// Print out positionals if any are left
|
||||
if(!positionals.empty()) {
|
||||
// Convert to help names
|
||||
std::vector<std::string> positional_names(positionals.size());
|
||||
std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
|
||||
return make_option_usage(opt);
|
||||
});
|
||||
|
||||
out << " " << detail::join(positional_names, " ");
|
||||
}
|
||||
|
||||
// Add a marker if subcommands are expected or optional
|
||||
if(!app->get_subcommands(
|
||||
[](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
|
||||
.empty()) {
|
||||
out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
|
||||
<< get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
|
||||
: "SUBCOMMANDS")
|
||||
<< (app->get_require_subcommand_min() == 0 ? "]" : "");
|
||||
}
|
||||
|
||||
out << std::endl;
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_footer(const App *app) const {
|
||||
std::string footer = app->get_footer();
|
||||
if(footer.empty()) {
|
||||
return std::string{};
|
||||
}
|
||||
return footer + "\n";
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
|
||||
|
||||
// This immediately forwards to the make_expanded method. This is done this way so that subcommands can
|
||||
// have overridden formatters
|
||||
if(mode == AppFormatMode::Sub)
|
||||
return make_expanded(app);
|
||||
|
||||
std::stringstream out;
|
||||
if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
|
||||
if(app->get_group() != "Subcommands") {
|
||||
out << app->get_group() << ':';
|
||||
}
|
||||
}
|
||||
|
||||
out << make_description(app);
|
||||
out << make_usage(app, name);
|
||||
out << make_positionals(app);
|
||||
out << make_groups(app, mode);
|
||||
out << make_subcommands(app, mode);
|
||||
out << '\n' << make_footer(app);
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
|
||||
std::stringstream out;
|
||||
|
||||
std::vector<const App *> subcommands = app->get_subcommands({});
|
||||
|
||||
// Make a list in definition order of the groups seen
|
||||
std::vector<std::string> subcmd_groups_seen;
|
||||
for(const App *com : subcommands) {
|
||||
if(com->get_name().empty()) {
|
||||
if(!com->get_group().empty()) {
|
||||
out << make_expanded(com);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
std::string group_key = com->get_group();
|
||||
if(!group_key.empty() &&
|
||||
std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
|
||||
return detail::to_lower(a) == detail::to_lower(group_key);
|
||||
}) == subcmd_groups_seen.end())
|
||||
subcmd_groups_seen.push_back(group_key);
|
||||
}
|
||||
|
||||
// For each group, filter out and print subcommands
|
||||
for(const std::string &group : subcmd_groups_seen) {
|
||||
out << "\n" << group << ":\n";
|
||||
std::vector<const App *> subcommands_group = app->get_subcommands(
|
||||
[&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
|
||||
for(const App *new_com : subcommands_group) {
|
||||
if(new_com->get_name().empty())
|
||||
continue;
|
||||
if(mode != AppFormatMode::All) {
|
||||
out << make_subcommand(new_com);
|
||||
} else {
|
||||
out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_subcommand(const App *sub) const {
|
||||
std::stringstream out;
|
||||
detail::format_help(out, sub->get_name(), sub->get_description(), column_width_);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_expanded(const App *sub) const {
|
||||
std::stringstream out;
|
||||
out << sub->get_display_name() << "\n";
|
||||
|
||||
out << make_description(sub);
|
||||
out << make_positionals(sub);
|
||||
out << make_groups(sub, AppFormatMode::Sub);
|
||||
out << make_subcommands(sub, AppFormatMode::Sub);
|
||||
|
||||
// Drop blank spaces
|
||||
std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n");
|
||||
tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n'
|
||||
|
||||
// Indent all but the first line (the name)
|
||||
return detail::find_and_replace(tmp, "\n", "\n ") + "\n";
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
|
||||
if(is_positional)
|
||||
return opt->get_name(true, false);
|
||||
|
||||
return opt->get_name(false, true);
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_opts(const Option *opt) const {
|
||||
std::stringstream out;
|
||||
|
||||
if(opt->get_type_size() != 0) {
|
||||
if(!opt->get_type_name().empty())
|
||||
out << " " << get_label(opt->get_type_name());
|
||||
if(!opt->get_default_str().empty())
|
||||
out << "=" << opt->get_default_str();
|
||||
if(opt->get_expected_max() == detail::expected_max_vector_size)
|
||||
out << " ...";
|
||||
else if(opt->get_expected_min() > 1)
|
||||
out << " x " << opt->get_expected();
|
||||
|
||||
if(opt->get_required())
|
||||
out << " " << get_label("REQUIRED");
|
||||
}
|
||||
if(!opt->get_envname().empty())
|
||||
out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
|
||||
if(!opt->get_needs().empty()) {
|
||||
out << " " << get_label("Needs") << ":";
|
||||
for(const Option *op : opt->get_needs())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
if(!opt->get_excludes().empty()) {
|
||||
out << " " << get_label("Excludes") << ":";
|
||||
for(const Option *op : opt->get_excludes())
|
||||
out << " " << op->get_name();
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
|
||||
|
||||
inline std::string Formatter::make_option_usage(const Option *opt) const {
|
||||
// Note that these are positionals usages
|
||||
std::stringstream out;
|
||||
out << make_option_name(opt, true);
|
||||
if(opt->get_expected_max() >= detail::expected_max_vector_size)
|
||||
out << "...";
|
||||
else if(opt->get_expected_max() > 1)
|
||||
out << "(" << opt->get_expected() << "x)";
|
||||
|
||||
return opt->get_required() ? out.str() : "[" + out.str() + "]";
|
||||
}
|
||||
|
||||
} // namespace CLI
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
|
||||
class Option;
|
||||
class App;
|
||||
|
||||
/// This enum signifies the type of help requested
|
||||
///
|
||||
/// This is passed in by App; all user classes must accept this as
|
||||
/// the second argument.
|
||||
|
||||
enum class AppFormatMode {
|
||||
Normal, ///< The normal, detailed help
|
||||
All, ///< A fully expanded help
|
||||
Sub, ///< Used when printed as part of expanded subcommand
|
||||
};
|
||||
|
||||
/// This is the minimum requirements to run a formatter.
|
||||
///
|
||||
/// A user can subclass this is if they do not care at all
|
||||
/// about the structure in CLI::Formatter.
|
||||
class FormatterBase {
|
||||
protected:
|
||||
/// @name Options
|
||||
///@{
|
||||
|
||||
/// The width of the first column
|
||||
std::size_t column_width_{30};
|
||||
|
||||
/// @brief The required help printout labels (user changeable)
|
||||
/// Values are Needs, Excludes, etc.
|
||||
std::map<std::string, std::string> labels_{};
|
||||
|
||||
///@}
|
||||
/// @name Basic
|
||||
///@{
|
||||
|
||||
public:
|
||||
FormatterBase() = default;
|
||||
FormatterBase(const FormatterBase &) = default;
|
||||
FormatterBase(FormatterBase &&) = default;
|
||||
|
||||
/// Adding a destructor in this form to work around bug in GCC 4.7
|
||||
virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default)
|
||||
|
||||
/// This is the key method that puts together help
|
||||
virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0;
|
||||
|
||||
///@}
|
||||
/// @name Setters
|
||||
///@{
|
||||
|
||||
/// Set the "REQUIRED" label
|
||||
void label(std::string key, std::string val) { labels_[key] = val; }
|
||||
|
||||
/// Set the column width
|
||||
void column_width(std::size_t val) { column_width_ = val; }
|
||||
|
||||
///@}
|
||||
/// @name Getters
|
||||
///@{
|
||||
|
||||
/// Get the current value of a name (REQUIRED, etc.)
|
||||
std::string get_label(std::string key) const {
|
||||
if(labels_.find(key) == labels_.end())
|
||||
return key;
|
||||
else
|
||||
return labels_.at(key);
|
||||
}
|
||||
|
||||
/// Get the current column width
|
||||
std::size_t get_column_width() const { return column_width_; }
|
||||
|
||||
///@}
|
||||
};
|
||||
|
||||
/// This is a specialty override for lambda functions
|
||||
class FormatterLambda final : public FormatterBase {
|
||||
using funct_t = std::function<std::string(const App *, std::string, AppFormatMode)>;
|
||||
|
||||
/// The lambda to hold and run
|
||||
funct_t lambda_;
|
||||
|
||||
public:
|
||||
/// Create a FormatterLambda with a lambda function
|
||||
explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
|
||||
|
||||
/// Adding a destructor (mostly to make GCC 4.7 happy)
|
||||
~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default)
|
||||
|
||||
/// This will simply call the lambda function
|
||||
std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {
|
||||
return lambda_(app, name, mode);
|
||||
}
|
||||
};
|
||||
|
||||
/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few
|
||||
/// overridable methods, to be highly customizable with minimal effort.
|
||||
class Formatter : public FormatterBase {
|
||||
public:
|
||||
Formatter() = default;
|
||||
Formatter(const Formatter &) = default;
|
||||
Formatter(Formatter &&) = default;
|
||||
|
||||
/// @name Overridables
|
||||
///@{
|
||||
|
||||
/// This prints out a group of options with title
|
||||
///
|
||||
virtual std::string make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
|
||||
|
||||
/// This prints out just the positionals "group"
|
||||
virtual std::string make_positionals(const App *app) const;
|
||||
|
||||
/// This prints out all the groups of options
|
||||
std::string make_groups(const App *app, AppFormatMode mode) const;
|
||||
|
||||
/// This prints out all the subcommands
|
||||
virtual std::string make_subcommands(const App *app, AppFormatMode mode) const;
|
||||
|
||||
/// This prints out a subcommand
|
||||
virtual std::string make_subcommand(const App *sub) const;
|
||||
|
||||
/// This prints out a subcommand in help-all
|
||||
virtual std::string make_expanded(const App *sub) const;
|
||||
|
||||
/// This prints out all the groups of options
|
||||
virtual std::string make_footer(const App *app) const;
|
||||
|
||||
/// This displays the description line
|
||||
virtual std::string make_description(const App *app) const;
|
||||
|
||||
/// This displays the usage line
|
||||
virtual std::string make_usage(const App *app, std::string name) const;
|
||||
|
||||
/// This puts everything together
|
||||
std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override;
|
||||
|
||||
///@}
|
||||
/// @name Options
|
||||
///@{
|
||||
|
||||
/// This prints out an option help line, either positional or optional form
|
||||
virtual std::string make_option(const Option *opt, bool is_positional) const {
|
||||
std::stringstream out;
|
||||
detail::format_help(
|
||||
out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/// @brief This is the name part of an option, Default: left column
|
||||
virtual std::string make_option_name(const Option *, bool) const;
|
||||
|
||||
/// @brief This is the options part of the name, Default: combined into left column
|
||||
virtual std::string make_option_opts(const Option *) const;
|
||||
|
||||
/// @brief This is the description. Default: Right column, on new line if left column too large
|
||||
virtual std::string make_option_desc(const Option *) const;
|
||||
|
||||
/// @brief This is used to print the name on the USAGE line
|
||||
virtual std::string make_option_usage(const Option *opt) const;
|
||||
|
||||
///@}
|
||||
};
|
||||
|
||||
} // namespace CLI
|
||||
115
ext/CLI11/CLI/Ini.hpp
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#pragma once
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "CLI/StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
namespace detail {
|
||||
|
||||
inline std::string inijoin(std::vector<std::string> args) {
|
||||
std::ostringstream s;
|
||||
size_t start = 0;
|
||||
for(const auto &arg : args) {
|
||||
if(start++ > 0)
|
||||
s << " ";
|
||||
|
||||
auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); });
|
||||
if(it == arg.end())
|
||||
s << arg;
|
||||
else if(arg.find(R"(")") == std::string::npos)
|
||||
s << R"(")" << arg << R"(")";
|
||||
else
|
||||
s << R"(')" << arg << R"(')";
|
||||
}
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
struct ini_ret_t {
|
||||
/// This is the full name with dots
|
||||
std::string fullname;
|
||||
|
||||
/// Listing of inputs
|
||||
std::vector<std::string> inputs;
|
||||
|
||||
/// Current parent level
|
||||
size_t level = 0;
|
||||
|
||||
/// Return parent or empty string, based on level
|
||||
///
|
||||
/// Level 0, a.b.c would return a
|
||||
/// Level 1, a.b.c could return b
|
||||
std::string parent() const {
|
||||
std::vector<std::string> plist = detail::split(fullname, '.');
|
||||
if(plist.size() > (level + 1))
|
||||
return plist[level];
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// Return name
|
||||
std::string name() const {
|
||||
std::vector<std::string> plist = detail::split(fullname, '.');
|
||||
return plist.at(plist.size() - 1);
|
||||
}
|
||||
};
|
||||
|
||||
/// Internal parsing function
|
||||
inline std::vector<ini_ret_t> parse_ini(std::istream &input) {
|
||||
std::string name, line;
|
||||
std::string section = "default";
|
||||
|
||||
std::vector<ini_ret_t> output;
|
||||
|
||||
while(getline(input, line)) {
|
||||
std::vector<std::string> items;
|
||||
|
||||
detail::trim(line);
|
||||
size_t len = line.length();
|
||||
if(len > 1 && line[0] == '[' && line[len - 1] == ']') {
|
||||
section = line.substr(1, len - 2);
|
||||
} else if(len > 0 && line[0] != ';') {
|
||||
output.emplace_back();
|
||||
ini_ret_t &out = output.back();
|
||||
|
||||
// Find = in string, split and recombine
|
||||
auto pos = line.find("=");
|
||||
if(pos != std::string::npos) {
|
||||
name = detail::trim_copy(line.substr(0, pos));
|
||||
std::string item = detail::trim_copy(line.substr(pos + 1));
|
||||
items = detail::split_up(item);
|
||||
} else {
|
||||
name = detail::trim_copy(line);
|
||||
items = {"ON"};
|
||||
}
|
||||
|
||||
if(detail::to_lower(section) == "default")
|
||||
out.fullname = name;
|
||||
else
|
||||
out.fullname = section + "." + name;
|
||||
|
||||
out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items));
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/// Parse an INI file, throw an error (ParseError:INIParseError or FileError) on failure
|
||||
inline std::vector<ini_ret_t> parse_ini(const std::string &name) {
|
||||
|
||||
std::ifstream input{name};
|
||||
if(!input.good())
|
||||
throw FileError(name);
|
||||
|
||||
return parse_ini(input);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace CLI
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// [CLI11:verbatim]
|
||||
|
||||
// The following version macro is very similar to the one in PyBind11
|
||||
#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER)
|
||||
#if __cplusplus >= 201402L
|
||||
#define CLI11_CPP14
|
||||
#if __cplusplus >= 201703L
|
||||
#define CLI11_CPP17
|
||||
#if __cplusplus > 201703L
|
||||
#define CLI11_CPP20
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(_MSC_VER) && __cplusplus == 199711L
|
||||
// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented)
|
||||
// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer
|
||||
#if _MSVC_LANG >= 201402L
|
||||
#define CLI11_CPP14
|
||||
#if _MSVC_LANG > 201402L && _MSC_VER >= 1910
|
||||
#define CLI11_CPP17
|
||||
#if __MSVC_LANG > 201703L && _MSC_VER >= 1910
|
||||
#define CLI11_CPP20
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(CLI11_CPP14)
|
||||
#define CLI11_DEPRECATED(reason) [[deprecated(reason)]]
|
||||
#elif defined(_MSC_VER)
|
||||
#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason))
|
||||
#else
|
||||
#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
|
||||
#endif
|
||||
|
||||
// [CLI11:verbatim]
|
||||
|
|
@ -1,18 +1,14 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Error.hpp"
|
||||
#include "StringTools.hpp"
|
||||
#include "CLI/Error.hpp"
|
||||
#include "CLI/StringTools.hpp"
|
||||
|
||||
namespace CLI {
|
||||
namespace detail {
|
||||
|
|
@ -23,14 +19,14 @@ inline bool split_short(const std::string ¤t, std::string &name, std::stri
|
|||
name = current.substr(1, 1);
|
||||
rest = current.substr(2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
|
||||
inline bool split_long(const std::string ¤t, std::string &name, std::string &value) {
|
||||
if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) {
|
||||
auto loc = current.find_first_of('=');
|
||||
auto loc = current.find("=");
|
||||
if(loc != std::string::npos) {
|
||||
name = current.substr(2, loc - 2);
|
||||
value = current.substr(loc + 1);
|
||||
|
|
@ -39,62 +35,19 @@ inline bool split_long(const std::string ¤t, std::string &name, std::strin
|
|||
value = "";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
|
||||
inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) {
|
||||
if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
|
||||
auto loc = current.find_first_of(':');
|
||||
if(loc != std::string::npos) {
|
||||
name = current.substr(1, loc - 1);
|
||||
value = current.substr(loc + 1);
|
||||
} else {
|
||||
name = current.substr(1);
|
||||
value = "";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Splits a string into multiple long and short names
|
||||
inline std::vector<std::string> split_names(std::string current) {
|
||||
std::vector<std::string> output;
|
||||
std::size_t val;
|
||||
size_t val;
|
||||
while((val = current.find(",")) != std::string::npos) {
|
||||
output.push_back(trim_copy(current.substr(0, val)));
|
||||
output.push_back(current.substr(0, val));
|
||||
current = current.substr(val + 1);
|
||||
}
|
||||
output.push_back(trim_copy(current));
|
||||
return output;
|
||||
}
|
||||
|
||||
/// extract default flag values either {def} or starting with a !
|
||||
inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
|
||||
std::vector<std::string> flags = split_names(str);
|
||||
flags.erase(std::remove_if(flags.begin(),
|
||||
flags.end(),
|
||||
[](const std::string &name) {
|
||||
return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
|
||||
(name.back() == '}')) ||
|
||||
(name[0] == '!'))));
|
||||
}),
|
||||
flags.end());
|
||||
std::vector<std::pair<std::string, std::string>> output;
|
||||
output.reserve(flags.size());
|
||||
for(auto &flag : flags) {
|
||||
auto def_start = flag.find_first_of('{');
|
||||
std::string defval = "false";
|
||||
if((def_start != std::string::npos) && (flag.back() == '}')) {
|
||||
defval = flag.substr(def_start + 1);
|
||||
defval.pop_back();
|
||||
flag.erase(def_start, std::string::npos);
|
||||
}
|
||||
flag.erase(0, flag.find_first_not_of("-!"));
|
||||
output.emplace_back(flag, defval);
|
||||
}
|
||||
output.push_back(current);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
|
@ -107,25 +60,24 @@ get_names(const std::vector<std::string> &input) {
|
|||
std::string pos_name;
|
||||
|
||||
for(std::string name : input) {
|
||||
if(name.length() == 0) {
|
||||
if(name.length() == 0)
|
||||
continue;
|
||||
}
|
||||
if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
|
||||
else if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
|
||||
if(name.length() == 2 && valid_first_char(name[1]))
|
||||
short_names.emplace_back(1, name[1]);
|
||||
else
|
||||
throw BadNameString::OneCharName(name);
|
||||
throw BadNameString("Invalid one char name: " + name);
|
||||
} else if(name.length() > 2 && name.substr(0, 2) == "--") {
|
||||
name = name.substr(2);
|
||||
if(valid_name_string(name))
|
||||
long_names.push_back(name);
|
||||
else
|
||||
throw BadNameString::BadLongName(name);
|
||||
throw BadNameString("Bad long name: " + name);
|
||||
} else if(name == "-" || name == "--") {
|
||||
throw BadNameString::DashesOnly(name);
|
||||
throw BadNameString("Must have a name, not just dashes");
|
||||
} else {
|
||||
if(pos_name.length() > 0)
|
||||
throw BadNameString::MultiPositionalNames(name);
|
||||
throw BadNameString("Only one positional name allowed, remove: " + name);
|
||||
pos_name = name;
|
||||
}
|
||||
}
|
||||
|
|
@ -134,5 +86,5 @@ get_names(const std::vector<std::string> &input) {
|
|||
short_names, long_names, pos_name);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace CLI
|
||||
} // namespace detail
|
||||
} // namespace CLI
|
||||
|
|
|
|||
|
|
@ -1,50 +1,26 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace CLI {
|
||||
|
||||
/// Include the items in this namespace to get free conversion of enums to/from streams.
|
||||
/// (This is available inside CLI as well, so CLI11 will use this without a using statement).
|
||||
namespace enums {
|
||||
|
||||
/// output streaming for enumerations
|
||||
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
std::ostream &operator<<(std::ostream &in, const T &item) {
|
||||
// make sure this is out of the detail namespace otherwise it won't be found when needed
|
||||
return in << static_cast<typename std::underlying_type<T>::type>(item);
|
||||
}
|
||||
|
||||
} // namespace enums
|
||||
|
||||
/// Export to CLI namespace
|
||||
using enums::operator<<;
|
||||
|
||||
namespace detail {
|
||||
/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not
|
||||
/// produce overflow for some expected uses
|
||||
constexpr int expected_max_vector_size{1 << 29};
|
||||
|
||||
// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
|
||||
/// Split a string by a delim
|
||||
inline std::vector<std::string> split(const std::string &s, char delim) {
|
||||
std::vector<std::string> elems;
|
||||
// Check to see if empty string, give consistent result
|
||||
if(s.empty()) {
|
||||
elems.emplace_back();
|
||||
} else {
|
||||
// Check to see if emtpy string, give consistent result
|
||||
if(s == "")
|
||||
elems.emplace_back("");
|
||||
else {
|
||||
std::stringstream ss;
|
||||
ss.str(s);
|
||||
std::string item;
|
||||
|
|
@ -58,28 +34,11 @@ inline std::vector<std::string> split(const std::string &s, char delim) {
|
|||
/// Simple function to join a string
|
||||
template <typename T> std::string join(const T &v, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
auto beg = std::begin(v);
|
||||
auto end = std::end(v);
|
||||
if(beg != end)
|
||||
s << *beg++;
|
||||
while(beg != end) {
|
||||
s << delim << *beg++;
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
/// Simple function to join a string from processed elements
|
||||
template <typename T,
|
||||
typename Callable,
|
||||
typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
|
||||
std::string join(const T &v, Callable func, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
auto beg = std::begin(v);
|
||||
auto end = std::end(v);
|
||||
if(beg != end)
|
||||
s << func(*beg++);
|
||||
while(beg != end) {
|
||||
s << delim << func(*beg++);
|
||||
size_t start = 0;
|
||||
for(const auto &i : v) {
|
||||
if(start++ > 0)
|
||||
s << delim;
|
||||
s << i;
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
|
@ -87,7 +46,7 @@ std::string join(const T &v, Callable func, std::string delim = ",") {
|
|||
/// Join a string in reverse order
|
||||
template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
|
||||
std::ostringstream s;
|
||||
for(std::size_t start = 0; start < v.size(); start++) {
|
||||
for(size_t start = 0; start < v.size(); start++) {
|
||||
if(start > 0)
|
||||
s << delim;
|
||||
s << v[v.size() - start - 1];
|
||||
|
|
@ -138,47 +97,30 @@ inline std::string trim_copy(const std::string &str) {
|
|||
return trim(s);
|
||||
}
|
||||
|
||||
/// remove quotes at the front and back of a string either '"' or '\''
|
||||
inline std::string &remove_quotes(std::string &str) {
|
||||
if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
|
||||
if(str.front() == str.back()) {
|
||||
str.pop_back();
|
||||
str.erase(str.begin(), str.begin() + 1);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)
|
||||
inline std::string trim_copy(const std::string &str, const std::string &filter) {
|
||||
std::string s = str;
|
||||
return trim(s, filter);
|
||||
}
|
||||
/// Print a two part "help" string
|
||||
inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) {
|
||||
inline void format_help(std::stringstream &out, std::string name, std::string description, size_t wid) {
|
||||
name = " " + name;
|
||||
out << std::setw(static_cast<int>(wid)) << std::left << name;
|
||||
if(!description.empty()) {
|
||||
if(description != "") {
|
||||
if(name.length() >= wid)
|
||||
out << "\n" << std::setw(static_cast<int>(wid)) << "";
|
||||
for(const char c : description) {
|
||||
out.put(c);
|
||||
if(c == '\n') {
|
||||
out << std::setw(static_cast<int>(wid)) << "";
|
||||
}
|
||||
}
|
||||
out << std::endl << std::setw(static_cast<int>(wid)) << "";
|
||||
out << description;
|
||||
}
|
||||
out << "\n";
|
||||
return out;
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
/// Verify the first character of an option
|
||||
template <typename T> bool valid_first_char(T c) {
|
||||
return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@';
|
||||
}
|
||||
template <typename T> bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; }
|
||||
|
||||
/// Verify following characters of an option
|
||||
template <typename T> bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; }
|
||||
template <typename T> bool valid_later_char(T c) {
|
||||
return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-';
|
||||
}
|
||||
|
||||
/// Verify an option name
|
||||
inline bool valid_name_string(const std::string &str) {
|
||||
|
|
@ -190,11 +132,6 @@ inline bool valid_name_string(const std::string &str) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/// Verify that str consists of letters only
|
||||
inline bool isalpha(const std::string &str) {
|
||||
return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
|
||||
}
|
||||
|
||||
/// Return a lower case version of a string
|
||||
inline std::string to_lower(std::string str) {
|
||||
std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
|
||||
|
|
@ -203,105 +140,18 @@ inline std::string to_lower(std::string str) {
|
|||
return str;
|
||||
}
|
||||
|
||||
/// remove underscores from a string
|
||||
inline std::string remove_underscore(std::string str) {
|
||||
str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Find and replace a substring with another substring
|
||||
inline std::string find_and_replace(std::string str, std::string from, std::string to) {
|
||||
|
||||
std::size_t start_pos = 0;
|
||||
|
||||
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// check if the flag definitions has possible false flags
|
||||
inline bool has_default_flag_values(const std::string &flags) {
|
||||
return (flags.find_first_of("{!") != std::string::npos);
|
||||
}
|
||||
|
||||
inline void remove_default_flag_values(std::string &flags) {
|
||||
auto loc = flags.find_first_of('{');
|
||||
while(loc != std::string::npos) {
|
||||
auto finish = flags.find_first_of("},", loc + 1);
|
||||
if((finish != std::string::npos) && (flags[finish] == '}')) {
|
||||
flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
|
||||
flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
|
||||
}
|
||||
loc = flags.find_first_of('{', loc + 1);
|
||||
}
|
||||
flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
|
||||
}
|
||||
|
||||
/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
|
||||
inline std::ptrdiff_t find_member(std::string name,
|
||||
const std::vector<std::string> names,
|
||||
bool ignore_case = false,
|
||||
bool ignore_underscore = false) {
|
||||
auto it = std::end(names);
|
||||
if(ignore_case) {
|
||||
if(ignore_underscore) {
|
||||
name = detail::to_lower(detail::remove_underscore(name));
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::to_lower(detail::remove_underscore(local_name)) == name;
|
||||
});
|
||||
} else {
|
||||
name = detail::to_lower(name);
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::to_lower(local_name) == name;
|
||||
});
|
||||
}
|
||||
|
||||
} else if(ignore_underscore) {
|
||||
name = detail::remove_underscore(name);
|
||||
it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
|
||||
return detail::remove_underscore(local_name) == name;
|
||||
});
|
||||
} else {
|
||||
it = std::find(std::begin(names), std::end(names), name);
|
||||
}
|
||||
|
||||
return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
|
||||
}
|
||||
|
||||
/// Find a trigger string and call a modify callable function that takes the current string and starting position of the
|
||||
/// trigger and returns the position in the string to search for the next trigger string
|
||||
template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
|
||||
std::size_t start_pos = 0;
|
||||
while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
|
||||
start_pos = modify(str, start_pos);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Split a string '"one two" "three"' into 'one two', 'three'
|
||||
/// Quote characters can be ` ' or "
|
||||
inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
|
||||
inline std::vector<std::string> split_up(std::string str) {
|
||||
|
||||
const std::string delims("\'\"`");
|
||||
auto find_ws = [delimiter](char ch) {
|
||||
return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
|
||||
};
|
||||
std::vector<char> delims = {'\'', '\"'};
|
||||
auto find_ws = [](char ch) { return std::isspace<char>(ch, std::locale()); };
|
||||
trim(str);
|
||||
|
||||
std::vector<std::string> output;
|
||||
bool embeddedQuote = false;
|
||||
char keyChar = ' ';
|
||||
|
||||
while(!str.empty()) {
|
||||
if(delims.find_first_of(str[0]) != std::string::npos) {
|
||||
keyChar = str[0];
|
||||
auto end = str.find_first_of(keyChar, 1);
|
||||
while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
|
||||
end = str.find_first_of(keyChar, end + 1);
|
||||
embeddedQuote = true;
|
||||
}
|
||||
if(str[0] == '\'') {
|
||||
auto end = str.find('\'', 1);
|
||||
if(end != std::string::npos) {
|
||||
output.push_back(str.substr(1, end - 1));
|
||||
str = str.substr(end + 1);
|
||||
|
|
@ -309,71 +159,32 @@ inline std::vector<std::string> split_up(std::string str, char delimiter = '\0')
|
|||
output.push_back(str.substr(1));
|
||||
str = "";
|
||||
}
|
||||
} else if(str[0] == '\"') {
|
||||
auto end = str.find('\"', 1);
|
||||
if(end != std::string::npos) {
|
||||
output.push_back(str.substr(1, end - 1));
|
||||
str = str.substr(end + 1);
|
||||
} else {
|
||||
output.push_back(str.substr(1));
|
||||
str = "";
|
||||
}
|
||||
|
||||
} else {
|
||||
auto it = std::find_if(std::begin(str), std::end(str), find_ws);
|
||||
if(it != std::end(str)) {
|
||||
std::string value = std::string(str.begin(), it);
|
||||
output.push_back(value);
|
||||
str = std::string(it + 1, str.end());
|
||||
str = std::string(it, str.end());
|
||||
} else {
|
||||
output.push_back(str);
|
||||
str = "";
|
||||
}
|
||||
}
|
||||
// transform any embedded quotes into the regular character
|
||||
if(embeddedQuote) {
|
||||
output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
|
||||
embeddedQuote = false;
|
||||
}
|
||||
trim(str);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// Add a leader to the beginning of all new lines (nothing is added
|
||||
/// at the start of the first line). `"; "` would be for ini files
|
||||
///
|
||||
/// Can't use Regex, or this would be a subs.
|
||||
inline std::string fix_newlines(const std::string &leader, std::string input) {
|
||||
std::string::size_type n = 0;
|
||||
while(n != std::string::npos && n < input.size()) {
|
||||
n = input.find('\n', n);
|
||||
if(n != std::string::npos) {
|
||||
input = input.substr(0, n + 1) + leader + input.substr(n + 1);
|
||||
n += leader.size();
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/// This function detects an equal or colon followed by an escaped quote after an argument
|
||||
/// then modifies the string to replace the equality with a space. This is needed
|
||||
/// to allow the split up function to work properly and is intended to be used with the find_and_modify function
|
||||
/// the return value is the offset+1 which is required by the find_and_modify function.
|
||||
inline std::size_t escape_detect(std::string &str, std::size_t offset) {
|
||||
auto next = str[offset + 1];
|
||||
if((next == '\"') || (next == '\'') || (next == '`')) {
|
||||
auto astart = str.find_last_of("-/ \"\'`", offset - 1);
|
||||
if(astart != std::string::npos) {
|
||||
if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
|
||||
str[offset] = ' '; // interpret this as a space so the split_up works properly
|
||||
}
|
||||
}
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
/// Add quotes if the string contains spaces
|
||||
inline std::string &add_quotes_if_needed(std::string &str) {
|
||||
if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
|
||||
char quote = str.find('"') < str.find('\'') ? '\'' : '"';
|
||||
if(str.find(' ') != std::string::npos) {
|
||||
str.insert(0, 1, quote);
|
||||
str.append(1, quote);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace CLI
|
||||
} // namespace detail
|
||||
} // namespace CLI
|
||||
|
|
|
|||
|
|
@ -1,19 +1,9 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// On GCC < 4.8, the following define is often missing. Due to the
|
||||
// fact that this library only uses sleep_for, this should be safe
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && __GNUC_MINOR__ < 8
|
||||
#define _GLIBCXX_USE_NANOSLEEP
|
||||
#endif
|
||||
// Distributed under the 3-Clause BSD License. See accompanying
|
||||
// file LICENSE or https://github.com/CLIUtils/CLI11 for details.
|
||||
|
||||
#include <array>
|
||||
#include <chrono> // NOLINT(build/c++11)
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
|
@ -21,7 +11,6 @@
|
|||
|
||||
namespace CLI {
|
||||
|
||||
/// This is a simple timer with pretty printing. Creating the timer starts counting.
|
||||
class Timer {
|
||||
protected:
|
||||
/// This is a typedef to make clocks easier to use
|
||||
|
|
@ -43,7 +32,7 @@ class Timer {
|
|||
time_point start_;
|
||||
|
||||
/// This is the number of times cycles (print divides by this number)
|
||||
std::size_t cycles{1};
|
||||
size_t cycles{1};
|
||||
|
||||
public:
|
||||
/// Standard print function, this one is set by default
|
||||
|
|
@ -57,7 +46,7 @@ class Timer {
|
|||
|
||||
public:
|
||||
/// Standard constructor, can set title and print function
|
||||
explicit Timer(std::string title = "Timer", time_print_t time_print = Simple)
|
||||
Timer(std::string title = "Timer", time_print_t time_print = Simple)
|
||||
: title_(std::move(title)), time_print_(std::move(time_print)), start_(clock::now()) {}
|
||||
|
||||
/// Time a function by running it multiple times. Target time is the len to target.
|
||||
|
|
@ -66,14 +55,14 @@ class Timer {
|
|||
double total_time;
|
||||
|
||||
start_ = clock::now();
|
||||
std::size_t n = 0;
|
||||
size_t n = 0;
|
||||
do {
|
||||
f();
|
||||
std::chrono::duration<double> elapsed = clock::now() - start_;
|
||||
total_time = elapsed.count();
|
||||
} while(n++ < 100u && total_time < target_time);
|
||||
} while(n++ < 100 && total_time < target_time);
|
||||
|
||||
std::string out = make_time_str(total_time / static_cast<double>(n)) + " for " + std::to_string(n) + " tries";
|
||||
std::string out = make_time_str(total_time / n) + " for " + std::to_string(n) + " tries";
|
||||
start_ = start;
|
||||
return out;
|
||||
}
|
||||
|
|
@ -82,18 +71,16 @@ class Timer {
|
|||
std::string make_time_str() const {
|
||||
time_point stop = clock::now();
|
||||
std::chrono::duration<double> elapsed = stop - start_;
|
||||
double time = elapsed.count() / static_cast<double>(cycles);
|
||||
double time = elapsed.count() / cycles;
|
||||
return make_time_str(time);
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
/// This prints out a time string from a time
|
||||
std::string make_time_str(double time) const {
|
||||
auto print_it = [](double x, std::string unit) {
|
||||
const unsigned int buffer_length = 50;
|
||||
std::array<char, buffer_length> buffer;
|
||||
std::snprintf(buffer.data(), buffer_length, "%.5g", x);
|
||||
return buffer.data() + std::string(" ") + unit;
|
||||
char buffer[50];
|
||||
std::snprintf(buffer, 50, "%.5g", x);
|
||||
return buffer + std::string(" ") + unit;
|
||||
};
|
||||
|
||||
if(time < .000001)
|
||||
|
|
@ -105,13 +92,13 @@ class Timer {
|
|||
else
|
||||
return print_it(time, "s");
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
// LCOV_EXCL_END
|
||||
|
||||
/// This is the main function, it creates a string
|
||||
std::string to_string() const { return time_print_(title_, make_time_str()); }
|
||||
|
||||
/// Division sets the number of cycles to divide by (no graphical change)
|
||||
Timer &operator/(std::size_t val) {
|
||||
Timer &operator/(size_t val) {
|
||||
cycles = val;
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -121,14 +108,14 @@ class Timer {
|
|||
class AutoTimer : public Timer {
|
||||
public:
|
||||
/// Reimplementing the constructor is required in GCC 4.7
|
||||
explicit AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
|
||||
AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
|
||||
// GCC 4.7 does not support using inheriting constructors.
|
||||
|
||||
/// This destructor prints the string
|
||||
/// This desctructor prints the string
|
||||
~AutoTimer() { std::cout << to_string() << std::endl; }
|
||||
};
|
||||
|
||||
} // namespace CLI
|
||||
} // namespace CLI
|
||||
|
||||
/// This prints out the time if shifted into a std::cout like stream.
|
||||
inline std::ostream &operator<<(std::ostream &in, const CLI::Timer &timer) { return in << timer.to_string(); }
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner
|
||||
// under NSF AWARD 1414736 and by the respective contributors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
// [CLI11:verbatim]
|
||||
|
||||
#define CLI11_VERSION_MAJOR 1
|
||||
#define CLI11_VERSION_MINOR 9
|
||||
#define CLI11_VERSION_PATCH 1
|
||||
#define CLI11_VERSION "1.9.1"
|
||||
|
||||
// [CLI11:verbatim]
|
||||
2931
ext/doctest.h
1
ext/fmt-5.2.1/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is fmt from https://github.com/fmtlib/fmt
|
||||
278
ext/fmt-5.2.1/include/fmt/color.h
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// Formatting library for C++ - color support
|
||||
//
|
||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COLOR_H_
|
||||
#define FMT_COLOR_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef FMT_DEPRECATED_COLORS
|
||||
|
||||
// color and (v)print_colored are deprecated.
|
||||
enum color { black, red, green, yellow, blue, magenta, cyan, white };
|
||||
FMT_API void vprint_colored(color c, string_view format, format_args args);
|
||||
FMT_API void vprint_colored(color c, wstring_view format, wformat_args args);
|
||||
template <typename... Args>
|
||||
inline void print_colored(color c, string_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint_colored(c, format_str, make_format_args(args...));
|
||||
}
|
||||
template <typename... Args>
|
||||
inline void print_colored(color c, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint_colored(c, format_str, make_format_args<wformat_context>(args...));
|
||||
}
|
||||
|
||||
inline void vprint_colored(color c, string_view format, format_args args) {
|
||||
char escape[] = "\x1b[30m";
|
||||
escape[3] = static_cast<char>('0' + c);
|
||||
std::fputs(escape, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
inline void vprint_colored(color c, wstring_view format, wformat_args args) {
|
||||
wchar_t escape[] = L"\x1b[30m";
|
||||
escape[3] = static_cast<wchar_t>('0' + c);
|
||||
std::fputws(escape, stdout);
|
||||
vprint(format, args);
|
||||
std::fputws(internal::data::WRESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Experimental color support.
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
antique_white = 0xFAEBD7, // rgb(250,235,215)
|
||||
aqua = 0x00FFFF, // rgb(0,255,255)
|
||||
aquamarine = 0x7FFFD4, // rgb(127,255,212)
|
||||
azure = 0xF0FFFF, // rgb(240,255,255)
|
||||
beige = 0xF5F5DC, // rgb(245,245,220)
|
||||
bisque = 0xFFE4C4, // rgb(255,228,196)
|
||||
black = 0x000000, // rgb(0,0,0)
|
||||
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
|
||||
blue = 0x0000FF, // rgb(0,0,255)
|
||||
blue_violet = 0x8A2BE2, // rgb(138,43,226)
|
||||
brown = 0xA52A2A, // rgb(165,42,42)
|
||||
burly_wood = 0xDEB887, // rgb(222,184,135)
|
||||
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
|
||||
chartreuse = 0x7FFF00, // rgb(127,255,0)
|
||||
chocolate = 0xD2691E, // rgb(210,105,30)
|
||||
coral = 0xFF7F50, // rgb(255,127,80)
|
||||
cornflower_blue = 0x6495ED, // rgb(100,149,237)
|
||||
cornsilk = 0xFFF8DC, // rgb(255,248,220)
|
||||
crimson = 0xDC143C, // rgb(220,20,60)
|
||||
cyan = 0x00FFFF, // rgb(0,255,255)
|
||||
dark_blue = 0x00008B, // rgb(0,0,139)
|
||||
dark_cyan = 0x008B8B, // rgb(0,139,139)
|
||||
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
|
||||
dark_gray = 0xA9A9A9, // rgb(169,169,169)
|
||||
dark_green = 0x006400, // rgb(0,100,0)
|
||||
dark_khaki = 0xBDB76B, // rgb(189,183,107)
|
||||
dark_magenta = 0x8B008B, // rgb(139,0,139)
|
||||
dark_olive_green = 0x556B2F, // rgb(85,107,47)
|
||||
dark_orange = 0xFF8C00, // rgb(255,140,0)
|
||||
dark_orchid = 0x9932CC, // rgb(153,50,204)
|
||||
dark_red = 0x8B0000, // rgb(139,0,0)
|
||||
dark_salmon = 0xE9967A, // rgb(233,150,122)
|
||||
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
|
||||
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
|
||||
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
|
||||
dark_turquoise = 0x00CED1, // rgb(0,206,209)
|
||||
dark_violet = 0x9400D3, // rgb(148,0,211)
|
||||
deep_pink = 0xFF1493, // rgb(255,20,147)
|
||||
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
|
||||
dim_gray = 0x696969, // rgb(105,105,105)
|
||||
dodger_blue = 0x1E90FF, // rgb(30,144,255)
|
||||
fire_brick = 0xB22222, // rgb(178,34,34)
|
||||
floral_white = 0xFFFAF0, // rgb(255,250,240)
|
||||
forest_green = 0x228B22, // rgb(34,139,34)
|
||||
fuchsia = 0xFF00FF, // rgb(255,0,255)
|
||||
gainsboro = 0xDCDCDC, // rgb(220,220,220)
|
||||
ghost_white = 0xF8F8FF, // rgb(248,248,255)
|
||||
gold = 0xFFD700, // rgb(255,215,0)
|
||||
golden_rod = 0xDAA520, // rgb(218,165,32)
|
||||
gray = 0x808080, // rgb(128,128,128)
|
||||
green = 0x008000, // rgb(0,128,0)
|
||||
green_yellow = 0xADFF2F, // rgb(173,255,47)
|
||||
honey_dew = 0xF0FFF0, // rgb(240,255,240)
|
||||
hot_pink = 0xFF69B4, // rgb(255,105,180)
|
||||
indian_red = 0xCD5C5C, // rgb(205,92,92)
|
||||
indigo = 0x4B0082, // rgb(75,0,130)
|
||||
ivory = 0xFFFFF0, // rgb(255,255,240)
|
||||
khaki = 0xF0E68C, // rgb(240,230,140)
|
||||
lavender = 0xE6E6FA, // rgb(230,230,250)
|
||||
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
|
||||
lawn_green = 0x7CFC00, // rgb(124,252,0)
|
||||
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
|
||||
light_blue = 0xADD8E6, // rgb(173,216,230)
|
||||
light_coral = 0xF08080, // rgb(240,128,128)
|
||||
light_cyan = 0xE0FFFF, // rgb(224,255,255)
|
||||
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
|
||||
light_gray = 0xD3D3D3, // rgb(211,211,211)
|
||||
light_green = 0x90EE90, // rgb(144,238,144)
|
||||
light_pink = 0xFFB6C1, // rgb(255,182,193)
|
||||
light_salmon = 0xFFA07A, // rgb(255,160,122)
|
||||
light_sea_green = 0x20B2AA, // rgb(32,178,170)
|
||||
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
|
||||
light_slate_gray = 0x778899, // rgb(119,136,153)
|
||||
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
|
||||
light_yellow = 0xFFFFE0, // rgb(255,255,224)
|
||||
lime = 0x00FF00, // rgb(0,255,0)
|
||||
lime_green = 0x32CD32, // rgb(50,205,50)
|
||||
linen = 0xFAF0E6, // rgb(250,240,230)
|
||||
magenta = 0xFF00FF, // rgb(255,0,255)
|
||||
maroon = 0x800000, // rgb(128,0,0)
|
||||
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
|
||||
medium_blue = 0x0000CD, // rgb(0,0,205)
|
||||
medium_orchid = 0xBA55D3, // rgb(186,85,211)
|
||||
medium_purple = 0x9370DB, // rgb(147,112,219)
|
||||
medium_sea_green = 0x3CB371, // rgb(60,179,113)
|
||||
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
|
||||
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
|
||||
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
|
||||
medium_violet_red = 0xC71585, // rgb(199,21,133)
|
||||
midnight_blue = 0x191970, // rgb(25,25,112)
|
||||
mint_cream = 0xF5FFFA, // rgb(245,255,250)
|
||||
misty_rose = 0xFFE4E1, // rgb(255,228,225)
|
||||
moccasin = 0xFFE4B5, // rgb(255,228,181)
|
||||
navajo_white = 0xFFDEAD, // rgb(255,222,173)
|
||||
navy = 0x000080, // rgb(0,0,128)
|
||||
old_lace = 0xFDF5E6, // rgb(253,245,230)
|
||||
olive = 0x808000, // rgb(128,128,0)
|
||||
olive_drab = 0x6B8E23, // rgb(107,142,35)
|
||||
orange = 0xFFA500, // rgb(255,165,0)
|
||||
orange_red = 0xFF4500, // rgb(255,69,0)
|
||||
orchid = 0xDA70D6, // rgb(218,112,214)
|
||||
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
|
||||
pale_green = 0x98FB98, // rgb(152,251,152)
|
||||
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
|
||||
pale_violet_red = 0xDB7093, // rgb(219,112,147)
|
||||
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
|
||||
peach_puff = 0xFFDAB9, // rgb(255,218,185)
|
||||
peru = 0xCD853F, // rgb(205,133,63)
|
||||
pink = 0xFFC0CB, // rgb(255,192,203)
|
||||
plum = 0xDDA0DD, // rgb(221,160,221)
|
||||
powder_blue = 0xB0E0E6, // rgb(176,224,230)
|
||||
purple = 0x800080, // rgb(128,0,128)
|
||||
rebecca_purple = 0x663399, // rgb(102,51,153)
|
||||
red = 0xFF0000, // rgb(255,0,0)
|
||||
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
|
||||
royal_blue = 0x4169E1, // rgb(65,105,225)
|
||||
saddle_brown = 0x8B4513, // rgb(139,69,19)
|
||||
salmon = 0xFA8072, // rgb(250,128,114)
|
||||
sandy_brown = 0xF4A460, // rgb(244,164,96)
|
||||
sea_green = 0x2E8B57, // rgb(46,139,87)
|
||||
sea_shell = 0xFFF5EE, // rgb(255,245,238)
|
||||
sienna = 0xA0522D, // rgb(160,82,45)
|
||||
silver = 0xC0C0C0, // rgb(192,192,192)
|
||||
sky_blue = 0x87CEEB, // rgb(135,206,235)
|
||||
slate_blue = 0x6A5ACD, // rgb(106,90,205)
|
||||
slate_gray = 0x708090, // rgb(112,128,144)
|
||||
snow = 0xFFFAFA, // rgb(255,250,250)
|
||||
spring_green = 0x00FF7F, // rgb(0,255,127)
|
||||
steel_blue = 0x4682B4, // rgb(70,130,180)
|
||||
tan = 0xD2B48C, // rgb(210,180,140)
|
||||
teal = 0x008080, // rgb(0,128,128)
|
||||
thistle = 0xD8BFD8, // rgb(216,191,216)
|
||||
tomato = 0xFF6347, // rgb(255,99,71)
|
||||
turquoise = 0x40E0D0, // rgb(64,224,208)
|
||||
violet = 0xEE82EE, // rgb(238,130,238)
|
||||
wheat = 0xF5DEB3, // rgb(245,222,179)
|
||||
white = 0xFFFFFF, // rgb(255,255,255)
|
||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||
yellow_green = 0x9ACD32, // rgb(154,205,50)
|
||||
}; // enum class color
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// We use rgb as name because some editors will show it as color direct in the
|
||||
// editor.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR_DECL rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_)
|
||||
: r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR_DECL rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b((hex) & 0xFF) {}
|
||||
FMT_CONSTEXPR_DECL rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
void vprint_rgb(rgb fd, string_view format, format_args args);
|
||||
void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args);
|
||||
|
||||
/**
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify foreground color 'fd'.
|
||||
Example:
|
||||
fmt::print(fmt::color::red, "Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline void print(rgb fd, string_view format_str, const Args & ... args) {
|
||||
vprint_rgb(fd, format_str, make_format_args(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify foreground color 'fd' and background color 'bg'.
|
||||
Example:
|
||||
fmt::print(fmt::color::red, fmt::color::black,
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline void print(rgb fd, rgb bg, string_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint_rgb(fd, bg, format_str, make_format_args(args...));
|
||||
}
|
||||
namespace internal {
|
||||
FMT_CONSTEXPR void to_esc(uint8_t c, char out[], int offset) {
|
||||
out[offset + 0] = static_cast<char>('0' + c / 100);
|
||||
out[offset + 1] = static_cast<char>('0' + c / 10 % 10);
|
||||
out[offset + 2] = static_cast<char>('0' + c % 10);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
inline void vprint_rgb(rgb fd, string_view format, format_args args) {
|
||||
char escape_fd[] = "\x1b[38;2;000;000;000m";
|
||||
internal::to_esc(fd.r, escape_fd, 7);
|
||||
internal::to_esc(fd.g, escape_fd, 11);
|
||||
internal::to_esc(fd.b, escape_fd, 15);
|
||||
|
||||
std::fputs(escape_fd, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
inline void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) {
|
||||
char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color
|
||||
char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color
|
||||
internal::to_esc(fd.r, escape_fd, 7);
|
||||
internal::to_esc(fd.g, escape_fd, 11);
|
||||
internal::to_esc(fd.b, escape_fd, 15);
|
||||
|
||||
internal::to_esc(bg.r, escape_bg, 7);
|
||||
internal::to_esc(bg.g, escape_bg, 11);
|
||||
internal::to_esc(bg.b, escape_bg, 15);
|
||||
|
||||
std::fputs(escape_fd, stdout);
|
||||
std::fputs(escape_bg, stdout);
|
||||
vprint(format, args);
|
||||
std::fputs(internal::data::RESET_COLOR, stdout);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
||||
1502
ext/fmt-5.2.1/include/fmt/core.h
Normal file
866
ext/fmt-5.2.1/include/fmt/format-inl.h
Normal file
|
|
@ -0,0 +1,866 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_FORMAT_INL_H_
|
||||
#define FMT_FORMAT_INL_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstddef> // for std::ptrdiff_t
|
||||
#include <cstring> // for std::memmove
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
# include <locale>
|
||||
#endif
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)
|
||||
# include <windows.h>
|
||||
# else
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
# undef NOMINMAX
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_EXCEPTIONS
|
||||
# define FMT_TRY try
|
||||
# define FMT_CATCH(x) catch (x)
|
||||
#else
|
||||
# define FMT_TRY if (true)
|
||||
# define FMT_CATCH(x) if (false)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4127) // conditional expression is constant
|
||||
# pragma warning(disable: 4702) // unreachable code
|
||||
// Disable deprecation warning for strerror. The latter is not called but
|
||||
// MSVC fails to detect it.
|
||||
# pragma warning(disable: 4996)
|
||||
#endif
|
||||
|
||||
// Dummy implementations of strerror_r and strerror_s called if corresponding
|
||||
// system functions are not available.
|
||||
inline fmt::internal::null<> strerror_r(int, char *, ...) {
|
||||
return fmt::internal::null<>();
|
||||
}
|
||||
inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) {
|
||||
return fmt::internal::null<>();
|
||||
}
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace {
|
||||
|
||||
#ifndef _MSC_VER
|
||||
# define FMT_SNPRINTF snprintf
|
||||
#else // _MSC_VER
|
||||
inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
# define FMT_SNPRINTF fmt_snprintf
|
||||
#endif // _MSC_VER
|
||||
|
||||
#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
||||
# define FMT_SWPRINTF snwprintf
|
||||
#else
|
||||
# define FMT_SWPRINTF swprintf
|
||||
#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
||||
|
||||
typedef void (*FormatFunc)(internal::buffer &, int, string_view);
|
||||
|
||||
// Portable thread-safe version of strerror.
|
||||
// Sets buffer to point to a string describing the error code.
|
||||
// This can be either a pointer to a string stored in buffer,
|
||||
// or a pointer to some static immutable string.
|
||||
// Returns one of the following values:
|
||||
// 0 - success
|
||||
// ERANGE - buffer is not large enough to store the error message
|
||||
// other - failure
|
||||
// Buffer should be at least of size 1.
|
||||
int safe_strerror(
|
||||
int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT {
|
||||
FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer");
|
||||
|
||||
class dispatcher {
|
||||
private:
|
||||
int error_code_;
|
||||
char *&buffer_;
|
||||
std::size_t buffer_size_;
|
||||
|
||||
// A noop assignment operator to avoid bogus warnings.
|
||||
void operator=(const dispatcher &) {}
|
||||
|
||||
// Handle the result of XSI-compliant version of strerror_r.
|
||||
int handle(int result) {
|
||||
// glibc versions before 2.13 return result in errno.
|
||||
return result == -1 ? errno : result;
|
||||
}
|
||||
|
||||
// Handle the result of GNU-specific version of strerror_r.
|
||||
int handle(char *message) {
|
||||
// If the buffer is full then the message is probably truncated.
|
||||
if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)
|
||||
return ERANGE;
|
||||
buffer_ = message;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle the case when strerror_r is not available.
|
||||
int handle(internal::null<>) {
|
||||
return fallback(strerror_s(buffer_, buffer_size_, error_code_));
|
||||
}
|
||||
|
||||
// Fallback to strerror_s when strerror_r is not available.
|
||||
int fallback(int result) {
|
||||
// If the buffer is full then the message is probably truncated.
|
||||
return result == 0 && strlen(buffer_) == buffer_size_ - 1 ?
|
||||
ERANGE : result;
|
||||
}
|
||||
|
||||
// Fallback to strerror if strerror_r and strerror_s are not available.
|
||||
int fallback(internal::null<>) {
|
||||
errno = 0;
|
||||
buffer_ = strerror(error_code_);
|
||||
return errno;
|
||||
}
|
||||
|
||||
public:
|
||||
dispatcher(int err_code, char *&buf, std::size_t buf_size)
|
||||
: error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
|
||||
|
||||
int run() {
|
||||
return handle(strerror_r(error_code_, buffer_, buffer_size_));
|
||||
}
|
||||
};
|
||||
return dispatcher(error_code, buffer, buffer_size).run();
|
||||
}
|
||||
|
||||
void format_error_code(internal::buffer &out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
// Report error code making sure that the output fits into
|
||||
// inline_buffer_size to avoid dynamic memory allocation and potential
|
||||
// bad_alloc.
|
||||
out.resize(0);
|
||||
static const char SEP[] = ": ";
|
||||
static const char ERROR_STR[] = "error ";
|
||||
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
|
||||
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
|
||||
typedef internal::int_traits<int>::main_type main_type;
|
||||
main_type abs_value = static_cast<main_type>(error_code);
|
||||
if (internal::is_negative(error_code)) {
|
||||
abs_value = 0 - abs_value;
|
||||
++error_code_size;
|
||||
}
|
||||
error_code_size += internal::count_digits(abs_value);
|
||||
writer w(out);
|
||||
if (message.size() <= inline_buffer_size - error_code_size) {
|
||||
w.write(message);
|
||||
w.write(SEP);
|
||||
}
|
||||
w.write(ERROR_STR);
|
||||
w.write(error_code);
|
||||
assert(out.size() <= inline_buffer_size);
|
||||
}
|
||||
|
||||
void report_error(FormatFunc func, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
memory_buffer full_message;
|
||||
func(full_message, error_code, message);
|
||||
// Use Writer::data instead of Writer::c_str to avoid potential memory
|
||||
// allocation.
|
||||
std::fwrite(full_message.data(), full_message.size(), 1, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
class locale {
|
||||
private:
|
||||
std::locale locale_;
|
||||
|
||||
public:
|
||||
explicit locale(std::locale loc = std::locale()) : locale_(loc) {}
|
||||
std::locale get() { return locale_; }
|
||||
};
|
||||
|
||||
FMT_FUNC size_t internal::count_code_points(u8string_view s) {
|
||||
const char8_t *data = s.data();
|
||||
int num_code_points = 0;
|
||||
for (size_t i = 0, size = s.size(); i != size; ++i) {
|
||||
if ((data[i].value & 0xc0) != 0x80)
|
||||
++num_code_points;
|
||||
}
|
||||
return num_code_points;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_FUNC Char internal::thousands_sep(locale_provider *lp) {
|
||||
std::locale loc = lp ? lp->locale().get() : std::locale();
|
||||
return std::use_facet<std::numpunct<Char>>(loc).thousands_sep();
|
||||
}
|
||||
#else
|
||||
template <typename Char>
|
||||
FMT_FUNC Char internal::thousands_sep(locale_provider *lp) {
|
||||
return FMT_STATIC_THOUSANDS_SEPARATOR;
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void system_error::init(
|
||||
int err_code, string_view format_str, format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
format_system_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error &base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
template <typename T>
|
||||
int char_traits<char>::format_float(
|
||||
char *buffer, std::size_t size, const char *format, int precision, T value) {
|
||||
return precision < 0 ?
|
||||
FMT_SNPRINTF(buffer, size, format, value) :
|
||||
FMT_SNPRINTF(buffer, size, format, precision, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int char_traits<wchar_t>::format_float(
|
||||
wchar_t *buffer, std::size_t size, const wchar_t *format, int precision,
|
||||
T value) {
|
||||
return precision < 0 ?
|
||||
FMT_SWPRINTF(buffer, size, format, value) :
|
||||
FMT_SWPRINTF(buffer, size, format, precision, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const char basic_data<T>::DIGITS[] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
|
||||
#define FMT_POWERS_OF_10(factor) \
|
||||
factor * 10, \
|
||||
factor * 100, \
|
||||
factor * 1000, \
|
||||
factor * 10000, \
|
||||
factor * 100000, \
|
||||
factor * 1000000, \
|
||||
factor * 10000000, \
|
||||
factor * 100000000, \
|
||||
factor * 1000000000
|
||||
|
||||
template <typename T>
|
||||
const uint32_t basic_data<T>::POWERS_OF_10_32[] = {
|
||||
1, FMT_POWERS_OF_10(1)
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const uint32_t basic_data<T>::ZERO_OR_POWERS_OF_10_32[] = {
|
||||
0, FMT_POWERS_OF_10(1)
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const uint64_t basic_data<T>::ZERO_OR_POWERS_OF_10_64[] = {
|
||||
0,
|
||||
FMT_POWERS_OF_10(1),
|
||||
FMT_POWERS_OF_10(1000000000ull),
|
||||
10000000000000000000ull
|
||||
};
|
||||
|
||||
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
|
||||
// These are generated by support/compute-powers.py.
|
||||
template <typename T>
|
||||
const uint64_t basic_data<T>::POW10_SIGNIFICANDS[] = {
|
||||
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
|
||||
0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
|
||||
0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
|
||||
0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
|
||||
0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
|
||||
0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
|
||||
0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
|
||||
0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
|
||||
0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
|
||||
0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
|
||||
0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
|
||||
0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
|
||||
0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
|
||||
0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
|
||||
0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
|
||||
0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
|
||||
0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
|
||||
0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
|
||||
0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
|
||||
0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
|
||||
0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
|
||||
0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
|
||||
0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
|
||||
0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
|
||||
0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
|
||||
0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
|
||||
0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
|
||||
0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
|
||||
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
|
||||
};
|
||||
|
||||
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
|
||||
// to significands above.
|
||||
template <typename T>
|
||||
const int16_t basic_data<T>::POW10_EXPONENTS[] = {
|
||||
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
|
||||
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
|
||||
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
|
||||
-343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
|
||||
-50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
|
||||
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
|
||||
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
|
||||
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066
|
||||
};
|
||||
|
||||
template <typename T> const char basic_data<T>::RESET_COLOR[] = "\x1b[0m";
|
||||
template <typename T> const wchar_t basic_data<T>::WRESET_COLOR[] = L"\x1b[0m";
|
||||
|
||||
// A handmade floating-point number f * pow(2, e).
|
||||
class fp {
|
||||
private:
|
||||
typedef uint64_t significand_type;
|
||||
|
||||
// All sizes are in bits.
|
||||
static FMT_CONSTEXPR_DECL const int char_size =
|
||||
std::numeric_limits<unsigned char>::digits;
|
||||
// Subtract 1 to account for an implicit most significant bit in the
|
||||
// normalized form.
|
||||
static FMT_CONSTEXPR_DECL const int double_significand_size =
|
||||
std::numeric_limits<double>::digits - 1;
|
||||
static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
|
||||
1ull << double_significand_size;
|
||||
|
||||
public:
|
||||
significand_type f;
|
||||
int e;
|
||||
|
||||
static FMT_CONSTEXPR_DECL const int significand_size =
|
||||
sizeof(significand_type) * char_size;
|
||||
|
||||
fp(): f(0), e(0) {}
|
||||
fp(uint64_t f, int e): f(f), e(e) {}
|
||||
|
||||
// Constructs fp from an IEEE754 double. It is a template to prevent compile
|
||||
// errors on platforms where double is not IEEE754.
|
||||
template <typename Double>
|
||||
explicit fp(Double d) {
|
||||
// Assume double is in the format [sign][exponent][significand].
|
||||
typedef std::numeric_limits<Double> limits;
|
||||
const int double_size = static_cast<int>(sizeof(Double) * char_size);
|
||||
const int exponent_size =
|
||||
double_size - double_significand_size - 1; // -1 for sign
|
||||
const uint64_t significand_mask = implicit_bit - 1;
|
||||
const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask;
|
||||
const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
|
||||
auto u = bit_cast<uint64_t>(d);
|
||||
auto biased_e = (u & exponent_mask) >> double_significand_size;
|
||||
f = u & significand_mask;
|
||||
if (biased_e != 0)
|
||||
f += implicit_bit;
|
||||
else
|
||||
biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
|
||||
e = static_cast<int>(biased_e - exponent_bias - double_significand_size);
|
||||
}
|
||||
|
||||
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
|
||||
template <int SHIFT = 0>
|
||||
void normalize() {
|
||||
// Handle subnormals.
|
||||
auto shifted_implicit_bit = implicit_bit << SHIFT;
|
||||
while ((f & shifted_implicit_bit) == 0) {
|
||||
f <<= 1;
|
||||
--e;
|
||||
}
|
||||
// Subtract 1 to account for hidden bit.
|
||||
auto offset = significand_size - double_significand_size - SHIFT - 1;
|
||||
f <<= offset;
|
||||
e -= offset;
|
||||
}
|
||||
|
||||
// Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), where
|
||||
// a boundary is a value half way between the number and its predecessor
|
||||
// (lower) or successor (upper). The upper boundary is normalized and lower
|
||||
// has the same exponent but may be not normalized.
|
||||
void compute_boundaries(fp &lower, fp &upper) const {
|
||||
lower = f == implicit_bit ?
|
||||
fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1);
|
||||
upper = fp((f << 1) + 1, e - 1);
|
||||
upper.normalize<1>(); // 1 is to account for the exponent shift above.
|
||||
lower.f <<= lower.e - upper.e;
|
||||
lower.e = upper.e;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns an fp number representing x - y. Result may not be normalized.
|
||||
inline fp operator-(fp x, fp y) {
|
||||
FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands");
|
||||
return fp(x.f - y.f, x.e);
|
||||
}
|
||||
|
||||
// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest
|
||||
// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be normalized.
|
||||
FMT_API fp operator*(fp x, fp y);
|
||||
|
||||
// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its
|
||||
// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 3.
|
||||
FMT_API fp get_cached_power(int min_exponent, int &pow10_exponent);
|
||||
|
||||
FMT_FUNC fp operator*(fp x, fp y) {
|
||||
// Multiply 32-bit parts of significands.
|
||||
uint64_t mask = (1ULL << 32) - 1;
|
||||
uint64_t a = x.f >> 32, b = x.f & mask;
|
||||
uint64_t c = y.f >> 32, d = y.f & mask;
|
||||
uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
|
||||
// Compute mid 64-bit of result and round.
|
||||
uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
|
||||
return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64);
|
||||
}
|
||||
|
||||
FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) {
|
||||
const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10)
|
||||
int index = static_cast<int>(std::ceil(
|
||||
(min_exponent + fp::significand_size - 1) * one_over_log2_10));
|
||||
// Decimal exponent of the first (smallest) cached power of 10.
|
||||
const int first_dec_exp = -348;
|
||||
// Difference between 2 consecutive decimal exponents in cached powers of 10.
|
||||
const int dec_exp_step = 8;
|
||||
index = (index - first_dec_exp - 1) / dec_exp_step + 1;
|
||||
pow10_exponent = first_dec_exp + index * dec_exp_step;
|
||||
return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]);
|
||||
}
|
||||
|
||||
// Generates output using Grisu2 digit-gen algorithm.
|
||||
FMT_FUNC void grisu2_gen_digits(
|
||||
const fp &scaled_value, const fp &scaled_upper, uint64_t delta,
|
||||
char *buffer, size_t &size, int &dec_exp) {
|
||||
internal::fp one(1ull << -scaled_upper.e, scaled_upper.e);
|
||||
// hi (p1 in Grisu) contains the most significant digits of scaled_upper.
|
||||
// hi = floor(scaled_upper / one).
|
||||
uint32_t hi = static_cast<uint32_t>(scaled_upper.f >> -one.e);
|
||||
// lo (p2 in Grisu) contains the least significants digits of scaled_upper.
|
||||
// lo = scaled_upper mod 1.
|
||||
uint64_t lo = scaled_upper.f & (one.f - 1);
|
||||
size = 0;
|
||||
auto exp = count_digits(hi); // kappa in Grisu.
|
||||
while (exp > 0) {
|
||||
uint32_t digit = 0;
|
||||
// This optimization by miloyip reduces the number of integer divisions by
|
||||
// one per iteration.
|
||||
switch (exp) {
|
||||
case 10: digit = hi / 1000000000; hi %= 1000000000; break;
|
||||
case 9: digit = hi / 100000000; hi %= 100000000; break;
|
||||
case 8: digit = hi / 10000000; hi %= 10000000; break;
|
||||
case 7: digit = hi / 1000000; hi %= 1000000; break;
|
||||
case 6: digit = hi / 100000; hi %= 100000; break;
|
||||
case 5: digit = hi / 10000; hi %= 10000; break;
|
||||
case 4: digit = hi / 1000; hi %= 1000; break;
|
||||
case 3: digit = hi / 100; hi %= 100; break;
|
||||
case 2: digit = hi / 10; hi %= 10; break;
|
||||
case 1: digit = hi; hi = 0; break;
|
||||
default:
|
||||
FMT_ASSERT(false, "invalid number of digits");
|
||||
}
|
||||
if (digit != 0 || size != 0)
|
||||
buffer[size++] = static_cast<char>('0' + digit);
|
||||
--exp;
|
||||
uint64_t remainder = (static_cast<uint64_t>(hi) << -one.e) + lo;
|
||||
if (remainder <= delta) {
|
||||
dec_exp += exp;
|
||||
// TODO: use scaled_value
|
||||
(void)scaled_value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (;;) {
|
||||
lo *= 10;
|
||||
delta *= 10;
|
||||
char digit = static_cast<char>(lo >> -one.e);
|
||||
if (digit != 0 || size != 0)
|
||||
buffer[size++] = static_cast<char>('0' + digit);
|
||||
lo &= one.f - 1;
|
||||
--exp;
|
||||
if (lo < delta) {
|
||||
dec_exp += exp;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FMT_FUNC void grisu2_format_positive(double value, char *buffer, size_t &size,
|
||||
int &dec_exp) {
|
||||
FMT_ASSERT(value > 0, "value is nonpositive");
|
||||
fp fp_value(value);
|
||||
fp lower, upper; // w^- and w^+ in the Grisu paper.
|
||||
fp_value.compute_boundaries(lower, upper);
|
||||
// Find a cached power of 10 close to 1 / upper.
|
||||
const int min_exp = -60; // alpha in Grisu.
|
||||
auto dec_pow = get_cached_power( // \tilde{c}_{-k} in Grisu.
|
||||
min_exp - (upper.e + fp::significand_size), dec_exp);
|
||||
dec_exp = -dec_exp;
|
||||
fp_value.normalize();
|
||||
fp scaled_value = fp_value * dec_pow;
|
||||
fp scaled_lower = lower * dec_pow; // \tilde{M}^- in Grisu.
|
||||
fp scaled_upper = upper * dec_pow; // \tilde{M}^+ in Grisu.
|
||||
++scaled_lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
|
||||
--scaled_upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}.
|
||||
uint64_t delta = scaled_upper.f - scaled_lower.f;
|
||||
grisu2_gen_digits(scaled_value, scaled_upper, delta, buffer, size, dec_exp);
|
||||
}
|
||||
|
||||
FMT_FUNC void round(char *buffer, size_t &size, int &exp,
|
||||
int digits_to_remove) {
|
||||
size -= to_unsigned(digits_to_remove);
|
||||
exp += digits_to_remove;
|
||||
int digit = buffer[size] - '0';
|
||||
// TODO: proper rounding and carry
|
||||
if (digit > 5 || (digit == 5 && (digits_to_remove > 1 ||
|
||||
(buffer[size - 1] - '0') % 2) != 0)) {
|
||||
++buffer[size - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Writes the exponent exp in the form "[+-]d{1,3}" to buffer.
|
||||
FMT_FUNC char *write_exponent(char *buffer, int exp) {
|
||||
FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range");
|
||||
if (exp < 0) {
|
||||
*buffer++ = '-';
|
||||
exp = -exp;
|
||||
} else {
|
||||
*buffer++ = '+';
|
||||
}
|
||||
if (exp >= 100) {
|
||||
*buffer++ = static_cast<char>('0' + exp / 100);
|
||||
exp %= 100;
|
||||
const char *d = data::DIGITS + exp * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
} else {
|
||||
const char *d = data::DIGITS + exp * 2;
|
||||
*buffer++ = d[0];
|
||||
*buffer++ = d[1];
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
FMT_FUNC void format_exp_notation(
|
||||
char *buffer, size_t &size, int exp, int precision, bool upper) {
|
||||
// Insert a decimal point after the first digit and add an exponent.
|
||||
std::memmove(buffer + 2, buffer + 1, size - 1);
|
||||
buffer[1] = '.';
|
||||
exp += static_cast<int>(size) - 1;
|
||||
int num_digits = precision - static_cast<int>(size) + 1;
|
||||
if (num_digits > 0) {
|
||||
std::uninitialized_fill_n(buffer + size + 1, num_digits, '0');
|
||||
size += to_unsigned(num_digits);
|
||||
} else if (num_digits < 0) {
|
||||
round(buffer, size, exp, -num_digits);
|
||||
}
|
||||
char *p = buffer + size + 1;
|
||||
*p++ = upper ? 'E' : 'e';
|
||||
size = to_unsigned(write_exponent(p, exp) - buffer);
|
||||
}
|
||||
|
||||
// Prettifies the output of the Grisu2 algorithm.
|
||||
// The number is given as v = buffer * 10^exp.
|
||||
FMT_FUNC void grisu2_prettify(char *buffer, size_t &size, int exp,
|
||||
int precision, bool upper) {
|
||||
// pow(10, full_exp - 1) <= v <= pow(10, full_exp).
|
||||
int int_size = static_cast<int>(size);
|
||||
int full_exp = int_size + exp;
|
||||
const int exp_threshold = 21;
|
||||
if (int_size <= full_exp && full_exp <= exp_threshold) {
|
||||
// 1234e7 -> 12340000000[.0+]
|
||||
std::uninitialized_fill_n(buffer + int_size, full_exp - int_size, '0');
|
||||
char *p = buffer + full_exp;
|
||||
if (precision > 0) {
|
||||
*p++ = '.';
|
||||
std::uninitialized_fill_n(p, precision, '0');
|
||||
p += precision;
|
||||
}
|
||||
size = to_unsigned(p - buffer);
|
||||
} else if (0 < full_exp && full_exp <= exp_threshold) {
|
||||
// 1234e-2 -> 12.34[0+]
|
||||
int fractional_size = -exp;
|
||||
std::memmove(buffer + full_exp + 1, buffer + full_exp,
|
||||
to_unsigned(fractional_size));
|
||||
buffer[full_exp] = '.';
|
||||
int num_zeros = precision - fractional_size;
|
||||
if (num_zeros > 0) {
|
||||
std::uninitialized_fill_n(buffer + size + 1, num_zeros, '0');
|
||||
size += to_unsigned(num_zeros);
|
||||
}
|
||||
++size;
|
||||
} else if (-6 < full_exp && full_exp <= 0) {
|
||||
// 1234e-6 -> 0.001234
|
||||
int offset = 2 - full_exp;
|
||||
std::memmove(buffer + offset, buffer, size);
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '.';
|
||||
std::uninitialized_fill_n(buffer + 2, -full_exp, '0');
|
||||
size = to_unsigned(int_size + offset);
|
||||
} else {
|
||||
format_exp_notation(buffer, size, exp, precision, upper);
|
||||
}
|
||||
}
|
||||
|
||||
#if FMT_CLANG_VERSION
|
||||
# define FMT_FALLTHROUGH [[clang::fallthrough]];
|
||||
#elif FMT_GCC_VERSION >= 700
|
||||
# define FMT_FALLTHROUGH [[gnu::fallthrough]];
|
||||
#else
|
||||
# define FMT_FALLTHROUGH
|
||||
#endif
|
||||
|
||||
// Formats a nonnegative value using Grisu2 algorithm. Grisu2 doesn't give any
|
||||
// guarantees on the shortness of the result.
|
||||
FMT_FUNC void grisu2_format(double value, char *buffer, size_t &size, char type,
|
||||
int precision, bool write_decimal_point) {
|
||||
FMT_ASSERT(value >= 0, "value is negative");
|
||||
int dec_exp = 0; // K in Grisu.
|
||||
if (value > 0) {
|
||||
grisu2_format_positive(value, buffer, size, dec_exp);
|
||||
} else {
|
||||
*buffer = '0';
|
||||
size = 1;
|
||||
}
|
||||
const int default_precision = 6;
|
||||
if (precision < 0)
|
||||
precision = default_precision;
|
||||
bool upper = false;
|
||||
switch (type) {
|
||||
case 'G':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH
|
||||
case '\0': case 'g': {
|
||||
int digits_to_remove = static_cast<int>(size) - precision;
|
||||
if (digits_to_remove > 0) {
|
||||
round(buffer, size, dec_exp, digits_to_remove);
|
||||
// Remove trailing zeros.
|
||||
while (size > 0 && buffer[size - 1] == '0') {
|
||||
--size;
|
||||
++dec_exp;
|
||||
}
|
||||
}
|
||||
precision = 0;
|
||||
break;
|
||||
}
|
||||
case 'F':
|
||||
upper = true;
|
||||
FMT_FALLTHROUGH
|
||||
case 'f': {
|
||||
int digits_to_remove = -dec_exp - precision;
|
||||
if (digits_to_remove > 0) {
|
||||
if (digits_to_remove >= static_cast<int>(size))
|
||||
digits_to_remove = static_cast<int>(size) - 1;
|
||||
round(buffer, size, dec_exp, digits_to_remove);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'e': case 'E':
|
||||
format_exp_notation(buffer, size, dec_exp, precision, type == 'E');
|
||||
return;
|
||||
}
|
||||
if (write_decimal_point && precision < 1)
|
||||
precision = 1;
|
||||
grisu2_prettify(buffer, size, dec_exp, precision, upper);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16";
|
||||
if (s.size() > INT_MAX)
|
||||
FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG));
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// MultiByteToWideChar does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int length = MultiByteToWideChar(
|
||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0);
|
||||
if (length == 0)
|
||||
FMT_THROW(windows_error(GetLastError(), ERROR_MSG));
|
||||
buffer_.resize(length + 1);
|
||||
length = MultiByteToWideChar(
|
||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length);
|
||||
if (length == 0)
|
||||
FMT_THROW(windows_error(GetLastError(), ERROR_MSG));
|
||||
buffer_[length] = 0;
|
||||
}
|
||||
|
||||
FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(windows_error(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) {
|
||||
if (s.size() > INT_MAX)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// WideCharToMultiByte does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = WideCharToMultiByte(
|
||||
CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL);
|
||||
if (length == 0)
|
||||
return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(
|
||||
CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL);
|
||||
if (length == 0)
|
||||
return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_FUNC void windows_error::init(
|
||||
int err_code, string_view format_str, format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
internal::format_windows_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error &base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
FMT_FUNC void internal::format_windows_error(
|
||||
internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
wmemory_buffer buf;
|
||||
buf.resize(inline_buffer_size);
|
||||
for (;;) {
|
||||
wchar_t *system_message = &buf[0];
|
||||
int result = FormatMessageW(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
system_message, static_cast<uint32_t>(buf.size()), FMT_NULL);
|
||||
if (result != 0) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
||||
writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(utf8_message);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
break; // Can't get error message, report error code instead.
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
} FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
#endif // FMT_USE_WINDOWS_H
|
||||
|
||||
FMT_FUNC void format_system_error(
|
||||
internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
memory_buffer buf;
|
||||
buf.resize(inline_buffer_size);
|
||||
for (;;) {
|
||||
char *system_message = &buf[0];
|
||||
int result = safe_strerror(error_code, system_message, buf.size());
|
||||
if (result == 0) {
|
||||
writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(system_message);
|
||||
return;
|
||||
}
|
||||
if (result != ERANGE)
|
||||
break; // Can't get error message, report error code instead.
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
} FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void basic_fixed_buffer<Char>::grow(std::size_t) {
|
||||
FMT_THROW(std::runtime_error("buffer overflow"));
|
||||
}
|
||||
|
||||
FMT_FUNC void internal::error_handler::on_error(const char *message) {
|
||||
FMT_THROW(format_error(message));
|
||||
}
|
||||
|
||||
FMT_FUNC void report_system_error(
|
||||
int error_code, fmt::string_view message) FMT_NOEXCEPT {
|
||||
report_error(format_system_error, error_code, message);
|
||||
}
|
||||
|
||||
#if FMT_USE_WINDOWS_H
|
||||
FMT_FUNC void report_windows_error(
|
||||
int error_code, fmt::string_view message) FMT_NOEXCEPT {
|
||||
report_error(internal::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) {
|
||||
memory_buffer buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
std::fwrite(buffer.data(), 1, buffer.size(), f);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) {
|
||||
wmemory_buffer buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(string_view format_str, format_args args) {
|
||||
vprint(stdout, format_str, args);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(wstring_view format_str, wformat_args args) {
|
||||
vprint(stdout, format_str, args);
|
||||
}
|
||||
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
FMT_FUNC locale locale_provider::locale() { return fmt::locale(); }
|
||||
#endif
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif // FMT_FORMAT_INL_H_
|
||||
3720
ext/fmt-5.2.1/include/fmt/format.h
Normal file
157
ext/fmt-5.2.1/include/fmt/ostream.h
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <ostream>
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
template <class Char>
|
||||
class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
typedef typename std::basic_streambuf<Char>::int_type int_type;
|
||||
typedef typename std::basic_streambuf<Char>::traits_type traits_type;
|
||||
|
||||
basic_buffer<Char> &buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(basic_buffer<Char> &buffer) : buffer_(buffer) {}
|
||||
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
struct null;
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void operator<<(null);
|
||||
};
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of std::ostream).
|
||||
template <typename T, typename Char>
|
||||
class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static decltype(
|
||||
internal::declval<test_stream<Char>&>()
|
||||
<< internal::declval<U>(), std::true_type()) test(int);
|
||||
|
||||
template <typename>
|
||||
static std::false_type test(...);
|
||||
|
||||
typedef decltype(test<T>(0)) result;
|
||||
|
||||
public:
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write(std::basic_ostream<Char> &os, basic_buffer<Char> &buf) {
|
||||
const Char *data = buf.data();
|
||||
typedef std::make_unsigned<std::streamsize>::type UnsignedStreamSize;
|
||||
UnsignedStreamSize size = buf.size();
|
||||
UnsignedStreamSize max_size =
|
||||
internal::to_unsigned((std::numeric_limits<std::streamsize>::max)());
|
||||
do {
|
||||
UnsignedStreamSize n = size <= max_size ? size : max_size;
|
||||
os.write(data, static_cast<std::streamsize>(n));
|
||||
data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(basic_buffer<Char> &buffer, const T &value) {
|
||||
internal::formatbuf<Char> format_buf(buffer);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
output << value;
|
||||
buffer.resize(buffer.size());
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
// Disable conversion to int if T has an overloaded operator<< which is a free
|
||||
// function (not a member of std::ostream).
|
||||
template <typename T, typename Char>
|
||||
struct convert_to_int<T, Char, void> {
|
||||
static const bool value =
|
||||
convert_to_int<T, Char, int>::value &&
|
||||
!internal::is_streamable<T, Char>::value;
|
||||
};
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct formatter<T, Char,
|
||||
typename std::enable_if<
|
||||
internal::is_streamable<T, Char>::value &&
|
||||
!internal::format_type<
|
||||
typename buffer_context<Char>::type, T>::value>::type>
|
||||
: formatter<basic_string_view<Char>, Char> {
|
||||
|
||||
template <typename Context>
|
||||
auto format(const T &value, Context &ctx) -> decltype(ctx.out()) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
internal::format_value(buffer, value);
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
inline void vprint(std::basic_ostream<Char> &os,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<typename buffer_context<Char>::type> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vformat_to(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
}
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline void print(std::ostream &os, string_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint<char>(os, format_str, make_format_args<format_context>(args...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(std::wostream &os, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
vprint<wchar_t>(os, format_str, make_format_args<wformat_context>(args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
||||
324
ext/fmt-5.2.1/include/fmt/posix.h
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
// A C++ interface to POSIX functions.
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_POSIX_H_
|
||||
#define FMT_POSIX_H_
|
||||
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
||||
# undef __STRICT_ANSI__
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h> // for O_RDONLY
|
||||
#include <locale.h> // for locale_t
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // for strtod_l
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
result = (expression); \
|
||||
} while (result == error_result && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
/**
|
||||
\rst
|
||||
A reference to a null-terminated string. It can be constructed from a C
|
||||
string or ``std::string``.
|
||||
|
||||
You can use one of the following typedefs for common character types:
|
||||
|
||||
+---------------+-----------------------------+
|
||||
| Type | Definition |
|
||||
+===============+=============================+
|
||||
| cstring_view | basic_cstring_view<char> |
|
||||
+---------------+-----------------------------+
|
||||
| wcstring_view | basic_cstring_view<wchar_t> |
|
||||
+---------------+-----------------------------+
|
||||
|
||||
This class is most useful as a parameter type to allow passing
|
||||
different types of strings to a function, for example::
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(cstring_view format_str, const Args & ... args);
|
||||
|
||||
format("{}", 42);
|
||||
format(std::string("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char>
|
||||
class basic_cstring_view {
|
||||
private:
|
||||
const Char *data_;
|
||||
|
||||
public:
|
||||
/** Constructs a string reference object from a C string. */
|
||||
basic_cstring_view(const Char *s) : data_(s) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a string reference from an ``std::string`` object.
|
||||
\endrst
|
||||
*/
|
||||
basic_cstring_view(const std::basic_string<Char> &s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char *c_str() const { return data_; }
|
||||
};
|
||||
|
||||
typedef basic_cstring_view<char> cstring_view;
|
||||
typedef basic_cstring_view<wchar_t> wcstring_view;
|
||||
|
||||
// An error code.
|
||||
class error_code {
|
||||
private:
|
||||
int value_;
|
||||
|
||||
public:
|
||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
|
||||
|
||||
int get() const FMT_NOEXCEPT { return value_; }
|
||||
};
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE *file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE *f) : file_(f) {}
|
||||
|
||||
public:
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_DTOR_NOEXCEPT;
|
||||
|
||||
private:
|
||||
buffered_file(const buffered_file &) = delete;
|
||||
void operator=(const buffered_file &) = delete;
|
||||
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = FMT_NULL;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file &&other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = FMT_NULL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Opens a file.
|
||||
FMT_API buffered_file(cstring_view filename, cstring_view mode);
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE *get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
FMT_API int (fileno)() const;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args & ... args) {
|
||||
vprint(format_str, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class file {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a file object with a given descriptor.
|
||||
explicit file(int fd) : fd_(fd) {}
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
file() FMT_NOEXCEPT : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a file object representing this file.
|
||||
FMT_API file(cstring_view path, int oflag);
|
||||
|
||||
private:
|
||||
file(const file &) = delete;
|
||||
void operator=(const file &) = delete;
|
||||
|
||||
public:
|
||||
file(file &&other) FMT_NOEXCEPT : fd_(other.fd_) {
|
||||
other.fd_ = -1;
|
||||
}
|
||||
|
||||
file& operator=(file &&other) {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~file() FMT_DTOR_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
FMT_API long long size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API std::size_t read(void *buffer, std::size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API std::size_t write(const void *buffer, std::size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
FMT_API static file dup(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd, error_code &ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
FMT_API static void pipe(file &read_end, file &write_end);
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
FMT_API buffered_file fdopen(const char *mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \
|
||||
!defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__) && \
|
||||
!defined(__NEWLIB_H__)
|
||||
# define FMT_LOCALE
|
||||
#endif
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class Locale {
|
||||
private:
|
||||
# ifdef _MSC_VER
|
||||
typedef _locale_t locale_t;
|
||||
|
||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||
|
||||
static locale_t newlocale(int category_mask, const char *locale, locale_t) {
|
||||
return _create_locale(category_mask, locale);
|
||||
}
|
||||
|
||||
static void freelocale(locale_t locale) {
|
||||
_free_locale(locale);
|
||||
}
|
||||
|
||||
static double strtod_l(const char *nptr, char **endptr, _locale_t locale) {
|
||||
return _strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
Locale(const Locale &) = delete;
|
||||
void operator=(const Locale &) = delete;
|
||||
|
||||
public:
|
||||
typedef locale_t Type;
|
||||
|
||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) {
|
||||
if (!locale_)
|
||||
FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~Locale() { freelocale(locale_); }
|
||||
|
||||
Type get() const { return locale_; }
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char *&str) const {
|
||||
char *end = FMT_NULL;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
#endif // FMT_LOCALE
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_POSIX_H_
|
||||
726
ext/fmt-5.2.1/include/fmt/printf.h
Normal file
|
|
@ -0,0 +1,726 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::fill_n
|
||||
#include <limits> // std::numeric_limits
|
||||
|
||||
#include "ostream.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned>
|
||||
struct int_checker {
|
||||
template <typename T>
|
||||
static bool fits_in_int(T value) {
|
||||
unsigned max = std::numeric_limits<int>::max();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct int_checker<true> {
|
||||
template <typename T>
|
||||
static bool fits_in_int(T value) {
|
||||
return value >= std::numeric_limits<int>::min() &&
|
||||
value <= std::numeric_limits<int>::max();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler: public function<int> {
|
||||
public:
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, int>::type
|
||||
operator()(T value) {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, int>::type operator()(T) {
|
||||
FMT_THROW(format_error("precision is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int: public function<bool> {
|
||||
public:
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, bool>::type
|
||||
operator()(T value) { return value == 0; }
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, bool>::type
|
||||
operator()(T) { return false; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <>
|
||||
struct make_unsigned_or_bool<bool> {
|
||||
typedef bool type;
|
||||
};
|
||||
|
||||
template <typename T, typename Context>
|
||||
class arg_converter: public function<void> {
|
||||
private:
|
||||
typedef typename Context::char_type Char;
|
||||
|
||||
basic_format_arg<Context> &arg_;
|
||||
typename Context::char_type type_;
|
||||
|
||||
public:
|
||||
arg_converter(basic_format_arg<Context> &arg, Char type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void operator()(bool value) {
|
||||
if (type_ != 's')
|
||||
operator()<bool>(value);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
typename std::enable_if<std::is_integral<U>::value>::type
|
||||
operator()(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
typedef typename std::conditional<
|
||||
std::is_same<T, void>::value, U, T>::type TargetType;
|
||||
if (const_check(sizeof(TargetType) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<int>(static_cast<TargetType>(value)));
|
||||
} else {
|
||||
typedef typename make_unsigned_or_bool<TargetType>::type Unsigned;
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<Unsigned>(value)));
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = internal::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
typename std::enable_if<!std::is_integral<U>::value>::type operator()(U) {
|
||||
// No coversion needed for non-integral types.
|
||||
}
|
||||
};
|
||||
|
||||
// Converts an integer argument to T for printf, if T is an integral type.
|
||||
// If T is void, the argument is converted to corresponding signed or unsigned
|
||||
// type depending on the type specifier: 'd' and 'i' - signed, other -
|
||||
// unsigned).
|
||||
template <typename T, typename Context, typename Char>
|
||||
void convert_arg(basic_format_arg<Context> &arg, Char type) {
|
||||
fmt::visit(arg_converter<T, Context>(arg, type), arg);
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
template <typename Context>
|
||||
class char_converter: public function<void> {
|
||||
private:
|
||||
basic_format_arg<Context> &arg_;
|
||||
|
||||
public:
|
||||
explicit char_converter(basic_format_arg<Context> &arg) : arg_(arg) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value>::type
|
||||
operator()(T value) {
|
||||
typedef typename Context::char_type Char;
|
||||
arg_ = internal::make_arg<Context>(static_cast<Char>(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value>::type operator()(T) {
|
||||
// No coversion needed for non-integral types.
|
||||
}
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char>
|
||||
class printf_width_handler: public function<unsigned> {
|
||||
private:
|
||||
typedef basic_format_specs<Char> format_specs;
|
||||
|
||||
format_specs &spec_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs &spec) : spec_(spec) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, unsigned>::type
|
||||
operator()(T value) {
|
||||
typedef typename internal::int_traits<T>::main_type UnsignedType;
|
||||
UnsignedType width = static_cast<UnsignedType>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
spec_.align_ = ALIGN_LEFT;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = std::numeric_limits<int>::max();
|
||||
if (width > int_max)
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, unsigned>::type
|
||||
operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
template <typename Range>
|
||||
class printf_arg_formatter;
|
||||
|
||||
template <
|
||||
typename OutputIt, typename Char,
|
||||
typename ArgFormatter =
|
||||
printf_arg_formatter<back_insert_range<internal::basic_buffer<Char>>>>
|
||||
class basic_printf_context;
|
||||
|
||||
/**
|
||||
\rst
|
||||
The ``printf`` argument formatter.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Range>
|
||||
class printf_arg_formatter:
|
||||
public internal::function<
|
||||
typename internal::arg_formatter_base<Range>::iterator>,
|
||||
public internal::arg_formatter_base<Range> {
|
||||
private:
|
||||
typedef typename Range::value_type char_type;
|
||||
typedef decltype(internal::declval<Range>().begin()) iterator;
|
||||
typedef internal::arg_formatter_base<Range> base;
|
||||
typedef basic_printf_context<iterator, char_type> context_type;
|
||||
|
||||
context_type &context_;
|
||||
|
||||
void write_null_pointer(char) {
|
||||
this->spec()->type_ = 0;
|
||||
this->write("(nil)");
|
||||
}
|
||||
|
||||
void write_null_pointer(wchar_t) {
|
||||
this->spec()->type_ = 0;
|
||||
this->write(L"(nil)");
|
||||
}
|
||||
|
||||
public:
|
||||
typedef typename base::format_specs format_specs;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an argument formatter object.
|
||||
*buffer* is a reference to the output buffer and *spec* contains format
|
||||
specifier information for standard argument types.
|
||||
\endrst
|
||||
*/
|
||||
printf_arg_formatter(internal::basic_buffer<char_type> &buffer,
|
||||
format_specs &spec, context_type &ctx)
|
||||
: base(back_insert_range<internal::basic_buffer<char_type>>(buffer), &spec),
|
||||
context_(ctx) {}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, iterator>::type
|
||||
operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
||||
// use std::is_same instead.
|
||||
if (std::is_same<T, bool>::value) {
|
||||
format_specs &fmt_spec = *this->spec();
|
||||
if (fmt_spec.type_ != 's')
|
||||
return base::operator()(value ? 1 : 0);
|
||||
fmt_spec.type_ = 0;
|
||||
this->write(value != 0);
|
||||
} else if (std::is_same<T, char_type>::value) {
|
||||
format_specs &fmt_spec = *this->spec();
|
||||
if (fmt_spec.type_ && fmt_spec.type_ != 'c')
|
||||
return (*this)(static_cast<int>(value));
|
||||
fmt_spec.flags_ = 0;
|
||||
fmt_spec.align_ = ALIGN_RIGHT;
|
||||
return base::operator()(value);
|
||||
} else {
|
||||
return base::operator()(value);
|
||||
}
|
||||
return this->out();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_floating_point<T>::value, iterator>::type
|
||||
operator()(T value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
iterator operator()(const char *value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->spec()->type_ == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write("(null)");
|
||||
return this->out();
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
iterator operator()(const wchar_t *value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else if (this->spec()->type_ == 'p')
|
||||
write_null_pointer(char_type());
|
||||
else
|
||||
this->write(L"(null)");
|
||||
return this->out();
|
||||
}
|
||||
|
||||
iterator operator()(basic_string_view<char_type> value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
iterator operator()(monostate value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a pointer. */
|
||||
iterator operator()(const void *value) {
|
||||
if (value)
|
||||
return base::operator()(value);
|
||||
this->spec()->type_ = 0;
|
||||
write_null_pointer(char_type());
|
||||
return this->out();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
handle.format(context_);
|
||||
return this->out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct printf_formatter {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T &value, FormatContext &ctx) -> decltype(ctx.out()) {
|
||||
internal::format_value(internal::get_container(ctx.out()), value);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
/** This template formats data and writes the output to a writer. */
|
||||
template <typename OutputIt, typename Char, typename ArgFormatter>
|
||||
class basic_printf_context :
|
||||
// Inherit publicly as a workaround for the icc bug
|
||||
// https://software.intel.com/en-us/forums/intel-c-compiler/topic/783476.
|
||||
public internal::context_base<
|
||||
OutputIt, basic_printf_context<OutputIt, Char, ArgFormatter>, Char> {
|
||||
public:
|
||||
/** The character type for the output. */
|
||||
typedef Char char_type;
|
||||
|
||||
template <typename T>
|
||||
struct formatter_type { typedef printf_formatter<T> type; };
|
||||
|
||||
private:
|
||||
typedef internal::context_base<OutputIt, basic_printf_context, Char> base;
|
||||
typedef typename base::format_arg format_arg;
|
||||
typedef basic_format_specs<char_type> format_specs;
|
||||
typedef internal::null_terminating_iterator<char_type> iterator;
|
||||
|
||||
void parse_flags(format_specs &spec, iterator &it);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is equal
|
||||
// to the maximum unsigned value, the next argument.
|
||||
format_arg get_arg(
|
||||
iterator it,
|
||||
unsigned arg_index = (std::numeric_limits<unsigned>::max)());
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
unsigned parse_header(iterator &it, format_specs &spec);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments and
|
||||
the writer are stored in the context object so make sure they have
|
||||
appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: base(out, format_str, args) {}
|
||||
|
||||
using base::parse_context;
|
||||
using base::out;
|
||||
using base::advance_to;
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
void format();
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
void basic_printf_context<OutputIt, Char, AF>::parse_flags(
|
||||
format_specs &spec, iterator &it) {
|
||||
for (;;) {
|
||||
switch (*it++) {
|
||||
case '-':
|
||||
spec.align_ = ALIGN_LEFT;
|
||||
break;
|
||||
case '+':
|
||||
spec.flags_ |= SIGN_FLAG | PLUS_FLAG;
|
||||
break;
|
||||
case '0':
|
||||
spec.fill_ = '0';
|
||||
break;
|
||||
case ' ':
|
||||
spec.flags_ |= SIGN_FLAG;
|
||||
break;
|
||||
case '#':
|
||||
spec.flags_ |= HASH_FLAG;
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
typename basic_printf_context<OutputIt, Char, AF>::format_arg
|
||||
basic_printf_context<OutputIt, Char, AF>::get_arg(
|
||||
iterator it, unsigned arg_index) {
|
||||
(void)it;
|
||||
if (arg_index == std::numeric_limits<unsigned>::max())
|
||||
return this->do_get_arg(this->parse_context().next_arg_id());
|
||||
return base::get_arg(arg_index - 1);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
unsigned basic_printf_context<OutputIt, Char, AF>::parse_header(
|
||||
iterator &it, format_specs &spec) {
|
||||
unsigned arg_index = std::numeric_limits<unsigned>::max();
|
||||
char_type c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
internal::error_handler eh;
|
||||
unsigned value = parse_nonnegative_int(it, eh);
|
||||
if (*it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
} else {
|
||||
if (c == '0')
|
||||
spec.fill_ = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
spec.width_ = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(spec, it);
|
||||
// Parse width.
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
internal::error_handler eh;
|
||||
spec.width_ = parse_nonnegative_int(it, eh);
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
spec.width_ =
|
||||
fmt::visit(internal::printf_width_handler<char_type>(spec), get_arg(it));
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename AF>
|
||||
void basic_printf_context<OutputIt, Char, AF>::format() {
|
||||
auto &buffer = internal::get_container(this->out());
|
||||
auto start = iterator(this->parse_context());
|
||||
auto it = start;
|
||||
using internal::pointer_from;
|
||||
while (*it) {
|
||||
char_type c = *it++;
|
||||
if (c != '%') continue;
|
||||
if (*it == c) {
|
||||
buffer.append(pointer_from(start), pointer_from(it));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
buffer.append(pointer_from(start), pointer_from(it) - 1);
|
||||
|
||||
format_specs spec;
|
||||
spec.align_ = ALIGN_RIGHT;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
unsigned arg_index = parse_header(it, spec);
|
||||
|
||||
// Parse precision.
|
||||
if (*it == '.') {
|
||||
++it;
|
||||
if ('0' <= *it && *it <= '9') {
|
||||
internal::error_handler eh;
|
||||
spec.precision_ = static_cast<int>(parse_nonnegative_int(it, eh));
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
spec.precision_ =
|
||||
fmt::visit(internal::printf_precision_handler(), get_arg(it));
|
||||
} else {
|
||||
spec.precision_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg arg = get_arg(it, arg_index);
|
||||
if (spec.flag(HASH_FLAG) && fmt::visit(internal::is_zero_int(), arg))
|
||||
spec.flags_ &= ~internal::to_unsigned<int>(HASH_FLAG);
|
||||
if (spec.fill_ == '0') {
|
||||
if (arg.is_arithmetic())
|
||||
spec.align_ = ALIGN_NUMERIC;
|
||||
else
|
||||
spec.fill_ = ' '; // Ignore '0' flag for non-numeric types.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
using internal::convert_arg;
|
||||
switch (*it++) {
|
||||
case 'h':
|
||||
if (*it == 'h')
|
||||
convert_arg<signed char>(arg, *++it);
|
||||
else
|
||||
convert_arg<short>(arg, *it);
|
||||
break;
|
||||
case 'l':
|
||||
if (*it == 'l')
|
||||
convert_arg<long long>(arg, *++it);
|
||||
else
|
||||
convert_arg<long>(arg, *it);
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, *it);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<std::size_t>(arg, *it);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, *it);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
convert_arg<void>(arg, *it);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (!*it)
|
||||
FMT_THROW(format_error("invalid format string"));
|
||||
spec.type_ = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (spec.type_) {
|
||||
case 'i': case 'u':
|
||||
spec.type_ = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
// TODO: handle wchar_t better?
|
||||
fmt::visit(internal::char_converter<basic_printf_context>(arg), arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
fmt::visit(AF(buffer, spec, *this), arg);
|
||||
}
|
||||
buffer.append(pointer_from(start), pointer_from(it));
|
||||
}
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void printf(internal::basic_buffer<Char> &buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
|
||||
template <typename Buffer>
|
||||
struct printf_context {
|
||||
typedef basic_printf_context<
|
||||
std::back_insert_iterator<Buffer>, typename Buffer::value_type> type;
|
||||
};
|
||||
|
||||
template <typename ...Args>
|
||||
inline format_arg_store<printf_context<internal::buffer>::type, Args...>
|
||||
make_printf_args(const Args & ... args) {
|
||||
return format_arg_store<printf_context<internal::buffer>::type, Args...>(
|
||||
args...);
|
||||
}
|
||||
typedef basic_format_args<printf_context<internal::buffer>::type> printf_args;
|
||||
typedef basic_format_args<printf_context<internal::wbuffer>::type> wprintf_args;
|
||||
|
||||
inline std::string vsprintf(string_view format, printf_args args) {
|
||||
memory_buffer buffer;
|
||||
printf(buffer, format, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline std::string sprintf(string_view format_str, const Args & ... args) {
|
||||
return vsprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::buffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline std::wstring vsprintf(wstring_view format, wprintf_args args) {
|
||||
wmemory_buffer buffer;
|
||||
printf(buffer, format, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline std::wstring sprintf(wstring_view format_str, const Args & ... args) {
|
||||
return vsprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline int vfprintf(std::FILE *f, basic_string_view<Char> format,
|
||||
basic_format_args<typename printf_context<
|
||||
internal::basic_buffer<Char>>::type> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, format, args);
|
||||
std::size_t size = buffer.size();
|
||||
return std::fwrite(
|
||||
buffer.data(), sizeof(Char), size, f) < size ? -1 : static_cast<int>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the file *f*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::FILE *f, string_view format_str, const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf<char>(f, format_str, vargs);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::FILE *f, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
return vfprintf(f, format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline int vprintf(string_view format, printf_args args) {
|
||||
return vfprintf(stdout, format, args);
|
||||
}
|
||||
|
||||
inline int vprintf(wstring_view format, wprintf_args args) {
|
||||
return vfprintf(stdout, format, args);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline int printf(string_view format_str, const Args & ... args) {
|
||||
return vprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::buffer>::type>(args...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int printf(wstring_view format_str, const Args & ... args) {
|
||||
return vprintf(format_str,
|
||||
make_format_args<typename printf_context<internal::wbuffer>::type>(args...));
|
||||
}
|
||||
|
||||
inline int vfprintf(std::ostream &os, string_view format_str,
|
||||
printf_args args) {
|
||||
memory_buffer buffer;
|
||||
printf(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
inline int vfprintf(std::wostream &os, wstring_view format_str,
|
||||
wprintf_args args) {
|
||||
wmemory_buffer buffer;
|
||||
printf(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::ostream &os, string_view format_str,
|
||||
const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf(os, format_str, vargs);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline int fprintf(std::wostream &os, wstring_view format_str,
|
||||
const Args & ... args) {
|
||||
auto vargs = make_format_args<
|
||||
typename printf_context<internal::buffer>::type>(args...);
|
||||
return vfprintf(os, format_str, vargs);
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
||||
308
ext/fmt-5.2.1/include/fmt/ranges.h
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
// Formatting library for C++ - the core API
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <type_traits>
|
||||
|
||||
// output only up to N items from the range.
|
||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
|
||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char>
|
||||
struct formatting_base {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_range : formatting_base<Char> {
|
||||
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
|
||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range.
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_tuple : formatting_base<Char> {
|
||||
Char prefix;
|
||||
Char delimiter;
|
||||
Char postfix;
|
||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
|
||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
void copy(const RangeT &range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
void copy(const char *str, OutputIterator out) {
|
||||
const char *p_curr = str;
|
||||
while (*p_curr) {
|
||||
*out++ = *p_curr++;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
void copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
}
|
||||
|
||||
/// Return true value if T has std::string interface, like std::string_view.
|
||||
template <typename T>
|
||||
class is_like_std_string {
|
||||
template <typename U>
|
||||
static auto check(U *p) ->
|
||||
decltype(p->find('a'), p->length(), p->data(), int());
|
||||
template <typename>
|
||||
static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(FMT_NULL))>::value;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename... Ts>
|
||||
struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void>
|
||||
struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||
template <typename T>
|
||||
struct is_range_<T, typename std::conditional<
|
||||
false,
|
||||
conditional_helper<decltype(internal::declval<T>().begin()),
|
||||
decltype(internal::declval<T>().end())>,
|
||||
void>::type> : std::true_type {};
|
||||
#endif
|
||||
|
||||
/// tuple_size and tuple_element check.
|
||||
template <typename T>
|
||||
class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U *p) ->
|
||||
decltype(std::tuple_size<U>::value,
|
||||
internal::declval<typename std::tuple_element<0, U>::type>(), int());
|
||||
template <typename>
|
||||
static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(FMT_NULL))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <std::size_t... N>
|
||||
using index_sequence = std::index_sequence<N...>;
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N>
|
||||
struct integer_sequence {
|
||||
typedef T value_type;
|
||||
|
||||
static FMT_CONSTEXPR std::size_t size() {
|
||||
return sizeof...(N);
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t... N>
|
||||
using index_sequence = integer_sequence<std::size_t, N...>;
|
||||
|
||||
template <typename T, std::size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = make_integer_sequence<std::size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple &&tup, F &&f) FMT_NOEXCEPT {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value>
|
||||
get_indexes(T const &) { return {}; }
|
||||
|
||||
template <class Tuple, class F>
|
||||
void for_each(Tuple &&tup, F &&f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template<typename Arg>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,
|
||||
typename std::enable_if<
|
||||
!is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) {
|
||||
return add_space ? " {}" : "{}";
|
||||
}
|
||||
|
||||
template<typename Arg>
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,
|
||||
typename std::enable_if<
|
||||
is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
|
||||
return add_space ? " \"{}\"" : "\"{}\"";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
|
||||
return add_space ? L" \"{}\"" : L"\"{}\"";
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
|
||||
return add_space ? " '{}'" : "'{}'";
|
||||
}
|
||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
||||
return add_space ? L" '{}'" : L"'{}'";
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
template <typename T>
|
||||
struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char,
|
||||
typename std::enable_if<fmt::is_tuple_like<TupleT>::value>::type> {
|
||||
private:
|
||||
// C++11 generic lambda for format()
|
||||
template <typename FormatContext>
|
||||
struct format_each {
|
||||
template <typename T>
|
||||
void operator()(const T& v) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.delimiter, out);
|
||||
}
|
||||
format_to(out,
|
||||
internal::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), v),
|
||||
v);
|
||||
++i;
|
||||
}
|
||||
|
||||
formatting_tuple<Char>& formatting;
|
||||
std::size_t& i;
|
||||
typename std::add_lvalue_reference<decltype(std::declval<FormatContext>().out())>::type out;
|
||||
};
|
||||
|
||||
public:
|
||||
formatting_tuple<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
std::size_t i = 0;
|
||||
internal::copy(formatting.prefix, out);
|
||||
|
||||
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_range_<T>::value && !internal::is_like_std_string<T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
struct formatter<RangeT, Char,
|
||||
typename std::enable_if<fmt::is_range<RangeT>::value>::type> {
|
||||
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(
|
||||
const RangeT &values, FormatContext &ctx) {
|
||||
auto out = ctx.out();
|
||||
internal::copy(formatting.prefix, out);
|
||||
std::size_t i = 0;
|
||||
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.delimiter, out);
|
||||
}
|
||||
format_to(out,
|
||||
internal::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
||||
*it);
|
||||
if (++i > formatting.range_length_limit) {
|
||||
format_to(out, " ... <other elements>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.postfix, out);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
|
||||
156
ext/fmt-5.2.1/include/fmt/time.h
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// Formatting library for C++ - time formatting
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_TIME_H_
|
||||
#define FMT_TIME_H_
|
||||
|
||||
#include "format.h"
|
||||
#include <ctime>
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
// Prevents expansion of a preceding token as a function-style macro.
|
||||
// Usage: f FMT_NOMACRO()
|
||||
#define FMT_NOMACRO
|
||||
|
||||
namespace internal{
|
||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
||||
inline null<> localtime_s(...) { return null<>(); }
|
||||
inline null<> gmtime_r(...) { return null<>(); }
|
||||
inline null<> gmtime_s(...) { return null<>(); }
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::localtime
|
||||
inline std::tm localtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(localtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(localtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
bool fallback(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
std::tm *tm = std::localtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
};
|
||||
dispatcher lt(time);
|
||||
if (lt.run())
|
||||
return lt.tm_;
|
||||
// Too big time values may be unsupported.
|
||||
FMT_THROW(format_error("time_t value out of range"));
|
||||
}
|
||||
|
||||
// Thread-safe replacement for std::gmtime
|
||||
inline std::tm gmtime(std::time_t time) {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t): time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
return handle(gmtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm *tm) { return tm != FMT_NULL; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
return fallback(gmtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
bool fallback(internal::null<>) {
|
||||
std::tm *tm = std::gmtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != FMT_NULL;
|
||||
}
|
||||
};
|
||||
dispatcher gt(time);
|
||||
if (gt.run())
|
||||
return gt.tm_;
|
||||
// Too big time values may be unsupported.
|
||||
FMT_THROW(format_error("time_t value out of range"));
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
inline std::size_t strftime(char *str, std::size_t count, const char *format,
|
||||
const std::tm *time) {
|
||||
return std::strftime(str, count, format, time);
|
||||
}
|
||||
|
||||
inline std::size_t strftime(wchar_t *str, std::size_t count,
|
||||
const wchar_t *format, const std::tm *time) {
|
||||
return std::wcsftime(str, count, format, time);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<std::tm, Char> {
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
|
||||
auto it = internal::null_terminating_iterator<Char>(ctx);
|
||||
if (*it == ':')
|
||||
++it;
|
||||
auto end = it;
|
||||
while (*end && *end != '}')
|
||||
++end;
|
||||
tm_format.reserve(end - it + 1);
|
||||
using internal::pointer_from;
|
||||
tm_format.append(pointer_from(it), pointer_from(end));
|
||||
tm_format.push_back('\0');
|
||||
return pointer_from(end);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) {
|
||||
internal::basic_buffer<Char> &buf = internal::get_container(ctx.out());
|
||||
std::size_t start = buf.size();
|
||||
for (;;) {
|
||||
std::size_t size = buf.capacity() - start;
|
||||
std::size_t count =
|
||||
internal::strftime(&buf[start], size, &tm_format[0], &tm);
|
||||
if (count != 0) {
|
||||
buf.resize(start + count);
|
||||
break;
|
||||
}
|
||||
if (size >= tm_format.size() * 256) {
|
||||
// If the buffer is 256 times larger than the format string, assume
|
||||
// that `strftime` gives an empty result. There doesn't seem to be a
|
||||
// better way to distinguish the two cases:
|
||||
// https://github.com/fmtlib/fmt/issues/367
|
||||
break;
|
||||
}
|
||||
const std::size_t MIN_GROWTH = 10;
|
||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
||||
}
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
basic_memory_buffer<Char> tm_format;
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_TIME_H_
|
||||
53
ext/fmt-5.2.1/src/format.cc
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template struct internal::basic_data<void>;
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API char internal::thousands_sep(locale_provider *lp);
|
||||
|
||||
template void internal::basic_buffer<char>::append(const char *, const char *);
|
||||
|
||||
template void basic_fixed_buffer<char>::grow(std::size_t);
|
||||
|
||||
template void internal::arg_map<format_context>::init(
|
||||
const basic_format_args<format_context> &args);
|
||||
|
||||
template FMT_API int internal::char_traits<char>::format_float(
|
||||
char *, std::size_t, const char *, int, double);
|
||||
|
||||
template FMT_API int internal::char_traits<char>::format_float(
|
||||
char *, std::size_t, const char *, int, long double);
|
||||
|
||||
template FMT_API std::string internal::vformat<char>(
|
||||
string_view, basic_format_args<format_context>);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API wchar_t internal::thousands_sep(locale_provider *);
|
||||
|
||||
template void internal::basic_buffer<wchar_t>::append(
|
||||
const wchar_t *, const wchar_t *);
|
||||
|
||||
template void basic_fixed_buffer<wchar_t>::grow(std::size_t);
|
||||
|
||||
template void internal::arg_map<wformat_context>::init(
|
||||
const basic_format_args<wformat_context> &);
|
||||
|
||||
template FMT_API int internal::char_traits<wchar_t>::format_float(
|
||||
wchar_t *, std::size_t, const wchar_t *, int, double);
|
||||
|
||||
template FMT_API int internal::char_traits<wchar_t>::format_float(
|
||||
wchar_t *, std::size_t, const wchar_t *, int, long double);
|
||||
|
||||
template FMT_API std::wstring internal::vformat<wchar_t>(
|
||||
wstring_view, basic_format_args<wformat_context>);
|
||||
FMT_END_NAMESPACE
|
||||
244
ext/fmt-5.2.1/src/posix.cc
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
// A C++ interface to POSIX functions.
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
// Disable bogus MSVC warnings.
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/posix.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# include <io.h>
|
||||
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# endif
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
#ifdef fileno
|
||||
# undef fileno
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
typedef int RWResult;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
inline unsigned convert_rwcount(std::size_t count) {
|
||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||
}
|
||||
#else
|
||||
// Return type of read and write functions.
|
||||
typedef ssize_t RWResult;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
}
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
buffered_file::~buffered_file() FMT_NOEXCEPT {
|
||||
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||
FMT_RETRY_VAL(file_,
|
||||
FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), FMT_NULL);
|
||||
if (!file_)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
|
||||
}
|
||||
|
||||
void buffered_file::close() {
|
||||
if (!file_)
|
||||
return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = FMT_NULL;
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
// A macro used to prevent expansion of fileno on broken versions of MinGW.
|
||||
#define FMT_ARGS
|
||||
|
||||
int buffered_file::fileno() const {
|
||||
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
|
||||
if (fd == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
||||
return fd;
|
||||
}
|
||||
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
||||
#else
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
#endif
|
||||
if (fd_ == -1)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
|
||||
}
|
||||
|
||||
file::~file() FMT_NOEXCEPT {
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
void file::close() {
|
||||
if (fd_ == -1)
|
||||
return;
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
long long file::size() const {
|
||||
#ifdef _WIN32
|
||||
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
||||
// is less than 0x0500 as is the case with some default MinGW builds.
|
||||
// Both functions support large file sizes.
|
||||
DWORD size_upper = 0;
|
||||
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
|
||||
DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
|
||||
if (size_lower == INVALID_FILE_SIZE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != NO_ERROR)
|
||||
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
|
||||
}
|
||||
unsigned long long long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
#else
|
||||
typedef struct stat Stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||
"return type of file::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t file::read(void *buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
return internal::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void *buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
return internal::to_unsigned(result);
|
||||
}
|
||||
|
||||
file file::dup(int fd) {
|
||||
// Don't retry as dup doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||
if (new_fd == -1)
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
|
||||
return file(new_fd);
|
||||
}
|
||||
|
||||
void file::dup2(int fd) {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
FMT_THROW(system_error(errno,
|
||||
"cannot duplicate file descriptor {} to {}", fd_, fd));
|
||||
}
|
||||
}
|
||||
|
||||
void file::dup2(int fd, error_code &ec) FMT_NOEXCEPT {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1)
|
||||
ec = error_code(errno);
|
||||
}
|
||||
|
||||
void file::pipe(file &read_end, file &write_end) {
|
||||
// Close the descriptors first to make sure that assignments don't throw
|
||||
// and there are no leaks.
|
||||
read_end.close();
|
||||
write_end.close();
|
||||
int fds[2] = {};
|
||||
#ifdef _WIN32
|
||||
// Make the default pipe capacity same as on Linux 2.6.11+.
|
||||
enum { DEFAULT_CAPACITY = 65536 };
|
||||
int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
|
||||
#else
|
||||
// Don't retry as the pipe function doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
#endif
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, "cannot create pipe"));
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
read_end = file(fds[0]);
|
||||
write_end = file(fds[1]);
|
||||
}
|
||||
|
||||
buffered_file file::fdopen(const char *mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
if (!f)
|
||||
FMT_THROW(system_error(errno,
|
||||
"cannot associate stream with file descriptor"));
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return bf;
|
||||
}
|
||||
|
||||
long getpagesize() {
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
#else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0)
|
||||
FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
return size;
|
||||
#endif
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 8d626a24125264dee5cfa549a59b0a7a7a732e6a
|
||||
Subproject commit 9f53fbba7d910ca06f73149d5ec375f73a85e5ac
|
||||
2
ext/sgp4
|
|
@ -1 +1 @@
|
|||
Subproject commit f5cb54b382a5b4787432ab5b9a1e83de1a224610
|
||||
Subproject commit 6b47861cd47a6e31841260c47a52b579f8cf2fa9
|
||||
218
fixhunter.cc
|
|
@ -1,218 +0,0 @@
|
|||
#include "fixhunter.hh"
|
||||
#include <iostream>
|
||||
#include "bits.hh"
|
||||
#include "rs.hh"
|
||||
#include "ephemeris.hh"
|
||||
#include "galileo.hh"
|
||||
using namespace std;
|
||||
|
||||
void FixHunter::reportInav(const vector<uint8_t>& inav, int32_t gst)
|
||||
{
|
||||
int wtype = getbitu(&inav[0], 0, 6);
|
||||
|
||||
if(wtype >= 1 && wtype <=4) {
|
||||
d_latestiod = getbitu(&inav[0], 6, 10);
|
||||
if(wtype == 1) inav1 = inav;
|
||||
else if(wtype == 2) inav2 = inav;
|
||||
else if(wtype == 3) inav3 = inav;
|
||||
else if(wtype == 4) inav4 = inav;
|
||||
}
|
||||
else if(wtype == 16) inav16 = inav;
|
||||
else if(wtype == 17) inav17 = inav;
|
||||
else if(wtype == 18) inav18 = inav;
|
||||
else if(wtype == 19) inav19 = inav;
|
||||
else if(wtype == 20) inav20 = inav;
|
||||
else return;
|
||||
|
||||
if(wtype == 16) {
|
||||
GalileoMessage gm;
|
||||
gm.parse(inav);
|
||||
int32_t t0r = 1 + gst - (gst%30);
|
||||
d_inav16t0r = t0r;
|
||||
cout<<" redced af0red "<< 1000000000.0*ldexp(gm.af0red, -26)<<" ns, "<<3600.0*(1000000000.0/(1<<20))*ldexp(gm.af1red, -15)<<" ns/hour ("<<gm.af1red<<") t0r "<<t0r<<" ";
|
||||
|
||||
|
||||
//(30*((nmm.gi().gnsstow()-2)/30)+1) % 604800; // page 56 of the OSS ICD 2.0
|
||||
REDCEDAdaptor rca(gm, t0r);
|
||||
|
||||
Point pointRed;
|
||||
cout<<"eyred "<<gm.eyred<<" exred "<<gm.exred<<"\nlambda0red in rad "<< ldexp(M_PI*gm.lambda0red, -22)<<" atan2 " <<atan2(1.0*gm.eyred, 1.0*gm.exred)<<" deltaAred "<<gm.deltaAred<<endl;
|
||||
getCoordinates(gst, rca, &pointRed, false);
|
||||
cout<<"Reduced coordinates: "<<pointRed<<endl;
|
||||
}
|
||||
|
||||
tryFix(gst);
|
||||
}
|
||||
|
||||
void FixHunter::tryFix(int32_t gst)
|
||||
{
|
||||
RSCodec rsc({0, 2, 3, 4, 8}, 1, 1, 60, 137);
|
||||
string in;
|
||||
vector<unsigned int> corr;
|
||||
|
||||
int cnt=0;
|
||||
for(const auto& theinav : {inav4, inav3, inav2, inav1, inav20, inav19, inav18, inav17})
|
||||
if(!theinav.empty()) ++cnt;
|
||||
if(cnt < 4)
|
||||
return;
|
||||
|
||||
auto blankOut = [&in, &corr](int amount) {
|
||||
for(int p=0; p < amount ; ++p) {
|
||||
corr.push_back(in.size());
|
||||
in.append(1, (char)0); // we just don't have the information
|
||||
}
|
||||
};
|
||||
|
||||
int wtype=4;
|
||||
int navcount=0;
|
||||
for(const auto& theinav : {inav4, inav3, inav2}) {
|
||||
if(!theinav.empty()) {
|
||||
++navcount;
|
||||
cout<<"Have wtype "<<wtype<<endl;
|
||||
unsigned char tmp[14];
|
||||
for(int i=0; i < 14; i++)
|
||||
setbitu(tmp, i*8, 8, getbitu(&theinav[0], 16+i*8, 8));
|
||||
std::reverse(begin(tmp), end(tmp));
|
||||
in.append((char*)tmp, 14);
|
||||
}
|
||||
else {
|
||||
cout<<"Blanking wtype "<<wtype<<endl;
|
||||
blankOut(14);
|
||||
}
|
||||
wtype--;
|
||||
}
|
||||
|
||||
if(!inav1.empty()) {
|
||||
cout<<"Have wtype "<<wtype<<endl;
|
||||
++navcount;
|
||||
unsigned char tmp[16];
|
||||
setbitu(tmp, 0, 6, 1); // wtype 1 somehow
|
||||
setbitu(tmp, 6, 2, getbitu(&inav1[0], 14, 2)); // last 2 bits of iodnav
|
||||
setbitu(tmp, 8, 8, getbitu(&inav1[0], 6, 8)); // first 8 bits of iodnav
|
||||
for(int i=0; i < 14; ++i)
|
||||
setbitu(tmp, 16+i*8, 8, getbitu(&inav1[0], 16+i*8, 8));
|
||||
std::reverse(begin(tmp), end(tmp));
|
||||
in.append((char*)tmp, 16);
|
||||
}
|
||||
else {
|
||||
cout<<"Blanking wtype "<< wtype<<", which is special"<<endl;
|
||||
blankOut(14); // 14 symbols we don't have
|
||||
unsigned char tmp[2];
|
||||
// and two that we can fake:
|
||||
setbitu(tmp, 0, 6, 1); // wtype 1 somehow
|
||||
setbitu(tmp, 6, 2, d_latestiod & 3); // last 2 bits of iodnav
|
||||
setbitu(tmp, 8, 8, d_latestiod >> 2);
|
||||
std::reverse(begin(tmp), end(tmp));
|
||||
in.append((char*)tmp, 2);
|
||||
}
|
||||
|
||||
// now the parity bits
|
||||
|
||||
wtype=20;
|
||||
for(const auto& theinav : {inav20, inav19, inav18, inav17}) {
|
||||
if(!theinav.empty()) {
|
||||
cout<<"Have wtype "<<wtype<<endl;
|
||||
GalileoMessage gm;
|
||||
gm.parse(theinav);
|
||||
|
||||
std::reverse(gm.rsparity.begin(), gm.rsparity.end());
|
||||
in += gm.rsparity;
|
||||
}
|
||||
else {
|
||||
cout<<"Blanking wtype "<<wtype<<endl;
|
||||
|
||||
blankOut(15);
|
||||
}
|
||||
--wtype;
|
||||
}
|
||||
|
||||
if(corr.size() >= 60 && navcount != 4) {
|
||||
cout<<"Too many erasures ("<<corr.size()<<"), can't correct"<<endl;
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto& c : corr)
|
||||
c = 137 + c;
|
||||
|
||||
|
||||
string out;
|
||||
cout<<"Input size "<<in.size()<<", corrections/erasure size "<<corr.size()<<", decode status: "<<endl;
|
||||
try {
|
||||
if(corr.size() < 60) {
|
||||
cout<<"Got a reconstructed ephemeris! With "<<rsc.decode(in, out, &corr)<<" corrections"<<endl;
|
||||
for(const auto& c : corr) {
|
||||
cout<<c-137<<" "; // padding!
|
||||
}
|
||||
cout<<endl;
|
||||
}
|
||||
else {
|
||||
cout<<"Had all 4 nav words but no parity, not doing corrections"<<endl;
|
||||
out=in;
|
||||
}
|
||||
struct GalileoMessage gm=fillGMFromRS(out);
|
||||
Point point;
|
||||
getCoordinates(gst, gm, &point, false);
|
||||
cout<<"full coordinates: "<<point<<endl;
|
||||
|
||||
if(!inav16.empty()) {
|
||||
GalileoMessage gm16;
|
||||
gm16.parse(inav16);
|
||||
REDCEDAdaptor rca(gm16, d_inav16t0r);
|
||||
|
||||
Point pointRed;
|
||||
cout<<"eyred "<<gm.eyred<<" exred "<<gm.exred<<"\nlambda0red in rad "<< ldexp(M_PI*gm.lambda0red, -22)<<" atan2 " <<atan2(1.0*gm.eyred, 1.0*gm.exred)<<" deltaAred "<<gm.deltaAred<<endl;
|
||||
getCoordinates(gst, rca, &pointRed);
|
||||
cout<<"Reduced coordinates: "<<pointRed<<", distance: ";
|
||||
Vector dist(pointRed, point);
|
||||
|
||||
cout<<"Distance: "<<dist<<", length "<<dist.length()<<", clockdiff "<<
|
||||
(rca.getAtomicOffset(gst).first - gm.getAtomicOffset(gst).first)/3<<"m"<<endl;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
cout << "failed"<<endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct GalileoMessage FixHunter::fillGMFromRS(const std::string& out)
|
||||
{
|
||||
// we need to reconstruct words 1, 2, 3 and 4 and feed them to the parser
|
||||
vector<uint8_t> inav[5];
|
||||
string inavraw[5];
|
||||
inavraw[4] = out.substr(0, 14);
|
||||
inavraw[3] = out.substr(14, 14);
|
||||
inavraw[2] = out.substr(28, 14);
|
||||
inavraw[1] = out.substr(42, 16);
|
||||
for(int n=1; n<=4; ++n)
|
||||
reverse(inavraw[n].begin(), inavraw[n].end());
|
||||
|
||||
|
||||
uint8_t tmp[16];
|
||||
setbitu(tmp, 0, 6, 1); // wtype 1
|
||||
setbitu(tmp, 6, 2, getbitu((unsigned char*) inavraw[1].c_str(), 14, 2));
|
||||
setbitu(tmp, 8, 8, getbitu((unsigned char*) inavraw[1].c_str(), 6, 8));
|
||||
for(int n=0; n < 14; ++n)
|
||||
setbitu(tmp, 16 + n*8, 8, getbitu((unsigned char*) inavraw[1].c_str(), 16 + n*8, 8));
|
||||
|
||||
inav[1]= makeVec(tmp, 16);
|
||||
|
||||
struct GalileoMessage gm={};
|
||||
gm.parse(inav[1]);
|
||||
cout<<"wtype: "<<(int)gm.wtype<<", iod "<<gm.iodnav<<endl;
|
||||
cout<<"T0e "<<gm.getT0e()<<" e "<<gm.getE()<<" sqrtA " << gm.getSqrtA() << endl;
|
||||
|
||||
for(int i = 2; i <= 4; ++i) {
|
||||
setbitu(tmp, 0, 6, i);
|
||||
setbitu(tmp, 6, 10, getbitu((unsigned char*) inav[1].c_str(), 6, 10)); // fake in IOD from inav1
|
||||
for(int n=0; n < 14; ++n)
|
||||
setbitu(tmp, 16 + n*8, 8, getbitu((unsigned char*) inavraw[i].c_str(), n*8, 8));
|
||||
inav[i]= makeVec(tmp, 16);
|
||||
gm.parse(inav[i]);
|
||||
cout<<"wtype: "<<(int)gm.wtype<<", iod "<<gm.iodnav<<endl;
|
||||
}
|
||||
return gm;
|
||||
}
|
||||
15
fixhunter.hh
|
|
@ -1,15 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include "galileo.hh"
|
||||
|
||||
class FixHunter
|
||||
{
|
||||
public:
|
||||
void reportInav(const std::vector<uint8_t>& inav, int32_t gst);
|
||||
private:
|
||||
void tryFix(int32_t gst);
|
||||
struct GalileoMessage fillGMFromRS(const std::string& out);
|
||||
std::vector<uint8_t> inav1, inav2, inav3, inav4, inav16, inav17, inav18, inav19, inav20;
|
||||
int d_latestiod;
|
||||
uint32_t d_inav16t0r;
|
||||
};
|
||||
138
galileo.cc
|
|
@ -1,7 +1,7 @@
|
|||
#include "bits.hh"
|
||||
#include "galileo.hh"
|
||||
|
||||
bool getTOWFromInav(const std::vector<uint8_t>& inav, uint32_t *satTOW, uint16_t *wn)
|
||||
bool getTOWFromInav(std::basic_string_view<uint8_t> inav, uint32_t *satTOW, uint16_t *wn)
|
||||
{
|
||||
unsigned int wtype = getbitu(&inav[0], 0, 6);
|
||||
if(wtype==0) {
|
||||
|
|
@ -24,139 +24,3 @@ bool getTOWFromInav(const std::vector<uint8_t>& inav, uint32_t *satTOW, uint16_t
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
int GalileoMessage::parseFnav(const std::vector<uint8_t>& page)
|
||||
{
|
||||
const uint8_t* ptr = &page[0];
|
||||
int offset=0;
|
||||
auto gbum=[&ptr, &offset](int bits) {
|
||||
unsigned int ret = getbitu(ptr, offset, bits);
|
||||
offset += bits;
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto gbsm=[&ptr, &offset](int bits) {
|
||||
int ret = getbits(ptr, offset, bits);
|
||||
offset += bits;
|
||||
return ret;
|
||||
};
|
||||
|
||||
wtype = gbum(6);
|
||||
if(wtype == 1) {
|
||||
/*int sv = */ (void)gbum(6);
|
||||
iodnav = gbum(10);
|
||||
t0c = gbum(14);
|
||||
af0 = gbsm(31);
|
||||
af1 = gbsm(21);
|
||||
af2 = gbsm(6);
|
||||
sisa = gbum(8);
|
||||
ai0 = gbum(11);
|
||||
ai1 = gbsm(11);
|
||||
ai2 = gbsm(14);
|
||||
sf1 = gbum(1);
|
||||
sf2 = gbum(1);
|
||||
sf3 = gbum(1);
|
||||
sf4 = gbum(1);
|
||||
sf5 = gbum(1);
|
||||
BGDE1E5a = gbsm(10);
|
||||
e5ahs = gbum(2);
|
||||
wn = gbum(12);
|
||||
tow = gbum(20);
|
||||
e5advs=gbum(1);
|
||||
}
|
||||
else if(wtype==2) {
|
||||
iodnav = gbum(10);
|
||||
m0 = gbsm(32);
|
||||
omegadot = gbsm(24);
|
||||
e = gbum(32);
|
||||
sqrtA = gbum(32);
|
||||
omega0 = gbsm(32);
|
||||
idot = gbsm(14);
|
||||
wn = gbum(12);
|
||||
tow = gbum(20);
|
||||
}
|
||||
else if(wtype == 3) {
|
||||
iodnav = gbum(10);
|
||||
i0 = gbsm(32);
|
||||
omega = gbsm(32);
|
||||
deltan = gbsm(16);
|
||||
cuc = gbsm(16);
|
||||
cus = gbsm(16);
|
||||
crc =gbsm(16);
|
||||
crs = gbsm(16);
|
||||
t0e = gbum(14);
|
||||
wn = gbum(12);
|
||||
tow = gbum(20);
|
||||
}
|
||||
else if(wtype == 4) {
|
||||
iodnav = gbum(10);
|
||||
cic = gbsm(16);
|
||||
cis = gbsm(16);
|
||||
|
||||
a0 = gbsm(32);
|
||||
a1 = gbsm(24);
|
||||
|
||||
dtLS= gbsm(8);
|
||||
t0t = gbum(8);
|
||||
wn0t = gbum(8);
|
||||
wnLSF = gbum(8);
|
||||
|
||||
dn = gbum(3);
|
||||
dtLSF = gbsm(8);
|
||||
|
||||
t0g = gbum(8);
|
||||
a0g = gbsm(16);
|
||||
a1g = gbsm(12);
|
||||
wn0g = gbum(6);
|
||||
tow = gbum(20);
|
||||
}
|
||||
else if(wtype == 5) { // almanac1, containing 1.5 satellites
|
||||
ioda = gbum(4);
|
||||
alma1.wnalmanac = gbum(2);
|
||||
alma1.t0almanac = gbum(10);
|
||||
alma1.svid = gbum(6);
|
||||
alma1.deltaSqrtA = gbsm(13);
|
||||
alma1.e = gbum(11);
|
||||
alma1.omega = gbum(16);
|
||||
alma1.deltai = gbum(11);
|
||||
alma1.Omega0 = gbum(16);
|
||||
alma1.Omegadot = gbum(11);
|
||||
alma1.M0 = gbum(16);
|
||||
alma1.af0 = gbsm(16);
|
||||
alma1.af1 = gbsm(13);
|
||||
alma1.e5ahs = gbum(2);
|
||||
|
||||
alma2.svid = gbum(6);
|
||||
alma2.deltaSqrtA = gbsm(13);
|
||||
alma2.e = gbum(11);
|
||||
alma2.omega = gbum(16);
|
||||
alma2.deltai = gbum(11);
|
||||
alma2.Omega0 = gbum(4);
|
||||
// omega02 .. is partial
|
||||
}
|
||||
else if(wtype == 6) { // almanac2, containing 1.5 satellites
|
||||
ioda = gbum(4);
|
||||
alma2.Omega0 = gbum(12); // PARTIAL, does not really work like this
|
||||
alma2.Omegadot = gbum(11);
|
||||
alma2.M0 = gbum(16);
|
||||
alma2.af0 = gbsm(16);
|
||||
alma2.af1 = gbsm(13);
|
||||
alma2.e5ahs = gbum(2);
|
||||
|
||||
alma3.svid = gbum(6);
|
||||
alma3.deltaSqrtA = gbsm(13);
|
||||
alma3.e = gbum(11);
|
||||
alma3.omega = gbum(16);
|
||||
alma3.deltai = gbum(11);
|
||||
alma3.Omega0 = gbum(4);
|
||||
alma3.M0 = gbum(16);
|
||||
alma3.af0 = gbsm(16);
|
||||
alma3.af1 = gbsm(13);
|
||||
alma3.e5ahs = gbum(2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return wtype;
|
||||
}
|
||||
|
||||
|
|
|
|||
190
galileo.hh
|
|
@ -2,55 +2,42 @@
|
|||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include "ephemeris.hh"
|
||||
#include "bits.hh"
|
||||
|
||||
bool getTOWFromInav(const std::vector<uint8_t>& inav, uint32_t *satTOW, uint16_t *wn);
|
||||
bool getTOWFromInav(std::basic_string_view<uint8_t> inav, uint32_t *satTOW, uint16_t *wn);
|
||||
|
||||
struct GalileoMessage : GPSLikeEphemeris
|
||||
struct GalileoMessage
|
||||
{
|
||||
uint8_t wtype;
|
||||
|
||||
typedef void (GalileoMessage::*func_t)(const std::vector<uint8_t>& page);
|
||||
std::map<int, func_t> parsers{
|
||||
{0, &GalileoMessage::parse0},
|
||||
{1, &GalileoMessage::parse1},
|
||||
{2, &GalileoMessage::parse2},
|
||||
{3, &GalileoMessage::parse3},
|
||||
{4, &GalileoMessage::parse4},
|
||||
{5, &GalileoMessage::parse5},
|
||||
{6, &GalileoMessage::parse6},
|
||||
{7, &GalileoMessage::parse7},
|
||||
{8, &GalileoMessage::parse8},
|
||||
{9, &GalileoMessage::parse9},
|
||||
{10, &GalileoMessage::parse10},
|
||||
{16, &GalileoMessage::parse16},
|
||||
{17, &GalileoMessage::parseRS},
|
||||
{18, &GalileoMessage::parseRS},
|
||||
{19, &GalileoMessage::parseRS},
|
||||
{20, &GalileoMessage::parseRS}
|
||||
};
|
||||
typedef void (GalileoMessage::*func_t)(std::basic_string_view<uint8_t> page);
|
||||
std::vector<func_t> parsers{
|
||||
&GalileoMessage::parse0,
|
||||
&GalileoMessage::parse1,
|
||||
&GalileoMessage::parse2,
|
||||
&GalileoMessage::parse3,
|
||||
&GalileoMessage::parse4,
|
||||
&GalileoMessage::parse5,
|
||||
&GalileoMessage::parse6,
|
||||
&GalileoMessage::parse7,
|
||||
&GalileoMessage::parse8,
|
||||
&GalileoMessage::parse9,
|
||||
&GalileoMessage::parse10
|
||||
};
|
||||
|
||||
|
||||
int parse(const std::vector<uint8_t>& page)
|
||||
int parse(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
wtype = getbitu(&page[0], 0, 6);
|
||||
if(!parsers.count(wtype)) {
|
||||
// std::cerr<<"Asked for impossible galileo type "<<(int)wtype<<std::endl;
|
||||
return wtype;
|
||||
}
|
||||
|
||||
std::invoke(parsers[wtype], this, page);
|
||||
std::invoke(parsers.at(wtype), this, page);
|
||||
return wtype;
|
||||
}
|
||||
|
||||
int parseFnav(const std::vector<uint8_t>& page);
|
||||
|
||||
uint8_t sparetime{0};
|
||||
uint16_t wn{0};
|
||||
uint32_t tow{0};
|
||||
uint16_t wn{0}; // we put the "unrolled" week number here!
|
||||
uint32_t tow{0}; // "last seen"
|
||||
int iodalmanac;
|
||||
int wnalmanac;
|
||||
int t0almanac;
|
||||
|
|
@ -58,7 +45,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
|
||||
|
||||
// spare word, only contains a WN and a TOW, but only if the 'time' field is set to 2
|
||||
void parse0(const std::vector<uint8_t>& page)
|
||||
void parse0(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
sparetime = getbitu(&page[0], 6, 2);
|
||||
if(sparetime == 2) {
|
||||
|
|
@ -71,13 +58,13 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
{
|
||||
}
|
||||
|
||||
uint8_t e5ahs{0}, e5bhs{0}, e1bhs{0};
|
||||
uint8_t e5bhs{0}, e1bhs{0};
|
||||
uint8_t gpshealth{0};
|
||||
uint16_t ai0{0};
|
||||
int16_t ai1{0}, ai2{0};
|
||||
bool sf1{0}, sf2{0}, sf3{0}, sf4{0}, sf5{0};
|
||||
int BGDE1E5a{0}, BGDE1E5b{0};
|
||||
bool e5advs{false}, e5bdvs{false}, e1bdvs{false};
|
||||
bool e5bdvs{false}, e1bdvs{false};
|
||||
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
|
||||
|
||||
//
|
||||
|
|
@ -107,29 +94,12 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
|
||||
uint16_t iodnav;
|
||||
|
||||
int16_t deltaAred; // 2^8 meters
|
||||
int16_t exred; // 2^-22 dimensionless
|
||||
int16_t eyred; // 2^-22 dimensionless
|
||||
int32_t deltai0red; // 2^-22 semi-circles
|
||||
int32_t omega0red; // 2^-22 semi-circles
|
||||
int32_t lambda0red; // 2^-22 semi-circles
|
||||
int32_t af0red; // 2^-26 s
|
||||
int32_t af1red; // 2^-35 s/s
|
||||
|
||||
uint8_t rs2bitiod;
|
||||
std::string rsparity;
|
||||
|
||||
int getIOD() const
|
||||
{
|
||||
return iodnav;
|
||||
}
|
||||
int ioda{-1}; // iod almanac
|
||||
struct Almanac
|
||||
{
|
||||
int svid{-1};
|
||||
int t0almanac, wnalmanac;
|
||||
int af0, af1;
|
||||
int e1bhs, e5bhs, e5ahs;
|
||||
int e1bhs, e5bhs;
|
||||
|
||||
uint32_t e, deltaSqrtA;
|
||||
int32_t M0, Omega0, deltai, omega, Omegadot;
|
||||
|
|
@ -156,15 +126,12 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
double getCrc() const { return 0; } // meters
|
||||
double getCrs() const { return 0; } // meters
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
int getIOD() const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
} alma1, alma2, alma3;
|
||||
|
||||
|
||||
// an ephemeris word
|
||||
void parse1(const std::vector<uint8_t>& page)
|
||||
void parse1(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
iodnav = getbitu(&page[0], 6, 10);
|
||||
t0e = getbitu(&page[0], 16, 14);
|
||||
|
|
@ -174,7 +141,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
}
|
||||
|
||||
// another ephemeris word
|
||||
void parse2(const std::vector<uint8_t>& page)
|
||||
void parse2(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
iodnav = getbitu(&page[0], 6, 10);
|
||||
omega0 = getbits(&page[0], 16, 32);
|
||||
|
|
@ -184,7 +151,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
}
|
||||
|
||||
// yet another ephemeris word
|
||||
void parse3(const std::vector<uint8_t>& page)
|
||||
void parse3(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
iodnav = getbitu(&page[0], 6, 10);
|
||||
omegadot = getbits(&page[0], 16, 24);
|
||||
|
|
@ -209,7 +176,6 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
return t0g * 3600;
|
||||
}
|
||||
|
||||
// pair of nanosecond, nanosecond/s
|
||||
std::pair<double, double> getAtomicOffset(int tow) const
|
||||
{
|
||||
int delta = ephAge(tow, getT0c());
|
||||
|
|
@ -239,12 +205,10 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
// pair of nanosecond, nanosecond/s
|
||||
|
||||
std::pair<double, double> getGPSOffset(int tow, int wn) const
|
||||
{
|
||||
int dw = (int)(wn%64) - (int)(wn0g%64);
|
||||
if(dw > 31)
|
||||
dw = dw - 64;
|
||||
int dw = (int)(uint8_t)wn - (int)(uint8_t) wn0g;
|
||||
int delta = dw*7*86400 + tow - getT0g(); // NOT ephemeris age tricks
|
||||
|
||||
// 2^-35 2^-51 3600
|
||||
|
|
@ -264,7 +228,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
|
||||
|
||||
// can't get enough of that ephemeris
|
||||
void parse4(const std::vector<uint8_t>& page)
|
||||
void parse4(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
iodnav = getbitu(&page[0], 6, 10);
|
||||
cic = getbits(&page[0], 22, 16);
|
||||
|
|
@ -277,7 +241,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
}
|
||||
|
||||
// ionospheric disturbance, health, group delay, time
|
||||
void parse5(const std::vector<uint8_t>& page)
|
||||
void parse5(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
ai0 = getbitu(&page[0], 6, 11);
|
||||
ai1 = getbits(&page[0], 17, 11); // ai1 & 2 are signed, 0 not
|
||||
|
|
@ -288,8 +252,8 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
sf3 = getbitu(&page[0], 44, 1);
|
||||
sf4 = getbitu(&page[0], 45, 1);
|
||||
sf5 = getbitu(&page[0], 46, 1);
|
||||
BGDE1E5a = getbits(&page[0], 47, 10); // 2^-32 s
|
||||
BGDE1E5b = getbits(&page[0], 57, 10); // 2^-32 s
|
||||
BGDE1E5a = getbits(&page[0], 47, 10);
|
||||
BGDE1E5b = getbits(&page[0], 57, 10);
|
||||
|
||||
e5bhs = getbitu(&page[0], 67, 2);
|
||||
e1bhs = getbitu(&page[0], 69, 2);
|
||||
|
|
@ -300,7 +264,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
}
|
||||
|
||||
// time stuff
|
||||
void parse6(const std::vector<uint8_t>& page)
|
||||
void parse6(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
a0 = getbits(&page[0], 6, 32);
|
||||
a1 = getbits(&page[0], 38, 24);
|
||||
|
|
@ -315,7 +279,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
}
|
||||
|
||||
// almanac
|
||||
void parse7(const std::vector<uint8_t>& page)
|
||||
void parse7(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
iodalmanac = getbitu(&page[0], 6, 4);
|
||||
alma1.wnalmanac = wnalmanac = getbitu(&page[0], 10, 2);
|
||||
|
|
@ -331,7 +295,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
|
||||
}
|
||||
// almanac
|
||||
void parse8(const std::vector<uint8_t>& page)
|
||||
void parse8(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
iodalmanac = getbitu(&page[0], 6, 4);
|
||||
alma1.af0 = getbits(&page[0], 10, 16);
|
||||
|
|
@ -350,7 +314,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
}
|
||||
|
||||
// almanac
|
||||
void parse9(const std::vector<uint8_t>& page)
|
||||
void parse9(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
iodalmanac = getbitu(&page[0], 6, 4);
|
||||
alma2.wnalmanac = wnalmanac = getbitu(&page[0], 10, 2);
|
||||
|
|
@ -371,7 +335,7 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
}
|
||||
|
||||
// almanac + more time stuff (GPS)
|
||||
void parse10(const std::vector<uint8_t>& page)
|
||||
void parse10(std::basic_string_view<uint8_t> page)
|
||||
{
|
||||
iodalmanac = getbitu(&page[0], 6, 4);
|
||||
alma3.Omega0 = getbits(&page[0], 10, 16);
|
||||
|
|
@ -389,35 +353,6 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
wn0g = getbitu(&page[0], 122, 6);
|
||||
}
|
||||
|
||||
|
||||
// reduced clock and ephemeris data (redced)
|
||||
void parse16(const std::vector<uint8_t>& page)
|
||||
{
|
||||
deltaAred = getbits(&page[0], 6, 5);
|
||||
exred = getbits(&page[0], 11, 13);
|
||||
eyred = getbits(&page[0], 24, 13);
|
||||
deltai0red = getbits(&page[0], 37, 17);
|
||||
omega0red = getbits(&page[0], 54, 23);
|
||||
lambda0red = getbits(&page[0], 77, 23);
|
||||
af0red = getbits(&page[0], 100, 22);
|
||||
af1red = getbits(&page[0], 122, 6);
|
||||
}
|
||||
|
||||
// reed-solomon data
|
||||
void parseRS(const std::vector<uint8_t>& page)
|
||||
{
|
||||
// see 5.1.13.2 of the Galileo SIS ICD 2.0
|
||||
rs2bitiod = getbitu(&page[0], 6+8, 2);
|
||||
|
||||
rsparity.clear();
|
||||
rsparity.append(1, getbitu(&page[0], 6, 8)); // first octet is different
|
||||
|
||||
for(int n = 0; n < 14; ++n)
|
||||
rsparity.append(1, getbitu(&page[0], 16+n*8, 8));
|
||||
|
||||
}
|
||||
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
return 3.986004418 * pow(10.0, 14.0);
|
||||
|
|
@ -441,53 +376,6 @@ struct GalileoMessage : GPSLikeEphemeris
|
|||
double getOmega0() const { return ldexp(omega0 * M_PI, -31); } // radians
|
||||
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
|
||||
double getOmega() const { return ldexp(omega * M_PI, -31); } // radians
|
||||
};
|
||||
|
||||
struct REDCEDAdaptor
|
||||
{
|
||||
REDCEDAdaptor(const GalileoMessage& gm, int32_t t0r) : d_gm(gm), d_t0r(t0r)
|
||||
{}
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
return 3.986004418 * pow(10.0, 14.0);
|
||||
} // m^3/s^2
|
||||
// same for galileo & gps
|
||||
double getOmegaE() const { return 7.2921151467 * pow(10.0, -5.0);} // rad/s
|
||||
|
||||
uint32_t getT0e() const { return d_t0r; }
|
||||
static constexpr double Anominal{29600000.0};
|
||||
double getSqrtA() const { return sqrt(Anominal + ldexp(1.0*d_gm.deltaAred, 8)); }
|
||||
double getE() const { return ldexp(sqrt(1.0*d_gm.exred*d_gm.exred + 1.0*d_gm.eyred*d_gm.eyred), -22); }
|
||||
double getCuc() const { return 0; } // radians
|
||||
double getCus() const { return 0; } // radians
|
||||
double getCrc() const { return 0; } // meters
|
||||
double getCrs() const { return 0; } // meters
|
||||
double getM0() const { return ldexp(M_PI * d_gm.lambda0red, -22) - getOmega(); } // lambda0red - omega, both radians
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
static constexpr double iNominal{56.0};
|
||||
double getI0() const { return M_PI*iNominal/180.0 + ldexp(d_gm.deltai0red * M_PI, -22); } // radians
|
||||
double getCic() const { return 0; } // radians
|
||||
double getCis() const { return 0; } // radians
|
||||
double getOmegadot() const { return 0; } // radians/s
|
||||
double getOmega0() const { return ldexp(d_gm.omega0red * M_PI, -22); } // radians
|
||||
double getIdot() const { return 0; } // radians/s
|
||||
double getOmega() const { return atan2(d_gm.eyred, d_gm.exred); } // radians
|
||||
|
||||
// pair of nanosecond, nanosecond/s
|
||||
std::pair<double, double> getAtomicOffset(int tow) const
|
||||
{
|
||||
int delta = ephAge(tow, d_t0r);
|
||||
// 2^-26 2^-35
|
||||
double cur = d_gm.af0red + ldexp(1.0*delta*d_gm.af1red, -9);
|
||||
double trend = ldexp(d_gm.af1red, -9);
|
||||
|
||||
// now in units of 2^-26 seconds, which are ~14.9 nanoseconds each
|
||||
double factor = ldexp(1000000000.0, -26);
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
|
||||
|
||||
const GalileoMessage d_gm;
|
||||
int32_t d_t0r;
|
||||
};
|
||||
|
|
|
|||
422
galmonmon.cc
|
|
@ -1,422 +0,0 @@
|
|||
#include <optional>
|
||||
#include "minicurl.hh"
|
||||
#include <iostream>
|
||||
#include "navmon.hh"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/printf.h"
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "CLI/CLI.hpp"
|
||||
#include "version.hh"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
static char program[]="galmonmon";
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern const char* g_gitHash;
|
||||
|
||||
/*
|
||||
Monitoring the satellites for sensible alerts.
|
||||
|
||||
A satellite transitions state if:
|
||||
* galmon is up
|
||||
* galmon is up to date
|
||||
* We are seeing recent messages for the satellite
|
||||
* For more than 60 seconds this state is different than it was
|
||||
|
||||
A satellite becomes 'unobserved' if:
|
||||
* galmon is up
|
||||
* galmon is up to date
|
||||
* We haven't seen a message since an hour
|
||||
|
||||
A satellite becomes observed again if we have seen it for > 60 seconds.
|
||||
|
||||
At startup, every satellite assumes, without alert, the status we find for it, which is not a transition.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
class StateKeeper
|
||||
{
|
||||
public:
|
||||
typedef std::variant<bool, double, string> var_t;
|
||||
void setBoolNames(string_view name, string_view offName, string_view onName);
|
||||
std::optional<string> reportState(string_view thing, string_view name, var_t state, const std::string& state_text="");
|
||||
std::optional<string> getState(string_view thing, string_view name);
|
||||
|
||||
std::optional<string> getPrevState(string_view thing, string_view name);
|
||||
|
||||
struct State
|
||||
{
|
||||
var_t state;
|
||||
time_t since;
|
||||
string text;
|
||||
};
|
||||
|
||||
std::optional<State> getFullState(string_view thing, string_view name);
|
||||
std::optional<State> getPrevFullState(string_view thing, string_view name);
|
||||
|
||||
private:
|
||||
struct Names
|
||||
{
|
||||
string offName, onName;
|
||||
};
|
||||
map<string, Names> names;
|
||||
|
||||
|
||||
struct ThingState
|
||||
{
|
||||
std::optional<State> prev, live, provisional;
|
||||
};
|
||||
|
||||
map<string, map<string, ThingState> > states;
|
||||
private:
|
||||
std::string getName(string_view name, var_t state)
|
||||
{
|
||||
if(auto ptr = std::get_if<bool>(&state)) {
|
||||
if(*ptr) {
|
||||
return names[(string)name].onName;
|
||||
}
|
||||
else
|
||||
return names[(string)name].offName;
|
||||
}
|
||||
if(auto ptr = std::get_if<string>(&state)) {
|
||||
return *ptr;
|
||||
}
|
||||
if(auto ptr = std::get_if<double>(&state)) {
|
||||
return to_string(*ptr);
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
};
|
||||
|
||||
void StateKeeper::setBoolNames(string_view name, string_view offName, string_view onName)
|
||||
{
|
||||
names[(string)name].offName = offName;
|
||||
names[(string)name].onName = onName;
|
||||
}
|
||||
|
||||
std::optional<string> StateKeeper::getState(string_view thing, string_view name)
|
||||
{
|
||||
if(states.count((string)thing) && states[(string)thing].count((string) name) && states[(string)thing][(string)name].live) {
|
||||
return getName(name, states[(string)thing][(string)name].live->state);
|
||||
}
|
||||
return std::optional<string>();
|
||||
}
|
||||
|
||||
std::optional<StateKeeper::State> StateKeeper::getFullState(string_view thing, string_view name)
|
||||
{
|
||||
if(states.count((string)thing) && states[(string)thing].count((string) name) && states[(string)thing][(string)name].live) {
|
||||
return states[(string)thing][(string)name].live;
|
||||
}
|
||||
return std::optional<StateKeeper::State>();
|
||||
}
|
||||
|
||||
|
||||
std::optional<string> StateKeeper::getPrevState(string_view thing, string_view name)
|
||||
{
|
||||
if(states.count((string)thing) && states[(string)thing].count((string) name) && states[(string)thing][(string)name].prev) {
|
||||
return getName(name, states[(string)thing][(string)name].prev->state);
|
||||
}
|
||||
return std::optional<string>();
|
||||
}
|
||||
|
||||
std::optional<StateKeeper::State> StateKeeper::getPrevFullState(string_view thing, string_view name)
|
||||
{
|
||||
if(states.count((string)thing) && states[(string)thing].count((string) name) && states[(string)thing][(string)name].prev) {
|
||||
return states[(string)thing][(string)name].prev;
|
||||
}
|
||||
return std::optional<StateKeeper::State>();
|
||||
}
|
||||
|
||||
|
||||
std::optional<string> StateKeeper::reportState(string_view thing, string_view name, var_t newstate, const std::string& state_text)
|
||||
{
|
||||
auto& state = states[(string)thing][(string)name];
|
||||
std::optional<string> ret;
|
||||
|
||||
time_t now = time(0);
|
||||
|
||||
if(!state.live) { // we had no state yet
|
||||
state.live = State{newstate, now, state_text};
|
||||
state.provisional.reset(); // for good measure
|
||||
return ret;
|
||||
}
|
||||
else if(state.live->state == newstate) { // confirmation of current state
|
||||
state.live->text = state_text; // update text perhaps
|
||||
state.provisional.reset();
|
||||
return ret;
|
||||
}
|
||||
else if(!state.provisional) { // new provisional state
|
||||
state.provisional = State{newstate, now, state_text};
|
||||
return ret;
|
||||
}
|
||||
else {
|
||||
if(now - state.provisional->since > 60) { // provisional state has been confirmed
|
||||
state.prev = state.live;
|
||||
state.live = state.provisional;
|
||||
state.provisional.reset();
|
||||
return getName(name, state.live->state);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
StateKeeper g_sk;
|
||||
|
||||
void sendTweet(const string& tweet)
|
||||
{
|
||||
string etweet = tweet;
|
||||
//system((string("twurl -X POST /1.1/statuses/update.json -d \"media_ids=1215649475231997953&status=")+etweet+"\" >> twitter.log").c_str());
|
||||
if(system((string("twurl -X POST /1.1/statuses/update.json -d \"status=")+etweet+"\" >> twitter.log").c_str()) < 0) {
|
||||
cout<<"Problem tweeting!"<<endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
MiniCurl mc;
|
||||
MiniCurl::MiniCurlHeaders mch;
|
||||
string url="https://galmon.eu/";
|
||||
// string url="http://[::1]:29599/";
|
||||
bool doVERSION{false};
|
||||
|
||||
CLI::App app(program);
|
||||
|
||||
app.add_flag("--version", doVERSION, "show program version and copyright");
|
||||
app.add_option("--url,-u", url, "URL of navparse process to retrieve status from");
|
||||
bool doTweet{false};
|
||||
app.add_flag("--tweet,-t", doTweet, "Actually send out tweets");
|
||||
|
||||
try {
|
||||
app.parse(argc, argv);
|
||||
} catch(const CLI::Error &e) {
|
||||
return app.exit(e);
|
||||
}
|
||||
|
||||
if(doVERSION) {
|
||||
showVersion(program, g_gitHash);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
g_sk.setBoolNames("health", "healthy", "unhealthy");
|
||||
g_sk.setBoolNames("eph-too-old", "ephemeris fresh", "ephemeris aged");
|
||||
g_sk.setBoolNames("silent", "observed", "not observed");
|
||||
g_sk.setBoolNames("osnma", "OFF", "ON");
|
||||
|
||||
std::variant<bool, string> tst;
|
||||
|
||||
auto observers = nlohmann::json::parse(mc.getURL(url+"observers.json"));
|
||||
|
||||
|
||||
// sendTweet("Galmonmon " +string(g_gitHash)+ " started, " + std::to_string(observers.size()) +" observers seen");
|
||||
cout<<("Galmonmon " +string(g_gitHash)+ " started, " + std::to_string(observers.size()) +" observers seen")<<endl;
|
||||
|
||||
string meh="🤔";
|
||||
string unhappy="😬";
|
||||
string alert="🚨";
|
||||
|
||||
for(;;) {
|
||||
try {
|
||||
auto res = mc.getURL(url+"global.json");
|
||||
auto j = nlohmann::json::parse(res);
|
||||
if(!j.count("last-seen") || time(0) - (long) j["last-seen"] > 30) {
|
||||
cout<<"Galmon at "<<url<<" is not current"<<endl;
|
||||
sleep(10);
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
if(auto iter = j.find("last-seen"); iter != j.end()) {
|
||||
cout<<"Galmon behind by " << (time(0) - (long) iter.value()) <<" seconds"<<endl;
|
||||
}
|
||||
*/
|
||||
|
||||
res = mc.getURL(url+"sbas.json");
|
||||
j = nlohmann::json::parse(res);
|
||||
std::optional<string> sbasHealthChange;
|
||||
for(auto iter = j.begin(); iter != j.end(); ++iter) {
|
||||
if(iter.value().count("health")) {
|
||||
string name = sbasName(atoi(iter.key().c_str()));
|
||||
sbasHealthChange = g_sk.reportState(name, "sbas-health", (string)iter.value()["health"]);
|
||||
// cout<<"Setting state for "<< name <<" to "<< (string)iter.value()["health"] << endl;
|
||||
|
||||
|
||||
if(sbasHealthChange) {
|
||||
ostringstream out;
|
||||
out<<"✈️ augmentation system "<<name<<" health changed: "<<*sbasHealthChange;
|
||||
cout<<out.str()<<endl;
|
||||
if(doTweet)
|
||||
sendTweet(out.str());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = mc.getURL(url+"svs.json");
|
||||
j = nlohmann::json::parse(res);
|
||||
bool first=true;
|
||||
bool globalOsnma=false;
|
||||
for(const auto& sv : j) {
|
||||
if(!sv.count("gnssid") || !sv.count("fullName") || !sv.count("sigid")) {
|
||||
cout<<"Skipping "<< sv.count("gnssid") <<", "<< sv.count("fullName") <<", " <<sv.count("sigid") <<endl;
|
||||
continue;
|
||||
}
|
||||
int gnssid = sv["gnssid"], sigid = sv["sigid"];
|
||||
string fullName = sv["fullName"];
|
||||
|
||||
if(!(gnssid == 2 && sigid==1) &&
|
||||
!(gnssid == 0 && sigid==0) /* &&
|
||||
!(gnssid == 3 && sigid==0) &&
|
||||
!(gnssid == 6 && sigid==0) */)
|
||||
continue;
|
||||
|
||||
int numfresh=0;
|
||||
// we only track "received status" for GPS and Galileo
|
||||
bool notseen= gnssid ==0 || gnssid==2;
|
||||
if(!sv.count("healthissue") || !sv.count("eph-age-m") || !sv.count("sisa") || !sv.count("perrecv")) {
|
||||
// cout<<"Skipping "<<fullName<<" in loop: "<<sv.count("healthissue")<<", "<<sv.count("eph-age-m") << ", "<<sv.count("perrecv")<<endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
for(const auto& recv : sv["perrecv"]) {
|
||||
if(!recv.count("last-seen-s")) {
|
||||
cout<<"Missing last-seen-s"<<endl;
|
||||
continue;
|
||||
}
|
||||
if((int)recv["last-seen-s"] < 60)
|
||||
numfresh++;
|
||||
if((int)recv["last-seen-s"] < 3600)
|
||||
notseen=false;
|
||||
}
|
||||
if(sv.count("osnma") && sv["osnma"]==true)
|
||||
globalOsnma |= 1;
|
||||
|
||||
|
||||
auto healthchange = g_sk.reportState(fullName, "health", sv["healthissue"]!=0);
|
||||
std::optional<string> tooOldChange;
|
||||
if(gnssid == 2)
|
||||
tooOldChange = g_sk.reportState(fullName, "eph-too-old", sv["eph-age-m"] > 105, fmt::sprintf("%.2f", (double)sv["eph-age-m"]));
|
||||
else
|
||||
tooOldChange = g_sk.reportState(fullName, "eph-too-old", sv["eph-age-m"] > 140, fmt::sprintf("%.2f", (double)sv["eph-age-m"]));
|
||||
|
||||
auto seenChange = g_sk.reportState(fullName, "silent", notseen);
|
||||
|
||||
auto sisaChange = g_sk.reportState(fullName, "sisa", (double) sv["sisa-m"], (string)sv["sisa"]);
|
||||
|
||||
double ephdisco = sv.count("latest-disco") ? (double)sv["latest-disco"] : -1.0;
|
||||
auto ephdiscochange = g_sk.reportState(fullName, "eph-disco", ephdisco);
|
||||
if(ephdisco == -1.0)
|
||||
ephdiscochange.reset();
|
||||
|
||||
double timedisco = sv.count("time-disco") ? fabs((double)sv["time-disco"]) : 0.0;
|
||||
auto timediscochange = g_sk.reportState(fullName, "time-disco", timedisco);
|
||||
|
||||
/*
|
||||
cout<<fullName<<": numfresh "<<numfresh << " healthissue "<<sv["healthissue"];
|
||||
cout<<" eph-age-m "<< fmt::sprintf("%.2f", (double)sv["eph-age-m"]);
|
||||
cout<<" not-seen "<<notseen;
|
||||
if(auto val = g_sk.getState(fullName, "eph-too-old"); val) {
|
||||
cout<<" eph-too-old \""<<*val<<"\"";
|
||||
}
|
||||
if(auto val = g_sk.getState(fullName, "health"); val) {
|
||||
cout<<" health \""<<*val<<"\"";
|
||||
}
|
||||
|
||||
if(ephdiscochange) {
|
||||
cout<<fullName <<": ephemeris (orbit description) discontinuity of "<< fmt::sprintf("%.02f", ephdisco)<<" meters"<<endl;
|
||||
}
|
||||
if(timediscochange) {
|
||||
cout<<fullName <<": clock jump of "<< fmt::sprintf("%.02f", timedisco)<<" nanoseconds (= " <<fmt::sprintf("%.01f meters)", timedisco/2.99)<<endl;
|
||||
}
|
||||
*/
|
||||
ostringstream out;
|
||||
|
||||
|
||||
if(healthchange)
|
||||
out<< *healthchange<<" ";
|
||||
if(tooOldChange) {
|
||||
out<< "Ephemeris age: "<<*tooOldChange<<", new value: "<< fmt::sprintf("%.02f", (double)sv["eph-age-m"])<<" minutes, old: ";
|
||||
out<< g_sk.getPrevFullState(fullName, "eph-too-old")->text <<" minutes";
|
||||
}
|
||||
if(seenChange)
|
||||
out<< *seenChange<<" ";
|
||||
|
||||
if(ephdiscochange && (gnssid ==0 || gnssid == 2) && ephdisco > 1.45) {
|
||||
if(ephdisco > 10)
|
||||
out<<alert;
|
||||
else if(ephdisco > 5)
|
||||
out<<unhappy;
|
||||
else
|
||||
out<<meh;
|
||||
|
||||
out<<" ephemeris (orbit description) discontinuity of "<< fmt::sprintf("%.02f", ephdisco)<<" meters"<<endl;
|
||||
}
|
||||
if(timediscochange && (gnssid == 2 && timedisco > 2.5)) {
|
||||
if(timedisco > 10)
|
||||
out<<alert;
|
||||
else if(timedisco > 5)
|
||||
out<<unhappy;
|
||||
else
|
||||
out<<meh;
|
||||
out<<" clock jump of "<< fmt::sprintf("%.02f", timedisco)<<" nanoseconds (= " <<fmt::sprintf("%.01f meters)", timedisco/2.99)<<endl;
|
||||
}
|
||||
|
||||
if(sisaChange) {
|
||||
ostringstream tmp;
|
||||
auto state = g_sk.getFullState(fullName, "sisa");
|
||||
auto prevState = g_sk.getPrevFullState(fullName, "sisa");
|
||||
tmp<< " SISA/URA reported ranging accuracy changed, new: "<<state->text<<", old: " << prevState->text;
|
||||
if(get<double>(state->state) > 3 || get<double>(prevState->state) > 3)
|
||||
out << tmp.str();
|
||||
else
|
||||
cout<<"Not reporting: "<<tmp.str()<<endl;
|
||||
}
|
||||
|
||||
string tweet;
|
||||
if(!out.str().empty()) {
|
||||
if(gnssid == 0)
|
||||
tweet = "GPS";
|
||||
else if(gnssid == 2)
|
||||
tweet = "Galileo";
|
||||
else if(gnssid == 3)
|
||||
tweet ="BeiDou";
|
||||
else if(gnssid== 6)
|
||||
tweet = "GLONASS";
|
||||
tweet += " " + fullName +": ";
|
||||
tweet += out.str();
|
||||
|
||||
|
||||
if(doTweet)
|
||||
sendTweet(tweet);
|
||||
if(first)
|
||||
cout<<"\n";
|
||||
first=false;
|
||||
cout<<humanTimeNow() <<" " << tweet << endl;
|
||||
}
|
||||
}
|
||||
|
||||
auto osnmachange = g_sk.reportState("global", "osnma", globalOsnma);
|
||||
if(osnmachange) {
|
||||
string tweet= "Galileo OSNMA new state: "+*osnmachange;
|
||||
cout<<humanTimeNow()<< " " <<tweet<<endl;
|
||||
if(doTweet) {
|
||||
sendTweet(tweet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cout<<".";
|
||||
cout.flush();
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
cerr<<"\nError: "<<e.what()<<endl;
|
||||
}
|
||||
sleep(20);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#include "githash.h"
|
||||
|
||||
const char* g_gitHash=GIT_HASH;
|
||||
99
glonass.cc
|
|
@ -1,19 +1,9 @@
|
|||
#include "glonass.hh"
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include "navmon.hh"
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
static const double ae = 6378.136; // km // IERS: 6378136.6
|
||||
static const double mu = 398.6004418E3; // km3/s2 // IERS: 3.986004418
|
||||
static const double J2 = 1082625.75E-9; // IERS: 1.0826359
|
||||
static const double oe = 7.2921151467E-5; // rad/s // IERS: 7.292115
|
||||
|
||||
// this strips out spare bits + parity, and leaves 10 clean 24 bit words
|
||||
std::vector<uint8_t> getGlonassMessage(const std::vector<uint8_t>& payload)
|
||||
std::basic_string<uint8_t> getGlonassMessage(std::basic_string_view<uint8_t> payload)
|
||||
{
|
||||
uint8_t buffer[4*4];
|
||||
|
||||
|
|
@ -21,7 +11,7 @@ std::vector<uint8_t> getGlonassMessage(const std::vector<uint8_t>& payload)
|
|||
setbitu(buffer, 32*w, 32, getbitu(&payload[0], w*32, 32));
|
||||
}
|
||||
|
||||
return makeVec(buffer, 16);
|
||||
return std::basic_string<uint8_t>(buffer, 16);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -57,88 +47,3 @@ uint32_t getGlonassT0e(time_t referencetime, int Tb)
|
|||
tm.tm_sec = 0;
|
||||
return timegm(&tm)-3*3600; // and back to UTC
|
||||
}
|
||||
|
||||
// y[0] .. y[2] --> X, Y, Z
|
||||
// y[3] .. y[5] --> Vx, Vy, Vz
|
||||
static void ff (const double A[3], const double y[6], double f[6])
|
||||
{
|
||||
double X = y[0];
|
||||
double Y = y[1];
|
||||
double Z = y[2];
|
||||
double Vx = y[3];
|
||||
double Vy = y[4];
|
||||
double Vz = y[5];
|
||||
double R = sqrt (X*X + Y*Y + Z*Z);
|
||||
double C0 = pow(R, -3);
|
||||
double C1 = 1.5*J2*ae*ae*pow(R, -5);
|
||||
double C2 = 5*pow(Z/R, 2);
|
||||
double CXY = C0 + C1 * (1 - C2);
|
||||
double CZ = C0 + C1 * (3 - C2);
|
||||
f[0] = Vx;
|
||||
f[1] = Vy;
|
||||
f[2] = Vz;
|
||||
f[3] = (- mu*CXY + oe*oe)*X + 2*oe*Vy + A[0];
|
||||
f[4] = (- mu*CXY + oe*oe)*Y - 2*oe*Vx + A[1];
|
||||
f[5] = - mu*CZ*Z + A[2];
|
||||
}
|
||||
|
||||
static void rk4step (const double A[3], double y[6], double h)
|
||||
{
|
||||
double k1[6], k2[6], k3[6], k4[6], z[6];
|
||||
|
||||
ff (A, y, k1);
|
||||
for (int j = 0; j < 6; j ++)
|
||||
z[j] = y[j] + 0.5*h*k1[j];
|
||||
|
||||
ff (A, z, k2);
|
||||
for (int j = 0; j < 6; j ++)
|
||||
z[j] = y[j] + 0.5*h*k2[j];
|
||||
|
||||
ff (A, z, k3);
|
||||
for (int j = 0; j < 6; j ++)
|
||||
z[j] = y[j] + h*k3[j];
|
||||
|
||||
ff (A, z, k4);
|
||||
for (int j = 0; j < 6; j ++)
|
||||
y[j] = y[j] + h * (k1[j] + 2*(k2[j] + k3[j]) + k4[j]) / 6;
|
||||
}
|
||||
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
static double passedMsec(const Clock::time_point& then, const Clock::time_point& now)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(now - then).count()/1000.0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static double passedMsec(const Clock::time_point& then)
|
||||
{
|
||||
return passedMsec(then, Clock::now());
|
||||
}
|
||||
#endif
|
||||
|
||||
double getCoordinates(double tow, const GlonassMessage& eph, Point* p)
|
||||
{
|
||||
// auto start = Clock::now();
|
||||
|
||||
double y0[6] = {ldexp(eph.x, -11), ldexp(eph.y, -11), ldexp(eph.z, -11),
|
||||
ldexp(eph.dx, -20), ldexp(eph.dy, -20), ldexp(eph.dz, -20)};
|
||||
double A[3] = {ldexp(eph.ddx, -30), ldexp(eph.ddy, -30), ldexp(eph.ddz, -30)};
|
||||
|
||||
// These all fake
|
||||
uint32_t glotime = eph.getGloTime();
|
||||
uint32_t gloT0e = getGlonassT0e(glotime + 820368000, eph.Tb);
|
||||
uint32_t ephtow = (gloT0e - 820368000) % (7*86400);
|
||||
|
||||
double DT = tow - ephtow;
|
||||
int n = abs (DT / 100) + 1; // integrate in roughly 100 second steps
|
||||
double h = DT / n;
|
||||
for (int j = 0; j < n; j ++)
|
||||
rk4step (A, y0, h);
|
||||
|
||||
*p = Point (1E3*y0[0], 1E3*y0[1], 1E3*y0 [2]);
|
||||
// static double total=0;
|
||||
// cout<<"Took: "<<(total+=passedMsec(start))<<" ms" <<endl;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
47
glonass.hh
|
|
@ -4,17 +4,13 @@
|
|||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#include "minivec.hh"
|
||||
std::vector<uint8_t> getGlonassessage(const std::vector<uint8_t>& payload);
|
||||
std::basic_string<uint8_t> getGlonassessage(std::basic_string_view<uint8_t> payload);
|
||||
|
||||
struct GlonassMessage
|
||||
{
|
||||
uint8_t strtype;
|
||||
|
||||
int parse(const std::vector<uint8_t>& gstr)
|
||||
int parse(std::basic_string_view<uint8_t> gstr)
|
||||
{
|
||||
strtype = getbitu(&gstr[0], 1, 4);
|
||||
if(strtype == 1) {
|
||||
|
|
@ -56,13 +52,10 @@ struct GlonassMessage
|
|||
double getdY() { return ldexp(dy*1000.0, -20); }
|
||||
double getdZ() { return ldexp(dz*1000.0, -20); }
|
||||
|
||||
// this is there to make doDoppler work, which sadly wants to do
|
||||
// arithmetic to get the age of an ephemeris
|
||||
double getT0e() const { return 0; }
|
||||
|
||||
double getRadius() { return sqrt(getX()*getX() + getY()*getY() + getZ()*getZ()); }
|
||||
|
||||
void parse1(const std::vector<uint8_t>& gstr)
|
||||
void parse1(std::basic_string_view<uint8_t> gstr)
|
||||
{
|
||||
hour = getbitu(&gstr[0], 9, 5);
|
||||
minute = getbitu(&gstr[0], 14, 6);
|
||||
|
|
@ -80,7 +73,7 @@ struct GlonassMessage
|
|||
An interval is 15 minutes long, plus a spacer of length described by P1. If P1 is zero, there is no spacer.
|
||||
*/
|
||||
|
||||
void parse2(const std::vector<uint8_t>& gstr)
|
||||
void parse2(std::basic_string_view<uint8_t> gstr)
|
||||
{
|
||||
Bn = getbitu(&gstr[0], 85-80, 3); // Health bit, only look at MSB, ignore the rest. 0 is ok.
|
||||
Tb = getbitu(&gstr[0], 85-76, 7);
|
||||
|
|
@ -95,7 +88,7 @@ struct GlonassMessage
|
|||
bool l_n;
|
||||
bool P, P3;
|
||||
uint16_t gamman;
|
||||
void parse3(const std::vector<uint8_t>& gstr)
|
||||
void parse3(std::basic_string_view<uint8_t> gstr)
|
||||
{
|
||||
z = getbitsglonass(&gstr[0], 85-35, 27); // 2^-11
|
||||
dz = getbitsglonass(&gstr[0], 85-64, 24); // 2^-20
|
||||
|
|
@ -123,7 +116,7 @@ struct GlonassMessage
|
|||
return 1000*ldexp(1000000.0*taun, -30);
|
||||
}
|
||||
|
||||
void parse4(const std::vector<uint8_t>& gstr)
|
||||
void parse4(std::basic_string_view<uint8_t> gstr)
|
||||
{
|
||||
NT = getbitu(&gstr[0], 85-26, 11);
|
||||
FT = getbitu(&gstr[0], 85-33, 4);
|
||||
|
|
@ -133,37 +126,18 @@ struct GlonassMessage
|
|||
P4 = getbitu(&gstr[0], 85-34, 1);
|
||||
deltaTaun = getbitsglonass(&gstr[0], 85 - 58, 4);
|
||||
}
|
||||
// nanosecond, nanosecond/s pair
|
||||
std::pair<double, double> getUTCOffset(int tow) const
|
||||
{
|
||||
std::pair<double, double> ret;
|
||||
ret.second=0;
|
||||
|
||||
ret.first = 1000000000.0*ldexp(tauc, -31); // this is Glonass-M
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<double, double> getGPSOffset(int tow) const
|
||||
{
|
||||
std::pair<double, double> ret;
|
||||
ret.second=0;
|
||||
ret.first = 1000000000.0*ldexp(taugps, -30);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
uint32_t getGloTime() const;
|
||||
|
||||
uint8_t n4{0}; // counting from 1996 ('n4=1'), this is the 4-year plan index we are currently in
|
||||
int32_t taugps;
|
||||
int32_t tauc;
|
||||
|
||||
void parse5(const std::vector<uint8_t>& gstr)
|
||||
void parse5(std::basic_string_view<uint8_t> gstr)
|
||||
{
|
||||
n4=getbitu(&gstr[0], 85-36, 5);
|
||||
taugps = getbitsglonass(&gstr[0], 85-31, 22);
|
||||
tauc = getbitsglonass(&gstr[0], 85-69, 32); // check the NEW ICD
|
||||
tauc = getbitsglonass(&gstr[0], 85-69, 32);
|
||||
l_n = getbitu(&gstr[0], 85 - 9, 1);
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +151,7 @@ struct GlonassMessage
|
|||
return ldexp(tlambdana, -5);
|
||||
}
|
||||
|
||||
void parse7_9_11_13_15(const std::vector<uint8_t>& gstr)
|
||||
void parse7_9_11_13_15(std::basic_string_view<uint8_t> gstr)
|
||||
{
|
||||
l_n = getbitu(&gstr[0], 85 - 9, 1);
|
||||
omegana = getbitsglonass(&gstr[0], 85-80, 16);
|
||||
|
|
@ -209,7 +183,7 @@ struct GlonassMessage
|
|||
return M_PI*63.0/180 + ldexp(M_PI* deltaina, -20);
|
||||
}
|
||||
|
||||
void parse6_8_10_12_14(const std::vector<uint8_t>& gstr)
|
||||
void parse6_8_10_12_14(std::basic_string_view<uint8_t> gstr)
|
||||
{
|
||||
CnA = getbitu(&gstr[0], 85-80, 1);
|
||||
nA = getbitu(&gstr[0], 85-77, 5);
|
||||
|
|
@ -221,4 +195,3 @@ struct GlonassMessage
|
|||
|
||||
};
|
||||
uint32_t getGlonassT0e(time_t referencetime, int Tb);
|
||||
double getCoordinates(double tow, const GlonassMessage& eph, Point* p);
|
||||
|
|
|
|||
81
gndate.cc
|
|
@ -1,81 +0,0 @@
|
|||
#include "navmon.hh"
|
||||
#include <iostream>
|
||||
#include "CLI/CLI.hpp"
|
||||
#include "version.hh"
|
||||
|
||||
extern const char* g_gitHash;
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
try
|
||||
{
|
||||
string program("gndate");
|
||||
CLI::App app(program);
|
||||
string date;
|
||||
int galwn{-1};
|
||||
bool doProgOutput{false};
|
||||
bool doGPSWN{false}, doGALWN{false}, doBEIDOUWN{false}, doVERSION{false}, doUTC{false};
|
||||
app.add_flag("--version", doVERSION, "show program version and copyright");
|
||||
app.add_option("--date,-d", date, "yyyy-mm-dd hh:mm[:ss] hh:mm yyyymmdd hhmm");
|
||||
app.add_option("--date-gal-wn", galwn, "Give data for this Galileo week number");
|
||||
app.add_flag("--utc,-u", doUTC, "Interpret --date,-d as UTC");
|
||||
app.add_flag("--gps-wn", doGPSWN, "Print GPS week number");
|
||||
app.add_flag("--gal-wn", doGALWN, "Print GPS week number");
|
||||
app.add_flag("--prog-output", doProgOutput, "Modulate some date formats for use as parameters to programs");
|
||||
try {
|
||||
app.parse(argc, argv);
|
||||
} catch(const CLI::Error &e) {
|
||||
return app.exit(e);
|
||||
}
|
||||
|
||||
if(doVERSION) {
|
||||
showVersion(program.c_str(), g_gitHash);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
time_t now;
|
||||
if(date.empty())
|
||||
now = time(0);
|
||||
else {
|
||||
if(doUTC)
|
||||
setenv("TZ", "UTC", 1);
|
||||
now = parseTime(date);
|
||||
}
|
||||
|
||||
int wn, tow;
|
||||
|
||||
if(galwn >= 0) {
|
||||
time_t week=utcFromGST(galwn, 0);
|
||||
if(doProgOutput)
|
||||
cout<<influxTime(week) << endl;
|
||||
else
|
||||
cout<<humanTime(week)<< " - " << humanTime(week+7*86400) << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(doGPSWN) {
|
||||
getGPSDateFromUTC(now, wn, tow);
|
||||
cout<<wn<<endl;
|
||||
}
|
||||
else if(doGALWN) {
|
||||
getGalDateFromUTC(now, wn, tow);
|
||||
cout<<wn<<endl;
|
||||
}
|
||||
else if(doBEIDOUWN) {
|
||||
getBeiDouDateFromUTC(now, wn, tow);
|
||||
cout<<wn<<endl;
|
||||
}
|
||||
else {
|
||||
getGPSDateFromUTC(now, wn, tow);
|
||||
cout<<"GPS Week Number (non-wrapped): "<< wn << ", tow " << tow << endl;
|
||||
getGalDateFromUTC(now, wn, tow);
|
||||
cout<<"Galileo Week Number: "<< wn << ", tow " << tow << endl;
|
||||
getBeiDouDateFromUTC(now, wn, tow);
|
||||
cout<<"BeiDou Week Number: "<< wn << ", sow " << tow << endl;
|
||||
|
||||
}
|
||||
}
|
||||
catch(exception& e) {
|
||||
cerr<<"Error: "<<e.what()<<endl;
|
||||
}
|
||||
131
gps.cc
|
|
@ -1,140 +1,15 @@
|
|||
#include "gps.hh"
|
||||
|
||||
// this strips out spare bits + parity, and leaves 10 clean 24 bit words
|
||||
std::vector<uint8_t> getCondensedGPSMessage(const std::vector<uint8_t>& payload)
|
||||
std::basic_string<uint8_t> getCondensedGPSMessage(std::basic_string_view<uint8_t> payload)
|
||||
{
|
||||
uint8_t buffer[10*24/8];
|
||||
|
||||
// ingests 32 bit words, per word ignores first 2 bits, and then takes 24
|
||||
for(int w = 0 ; w < 10; ++w) {
|
||||
setbitu(buffer, 24*w, 24, getbitu(&payload[0], 2 + w*32, 24));
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>(buffer, buffer+30);
|
||||
return std::basic_string<uint8_t>(buffer, 30);
|
||||
|
||||
}
|
||||
|
||||
// expects input as 24 bit read to to use messages, returns frame number
|
||||
int GPSState::parseGPSMessage(const std::vector<uint8_t>& cond, uint8_t* pageptr)
|
||||
{
|
||||
using namespace std;
|
||||
int frame = getbitu(&cond[0], 24+19, 3);
|
||||
// 10 * 4 bytes in payload now
|
||||
tow = 1.5*(getbitu(&cond[0], 24, 17)*4);
|
||||
// cerr << "Preamble: "<<getbitu(&cond[0], 0, 8) <<", frame: "<< frame<<", truncated TOW: "<<tow<<endl;
|
||||
if(frame == 1) {
|
||||
// word 1, word 2 are TLM and HOW
|
||||
// 2 bits of padding on each word
|
||||
// word3:
|
||||
// 1-10: WN
|
||||
// 11-12: Which codes 00 = invalid, 01 = P-code on, 10 = C/A code on, 11 invalid
|
||||
// 13-16: URA, 0-15 scale
|
||||
// 17-22: 0 is ok
|
||||
// 23-24: MSB of IODC
|
||||
|
||||
// word 8:
|
||||
// 1-8: LSB of IODC
|
||||
// 9-24:
|
||||
|
||||
wn = 2048 + getbitu(&cond[0], 2*24, 10);
|
||||
ura = getbitu(&cond[0], 2*24+12, 4);
|
||||
gpshealth = getbitu(&cond[0], 2*24+16, 6);
|
||||
iodc = getbitu(&cond[0], 2*24 +22, 2) * 256;
|
||||
iodc += getbitu(&cond[0], 7*24, 8);
|
||||
|
||||
// cerr<<"GPS Week Number: "<< wn <<", URA: "<< (int)ura<<", health: "<<
|
||||
// (int)gpshealth <<endl;
|
||||
|
||||
af2 = getbits(&cond[0], 8*24, 8); // * 2^-55
|
||||
af1 = getbits(&cond[0], 8*24 + 8, 16); // * 2^-43
|
||||
af0 = getbits(&cond[0], 9*24, 22); // * 2^-31
|
||||
t0c = getbitu(&cond[0], 7*24 + 8, 16); // * 16
|
||||
// cerr<<"t0c*16: "<<t0c*16<<", af2: "<< (int)af2 <<", af1: "<< af1 <<", af0: "<<
|
||||
// af0 <<endl;
|
||||
}
|
||||
else if(frame == 2) {
|
||||
gpsiod = getbitu(&cond[0], 2*24, 8);
|
||||
t0e = getbitu(&cond[0], 9*24, 16) * 16.0; // WE SCALE THIS FOR THE USER!!
|
||||
// cerr<<"IODe "<<(int)iod<<", t0e "<< t0e << " = "<< 16* t0e <<"s"<<endl;
|
||||
|
||||
e= getbitu(&cond[0], 5*24+16, 32);
|
||||
// cerr<<"e: "<<ldexp(e, -33)<<", ";
|
||||
|
||||
|
||||
// sqrt(A), 32 bits, 2^-19
|
||||
sqrtA= getbitu(&cond[0], 7*24+ 16, 32);
|
||||
// double sqrtA=ldexp(sqrtA, -19); // 2^-19
|
||||
// cerr<<"Radius: "<<sqrtA*sqrtA<<endl;
|
||||
|
||||
crs = getbits(&cond[0], 2*24 + 8, 16); // 2^-5 meters
|
||||
deltan = getbits(&cond[0], 3*24, 16); // 2^-43 semi-circles/s
|
||||
m0 = getbits(&cond[0], 3*24+16, 32); // 2^-31 semi-circles
|
||||
|
||||
cuc = getbits(&cond[0], 5*24, 16); // 2^-29 RADIANS
|
||||
cus = getbits(&cond[0], 7*24, 16); // 2^-29 RADIANS
|
||||
}
|
||||
else if(frame == 3) {
|
||||
gpsiod = getbitu(&cond[0], 9*24, 8);
|
||||
cic = getbits(&cond[0], 2*24, 16); // 2^-29 RADIANS
|
||||
omega0 = getbits(&cond[0], 2*24 + 16, 32); // 2^-31 semi-circles
|
||||
cis = getbits(&cond[0], 4*24, 16); // 2^-29 radians
|
||||
i0 = getbits(&cond[0], 4*24 + 16, 32); // 2^-31, semicircles
|
||||
|
||||
crc = getbits(&cond[0], 6*24, 16); // 2^-5, meters
|
||||
omega = getbits(&cond[0], 6*24+16, 32); // 2^-31, semi-circles
|
||||
|
||||
omegadot = getbits(&cond[0], 8*24, 24); // 2^-43, semi-circles/s
|
||||
idot = getbits(&cond[0], 9*24+8, 14); // 2^-43, semi-cirlces/s
|
||||
}
|
||||
else if(frame == 4) { // this is a carousel frame
|
||||
int page = getbitu(&cond[0], 2*24 + 2, 6);
|
||||
if(pageptr)
|
||||
*pageptr=0;
|
||||
// cerr<<"Frame 4, page "<<page;
|
||||
if(page == 56) { // 56 is the new 18 somehow? See table 20-V of the ICD
|
||||
if(pageptr)
|
||||
*pageptr=18;
|
||||
a0 = getbits(&cond[0], 6*24 , 32); // 2^-30
|
||||
a1 = getbits(&cond[0], 5*24 , 24); // 2^-50
|
||||
|
||||
t0t = getbitu(&cond[0], 7*24 + 8, 8) * 4096; // WE SCALE THIS FOR THE USER!
|
||||
wn0t = getbitu(&cond[0], 7*24 + 16, 8);
|
||||
|
||||
dtLS = getbits(&cond[0], 8*24, 8);
|
||||
wnLSF= getbitu(&cond[0], 8*24 + 8, 8);
|
||||
dn = getbitu(&cond[0], 8*24 + 16, 8);
|
||||
dtLSF = getbits(&cond[0], 9*24, 8);
|
||||
|
||||
|
||||
// cerr<<": a0: "<<a0<<", a1: "<<a1<<", t0t: "<< t0t * (1<<12) <<", wn0t: "<< wn0t<<", rough offset: "<<ldexp(a0, -30)<<endl;
|
||||
// cerr<<"deltaTLS: "<< (int)dtLS<<", post "<< (int)dtLSF<<endl;
|
||||
return frame; // otherwise pageptr gets overwritten below
|
||||
}
|
||||
//else cerr<<endl;
|
||||
// page 18 contains UTC -> 56
|
||||
// page 25 -> 63
|
||||
// 2-10 -> 25 -> 32 ??
|
||||
}
|
||||
|
||||
if(frame == 5 || frame==4) { // this is a caroussel frame
|
||||
gpsalma.dataid = getbitu(&cond[0], 2*24, 2);
|
||||
gpsalma.sv = getbitu(&cond[0], 2*24+2, 6);
|
||||
|
||||
if(pageptr)
|
||||
*pageptr= gpsalma.sv;
|
||||
|
||||
|
||||
gpsalma.e = getbitu(&cond[0], 2*24 + 8, 16);
|
||||
gpsalma.t0a = getbitu(&cond[0], 3*24, 8);
|
||||
gpsalma.deltai = getbits(&cond[0], 3*24 +8 , 16);
|
||||
gpsalma.omegadot = getbits(&cond[0], 4*24, 16);
|
||||
gpsalma.health = getbitu(&cond[0], 4*24 +16, 8);
|
||||
gpsalma.sqrtA = getbitu(&cond[0], 5*24, 24);
|
||||
gpsalma.Omega0 = getbits(&cond[0], 6*24, 24);
|
||||
gpsalma.omega = getbits(&cond[0], 7*24, 24);
|
||||
gpsalma.M0 = getbits(&cond[0], 8*24, 24);
|
||||
gpsalma.af0 = (getbits(&cond[0], 9*24, 8) << 3) + getbits(&cond[0], 9*24 +19, 3);
|
||||
gpsalma.af1 = getbits(&cond[0], 9*24 + 8, 11);
|
||||
// cerr<<"Frame 5, SV: "<<getbitu(&cond[0], 2*32 + 2 +2, 6)<<endl;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
|
|
|||
195
gps.hh
|
|
@ -5,13 +5,9 @@
|
|||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include "ephemeris.hh"
|
||||
#include <vector>
|
||||
std::basic_string<uint8_t> getCondensedGPSMessage(std::basic_string_view<uint8_t> payload);
|
||||
|
||||
std::vector<uint8_t> getCondensedGPSMessage(const std::vector<uint8_t>& payload);
|
||||
|
||||
|
||||
struct GPSAlmanac : GPSLikeEphemeris
|
||||
struct GPSAlmanac
|
||||
{
|
||||
int dataid{-1};
|
||||
int sv;
|
||||
|
|
@ -33,7 +29,7 @@ struct GPSAlmanac : GPSLikeEphemeris
|
|||
{
|
||||
return ldexp(e, -21);
|
||||
}
|
||||
uint32_t getT0e() const
|
||||
double getT0e() const
|
||||
{
|
||||
return ldexp(t0a, 12);
|
||||
}
|
||||
|
|
@ -75,23 +71,30 @@ struct GPSAlmanac : GPSLikeEphemeris
|
|||
double getCrs() const { return 0; } // meters
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
|
||||
int getIOD() const { return 0; } // XXX ioda?
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct GPSState : GPSLikeEphemeris
|
||||
struct GPSState
|
||||
{
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 16 seconds
|
||||
uint16_t t0c;
|
||||
struct SVIOD
|
||||
{
|
||||
std::bitset<32> words;
|
||||
int gnssid;
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
|
||||
|
||||
// 2^-31 2^-43
|
||||
int32_t af0 , af1;
|
||||
// ???
|
||||
int8_t af2;
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 16 seconds
|
||||
uint16_t t0c;
|
||||
|
||||
// 2^-31 2^-43
|
||||
int32_t af0 , af1;
|
||||
// ???
|
||||
int8_t af2;
|
||||
uint32_t wn{0}, tow{0};
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
|
|
@ -117,12 +120,17 @@ struct GPSState : GPSLikeEphemeris
|
|||
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
|
||||
double getOmega() const { return ldexp(omega * M_PI, -31); } // radians
|
||||
|
||||
|
||||
};
|
||||
|
||||
GPSAlmanac gpsalma;
|
||||
|
||||
uint8_t gpshealth{0};
|
||||
uint16_t ai0{0};
|
||||
int16_t ai1{0}, ai2{0};
|
||||
|
||||
int BGDE1E5a{0}, BGDE1E5b{0};
|
||||
|
||||
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
|
||||
uint16_t wn{0}; // we put the "unrolled" week number here!
|
||||
uint32_t tow{0}; // "last seen"
|
||||
|
|
@ -134,17 +142,27 @@ struct GPSState : GPSLikeEphemeris
|
|||
uint16_t wnLSF{0};
|
||||
uint8_t dn; // leap second day number
|
||||
// 1 2^-31 2^-43 2^-55 16 second
|
||||
int iodc;
|
||||
int ura;
|
||||
int ura, af0, af1, af2, t0c; // GPS parameters that should not be here XXX
|
||||
|
||||
|
||||
int gpsiod{-1};
|
||||
|
||||
int getIOD() const
|
||||
|
||||
std::map<int, SVIOD> iods;
|
||||
SVIOD& getEph(int i) { return iods[i]; } // XXXX gps adaptor
|
||||
void checkCompleteAndClean(int iod)
|
||||
{
|
||||
return gpsiod;
|
||||
if(iods[iod].words[2] && iods[iod].words[3]) {
|
||||
if(iods.size() > 1) {
|
||||
auto tmp = iods[iod];
|
||||
iods.clear();
|
||||
iods[iod] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
int parseGPSMessage(const std::vector<uint8_t>& cond, uint8_t* pageptr=0);
|
||||
bool isComplete(int iod)
|
||||
{
|
||||
return iods[iod].words[2] && iods[iod].words[3];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
|
@ -185,3 +203,128 @@ std::pair<double, double> getGPSUTCOffset(int tow, int wn, const T& eph)
|
|||
|
||||
|
||||
|
||||
// expects input as 24 bit read to to use messages, returns frame number
|
||||
template<typename T>
|
||||
int parseGPSMessage(std::basic_string_view<uint8_t> cond, T& out, uint8_t* pageptr=0)
|
||||
{
|
||||
using namespace std;
|
||||
int frame = getbitu(&cond[0], 24+19, 3);
|
||||
// 10 * 4 bytes in payload now
|
||||
out.tow = 1.5*(getbitu(&cond[0], 24, 17)*4);
|
||||
// cerr << "Preamble: "<<getbitu(&cond[0], 0, 8) <<", frame: "<< frame<<", truncated TOW: "<<out.tow<<endl;
|
||||
if(frame == 1) {
|
||||
// word 1, word 2 are TLM and HOW
|
||||
// 2 bits of padding on each word
|
||||
// word3:
|
||||
// 1-10: WN
|
||||
// 11-12: Which codes 00 = invalid, 01 = P-code on, 10 = C/A code on, 11 invalid
|
||||
// 13-16: URA, 0-15 scale
|
||||
// 17-22: 0 is ok
|
||||
// 23-24: MSB of IODC
|
||||
|
||||
// word 8:
|
||||
// 1-8: LSB of IODC
|
||||
// 9-24:
|
||||
|
||||
out.wn = 2048 + getbitu(&cond[0], 2*24, 10);
|
||||
out.ura = getbitu(&cond[0], 2*24+12, 4);
|
||||
out.gpshealth = getbitu(&cond[0], 2*24+16, 6);
|
||||
|
||||
// cerr<<"GPS Week Number: "<< out.wn <<", URA: "<< (int)out.ura<<", health: "<<
|
||||
// (int)out.gpshealth <<endl;
|
||||
|
||||
out.af2 = getbits(&cond[0], 8*24, 8); // * 2^-55
|
||||
out.af1 = getbits(&cond[0], 8*24 + 8, 16); // * 2^-43
|
||||
out.af0 = getbits(&cond[0], 9*24, 22); // * 2^-31
|
||||
out.t0c = getbits(&cond[0], 7*24 + 8, 16); // * 16
|
||||
// cerr<<"t0c*16: "<<out.t0c*16<<", af2: "<< (int)out.af2 <<", af1: "<< out.af1 <<", af0: "<<
|
||||
// out.af0 <<endl;
|
||||
}
|
||||
else if(frame == 2) {
|
||||
out.gpsiod = getbitu(&cond[0], 2*24, 8);
|
||||
auto& eph = out.getEph(out.gpsiod);
|
||||
eph.words[2]=1;
|
||||
eph.t0e = getbitu(&cond[0], 9*24, 16) * 16.0; // WE SCALE THIS FOR THE USER!!
|
||||
// cerr<<"IODe "<<(int)iod<<", t0e "<< eph.t0e << " = "<< 16* eph.t0e <<"s"<<endl;
|
||||
|
||||
eph.e= getbitu(&cond[0], 5*24+16, 32);
|
||||
// cerr<<"e: "<<ldexp(eph.e, -33)<<", ";
|
||||
|
||||
|
||||
// sqrt(A), 32 bits, 2^-19
|
||||
eph.sqrtA= getbitu(&cond[0], 7*24+ 16, 32);
|
||||
// double sqrtA=ldexp(eph.sqrtA, -19); // 2^-19
|
||||
// cerr<<"Radius: "<<sqrtA*sqrtA<<endl;
|
||||
|
||||
eph.crs = getbits(&cond[0], 2*24 + 8, 16); // 2^-5 meters
|
||||
eph.deltan = getbits(&cond[0], 3*24, 16); // 2^-43 semi-circles/s
|
||||
eph.m0 = getbits(&cond[0], 3*24+16, 32); // 2^-31 semi-circles
|
||||
|
||||
eph.cuc = getbits(&cond[0], 5*24, 16); // 2^-29 RADIANS
|
||||
eph.cus = getbits(&cond[0], 7*24, 16); // 2^-29 RADIANS
|
||||
out.checkCompleteAndClean(out.gpsiod);
|
||||
}
|
||||
else if(frame == 3) {
|
||||
out.gpsiod = getbitu(&cond[0], 9*24, 8);
|
||||
auto& eph = out.getEph(out.gpsiod);
|
||||
eph.words[3]=1;
|
||||
eph.cic = getbits(&cond[0], 2*24, 16); // 2^-29 RADIANS
|
||||
eph.omega0 = getbits(&cond[0], 2*24 + 16, 32); // 2^-31 semi-circles
|
||||
eph.cis = getbits(&cond[0], 4*24, 16); // 2^-29 radians
|
||||
eph.i0 = getbits(&cond[0], 4*24 + 16, 32); // 2^-31, semicircles
|
||||
|
||||
eph.crc = getbits(&cond[0], 6*24, 16); // 2^-5, meters
|
||||
eph.omega = getbits(&cond[0], 6*24+16, 32); // 2^-31, semi-circles
|
||||
|
||||
eph.omegadot = getbits(&cond[0], 8*24, 24); // 2^-43, semi-circles/s
|
||||
eph.idot = getbits(&cond[0], 9*24+8, 14); // 2^-43, semi-cirlces/s
|
||||
out.checkCompleteAndClean(out.gpsiod);
|
||||
}
|
||||
else if(frame == 4) { // this is a carousel frame
|
||||
int page = getbitu(&cond[0], 2*24 + 2, 6);
|
||||
if(pageptr)
|
||||
*pageptr=0;
|
||||
// cerr<<"Frame 4, page "<<page;
|
||||
if(page == 56) { // 56 is the new 18 somehow? See table 20-V of the ICD
|
||||
if(pageptr)
|
||||
*pageptr=18;
|
||||
out.a0 = getbits(&cond[0], 6*24 , 32); // 2^-30
|
||||
out.a1 = getbits(&cond[0], 5*24 , 24); // 2^-50
|
||||
|
||||
out.t0t = getbitu(&cond[0], 7*24 + 8, 8) * 4096; // WE SCALE THIS FOR THE USER!
|
||||
out.wn0t = getbitu(&cond[0], 7*24 + 16, 8);
|
||||
out.dtLS = getbits(&cond[0], 8*24, 8);
|
||||
out.dtLSF = getbits(&cond[0], 9*24, 8);
|
||||
|
||||
// cerr<<": a0: "<<out.a0<<", a1: "<<out.a1<<", t0t: "<< out.t0t * (1<<12) <<", wn0t: "<< out.wn0t<<", rough offset: "<<ldexp(out.a0, -30)<<endl;
|
||||
// cerr<<"deltaTLS: "<< (int)out.dtLS<<", post "<< (int)out.dtLSF<<endl;
|
||||
}
|
||||
//else cerr<<endl;
|
||||
// page 18 contains UTC -> 56
|
||||
// page 25 -> 63
|
||||
// 2-10 -> 25 -> 32 ??
|
||||
}
|
||||
|
||||
if(frame == 5 || frame==4) { // this is a caroussel frame
|
||||
out.gpsalma.dataid = getbitu(&cond[0], 2*24, 2);
|
||||
out.gpsalma.sv = getbitu(&cond[0], 2*24+2, 6);
|
||||
|
||||
if(pageptr)
|
||||
*pageptr= out.gpsalma.sv;
|
||||
|
||||
|
||||
out.gpsalma.e = getbitu(&cond[0], 2*24 + 8, 16);
|
||||
out.gpsalma.t0a = getbitu(&cond[0], 3*24, 8);
|
||||
out.gpsalma.deltai = getbits(&cond[0], 3*24 +8 , 16);
|
||||
out.gpsalma.omegadot = getbits(&cond[0], 4*24, 16);
|
||||
out.gpsalma.health = getbitu(&cond[0], 4*24 +16, 8);
|
||||
out.gpsalma.sqrtA = getbitu(&cond[0], 5*24, 24);
|
||||
out.gpsalma.Omega0 = getbits(&cond[0], 6*24, 24);
|
||||
out.gpsalma.omega = getbits(&cond[0], 7*24, 24);
|
||||
out.gpsalma.M0 = getbits(&cond[0], 8*24, 24);
|
||||
out.gpsalma.af0 = (getbits(&cond[0], 9*24, 8) << 3) + getbits(&cond[0], 9*24 +19, 3);
|
||||
out.gpsalma.af1 = getbits(&cond[0], 9*24 + 8, 11);
|
||||
// cerr<<"Frame 5, SV: "<<getbitu(&cond[0], 2*32 + 2 +2, 6)<<endl;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
|
|
|||
20
gpscnav.cc
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
cerr<<"Preamble "<<getbitu(&cnav[0], 0, 8)<<" sv "<<
|
||||
getbitu(&cnav[0], 8, 6) << " type "<< type <<
|
||||
" tow " << tow;
|
||||
|
||||
if(type == 10) {
|
||||
wn = getbitu(&cnav[0], 38, 13);
|
||||
cerr<<" t0p " << 300*getbitu(&cnav[0], 54,11)<< " t0e "<< 300*getbitu(&cnav[0], 70, 11);
|
||||
}
|
||||
else if(type == 11) {
|
||||
cerr<<" t0e "<< 300* getbitu(&cnav[0], 38, 11);
|
||||
|
||||
|
||||
}
|
||||
else if(type == 32) {
|
||||
cerr<< " delta-ut1 " << 1000.0*ldexp(getbits(&cnav[0], 215, 31), -24)<< "ms rate " << 1000.0*ldexp(getbits(&cnav[0], 246, 19), -25) << " te0p " <<
|
||||
16*getbits(&cnav[0], 127, 16);
|
||||
|
||||
}
|
||||
*/
|
||||
228
gpscnav.hh
|
|
@ -1,228 +0,0 @@
|
|||
#pragma once
|
||||
#include <bitset>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "bits.hh"
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
|
||||
struct GPSCNavAlmanac
|
||||
{
|
||||
int dataid{-1};
|
||||
int sv;
|
||||
uint32_t t0a{0};
|
||||
uint32_t e{0}, sqrtA{0};
|
||||
int32_t M0, Omega0, deltai, omega, omegadot;
|
||||
int health;
|
||||
int af0, af1;
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
return 3.986005 * pow(10.0, 14.0);
|
||||
} // m^3/s^2
|
||||
// same for galileo & gps
|
||||
double getOmegaE() const { return 7.2921151467 * pow(10.0, -5.0);} // rad/s
|
||||
|
||||
|
||||
double getE() const
|
||||
{
|
||||
return ldexp(e, -21);
|
||||
}
|
||||
double getT0e() const
|
||||
{
|
||||
return ldexp(t0a, 12);
|
||||
}
|
||||
|
||||
double getI0() const
|
||||
{
|
||||
return M_PI*0.3 + ldexp(M_PI*deltai, -19);
|
||||
}
|
||||
|
||||
double getOmegadot() const
|
||||
{
|
||||
return ldexp(M_PI * omegadot, -38);
|
||||
}
|
||||
|
||||
double getSqrtA() const
|
||||
{
|
||||
return ldexp(sqrtA, -11);
|
||||
}
|
||||
|
||||
double getOmega0() const
|
||||
{
|
||||
return ldexp(M_PI * Omega0, -23);
|
||||
}
|
||||
double getOmega() const
|
||||
{
|
||||
return ldexp(M_PI * omega, -23);
|
||||
}
|
||||
|
||||
double getM0() const
|
||||
{
|
||||
return ldexp(M_PI * M0, -23);
|
||||
}
|
||||
double getIdot() const { return 0; } // radians/s
|
||||
double getCic() const { return 0; } // radians
|
||||
double getCis() const { return 0; } // radians
|
||||
double getCuc() const { return 0; } // radians
|
||||
double getCus() const { return 0; } // radians
|
||||
double getCrc() const { return 0; } // meters
|
||||
double getCrs() const { return 0; } // meters
|
||||
double getDeltan()const { return 0; } //radians/s
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct GPSCNavState
|
||||
{
|
||||
uint32_t t0e;
|
||||
uint32_t e, sqrtA;
|
||||
int32_t m0, omega0, i0, omega, idot, omegadot, deltan;
|
||||
|
||||
|
||||
int16_t cuc{0}, cus{0}, crc{0}, crs{0}, cic{0}, cis{0};
|
||||
// 16 seconds
|
||||
uint16_t t0c;
|
||||
|
||||
// 2^-31 2^-43
|
||||
int32_t af0 , af1;
|
||||
// ???
|
||||
int8_t af2;
|
||||
uint32_t wn{0}, tow{0};
|
||||
|
||||
double getMu() const
|
||||
{
|
||||
return 3.986005 * pow(10.0, 14.0);
|
||||
} // m^3/s^2
|
||||
// same for galileo & gps
|
||||
double getOmegaE() const { return 7.2921151467 * pow(10.0, -5.0);} // rad/s
|
||||
|
||||
uint32_t getT0e() const { return t0e; }
|
||||
double getSqrtA() const { return ldexp(sqrtA, -19); }
|
||||
double getE() const { return ldexp(e, -33); }
|
||||
double getCuc() const { return ldexp(cuc, -29); } // radians
|
||||
double getCus() const { return ldexp(cus, -29); } // radians
|
||||
double getCrc() const { return ldexp(crc, -5); } // meters
|
||||
double getCrs() const { return ldexp(crs, -5); } // meters
|
||||
double getM0() const { return ldexp(m0 * M_PI, -31); } // radians
|
||||
double getDeltan()const { return ldexp(deltan *M_PI, -43); } //radians/s
|
||||
double getI0() const { return ldexp(i0 * M_PI, -31); } // radians
|
||||
double getCic() const { return ldexp(cic, -29); } // radians
|
||||
double getCis() const { return ldexp(cis, -29); } // radians
|
||||
double getOmegadot() const { return ldexp(omegadot * M_PI, -43); } // radians/s
|
||||
double getOmega0() const { return ldexp(omega0 * M_PI, -31); } // radians
|
||||
double getIdot() const { return ldexp(idot * M_PI, -43); } // radians/s
|
||||
double getOmega() const { return ldexp(omega * M_PI, -31); } // radians
|
||||
|
||||
|
||||
uint8_t gpshealth{0};
|
||||
uint16_t ai0{0};
|
||||
int16_t ai1{0}, ai2{0};
|
||||
|
||||
int BGDE1E5a{0}, BGDE1E5b{0};
|
||||
|
||||
bool disturb1{false}, disturb2{false}, disturb3{false}, disturb4{false}, disturb5{false};
|
||||
//
|
||||
// 2^-30 2^-50 1 8-bit week
|
||||
int32_t a0{0}, a1{0}, t0t{0}, wn0t{0};
|
||||
int32_t a0g{0}, a1g{0}, t0g{0}, wn0g{0};
|
||||
int8_t dtLS{0}, dtLSF{0};
|
||||
uint16_t wnLSF{0};
|
||||
uint8_t dn; // leap second day number
|
||||
|
||||
|
||||
int gpsiod{-1};
|
||||
|
||||
int teop;
|
||||
int deltaUT1;
|
||||
int deltaUT1dot;
|
||||
|
||||
// milliseconds
|
||||
std::pair<double, double> getUT1OffsetMS(int tow)
|
||||
{
|
||||
int delta = ephAge(tow, 16* teop);
|
||||
std::cout<<" tow "<<tow<<" teop "<< 16*teop <<" delta "<<delta<<" " << 1000.0*ldexp(deltaUT1, -24)<<"ms ";
|
||||
double cur = ldexp(deltaUT1 + ldexp(delta * deltaUT1dot / 86400.0, -1), -24);
|
||||
double trend = ldexp(deltaUT1dot, -25);
|
||||
return {1000.0* cur, 1000.0*trend};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
std::pair<double, double> getGPSCNavAtomicOffset(int tow, const T& eph)
|
||||
{
|
||||
int delta = ephAge(tow, getT0c(eph));
|
||||
double cur = eph.af0 + ldexp(delta*eph.af1, -12) + ldexp(delta*delta*eph.af2, -24);
|
||||
double trend = ldexp(eph.af1, -12) + ldexp(2*delta*eph.af2, -24);
|
||||
|
||||
// now in units of 2^-31 seconds, which are 0.5 nanoseconds each
|
||||
double factor = ldexp(1000000000, -31);
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::pair<double, double> getGPSCNavUTCOffset(int tow, int wn, const T& eph)
|
||||
{
|
||||
// 2^-30 2^-50 3600
|
||||
// a0 a1 t0t
|
||||
|
||||
int dw = (int)(uint8_t)wn - (int)(uint8_t) eph.wn0t;
|
||||
int delta = dw*7*86400 + tow - eph.t0t; // this is pre-scaled for GPS..
|
||||
|
||||
double cur = eph.a0 + ldexp(1.0*delta*eph.a1, -20);
|
||||
double trend = ldexp(eph.a1, -20);
|
||||
|
||||
// now in units of 2^-30 seconds, which are ~1.1 nanoseconds each
|
||||
|
||||
double factor = ldexp(1000000000, -30);
|
||||
return {factor * cur, factor * trend};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template<typename T>
|
||||
int parseGPSCNavMessage(const std::vector<uint8_t>& msg, T& out)
|
||||
{
|
||||
using namespace std;
|
||||
int type = getbitu(&msg[0], 14, 6);
|
||||
out.tow = 6 * getbitu(&msg[0], 20, 17) - 12;
|
||||
|
||||
if(type == 10) {
|
||||
out.wn = getbitu(&msg[0], 38, 13);
|
||||
// out.ura = getbitu(&msg[0], 65, 5);
|
||||
out.t0e = getbitu(&msg[0], 70, 11); // XXX scale?
|
||||
|
||||
// int32_t deltaSqrtA = getbits(&msg[0], 81, 26);
|
||||
// int32_t SqrtADot = getbits(&msg[0], 107, 25);
|
||||
out.deltan = getbitu(&msg[0], 132, 17); // XXX scale??
|
||||
// int deltandot = getbitu(&msg[0], 149, 23); // XXX scale??
|
||||
// int64_t M0n = getbitu(&msg[0], 172, 33); // XXX scale??
|
||||
|
||||
out.e = getbitu(&msg[0], 205, 33); // XXX scale??
|
||||
out.omega = getbitu(&msg[0], 238, 33); // XXX scale?
|
||||
}
|
||||
else if(type == 11) {
|
||||
out.t0e = getbitu(&msg[0], 38, 11); // XXX scale?
|
||||
out.omega0 = getbitu(&msg[0], 49, 33); // XXX scale?
|
||||
out.i0 = getbitu(&msg[0], 82, 33); // XXX scale?
|
||||
// int64_t deltaOmegaDot = getbitu(&msg[0], 115, 17); // XXX scale?
|
||||
out.idot = getbitu(&msg[0], 132, 15); // XXX scale?
|
||||
out.cis = getbits(&msg[0], 147, 16);// XXX scale?
|
||||
out.cic = getbits(&msg[0], 163, 16);// XXX scale?
|
||||
out.crs = getbits(&msg[0], 179, 24);// XXX scale?
|
||||
out.crc = getbits(&msg[0], 203, 24);// XXX scale?
|
||||
out.cus = getbits(&msg[0], 227, 21);// XXX scale?
|
||||
out.cuc = getbits(&msg[0], 248, 21); // XXX scale?
|
||||
}
|
||||
else if(type == 32) {
|
||||
out.deltaUT1 = getbits(&msg[0], 215, 31);
|
||||
out.deltaUT1dot = getbits(&msg[0], 246, 19);
|
||||
out.teop = getbitu(&msg[0], 127, 16);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
|
@ -1,10 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>galmon.eu almanac</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
</head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
|
||||
text {
|
||||
font: 12px sans-serif;
|
||||
}
|
||||
|
||||
|
||||
th, td {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
font-family: monospace;
|
||||
}
|
||||
tr:nth-child(even) {background: #CCC}
|
||||
tr:nth-child(odd) {background: #FFF}
|
||||
</style>
|
||||
<body>
|
||||
Last update: <span id="freshness"></span><br/>
|
||||
Note: all distances are in kilometers!
|
||||
|
|
@ -14,13 +26,13 @@
|
|||
<span id="facts"></span>
|
||||
</p>
|
||||
<p>
|
||||
Information on the status of BeiDou can be found on <a href="http://www.csno-tarc.cn/system/constellation&ce=english">this page</a> from the Chinese Test and Assessment Research Center of China Satellite Navigation Office.
|
||||
Information on the status of BeiDou can be found on <a href="http://www.csno-tarc.cn/system/constellation&ce=english">this page</a> from the Chinese Test and Assessment Research Center of China Satellite Navigation Office.
|
||||
</p>
|
||||
<p>
|
||||
Feedback is very welcome on bert@hubertnet.nl or <a href="https://twitter.com/PowerDNS_Bert">@PowerDNS_Bert</a>.
|
||||
</p>
|
||||
<script src="d3.v4.min.js"></script>
|
||||
<script src="ext/moment-with-locales.js"></script>
|
||||
<script src="almanac.js"></script>
|
||||
<script src="d3.v4.min.js"></script>
|
||||
<script src="ext/moment-with-locales.js"></script>
|
||||
<script src="almanac.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ function maketable(str, arr)
|
|||
enter().
|
||||
append("tr");
|
||||
|
||||
var columns = ["sv", "best-tle", "best-tle-dist", "best-tle-norad", "best-tle-int-desig", "eph-ecefX", "eph-ecefY", "eph-ecefZ", "tle-ecefX", "tle-ecefY", "tle-ecefZ", "eph-latitude", "eph-longitude", "tle-latitude", "tle-longitude", "tle-eciX", "tle-eciY", "tle-eciZ", "t0e", "t", "E", "M0"];
|
||||
var columns = ["sv", "best-tle", "best-tle-dist", "best-tle-norad", "best-tle-int-desig", "eph-ecefX", "eph-ecefY", "eph-ecefZ", "tle-ecefX", "tle-ecefY", "tle-ecefZ", "eph-latitude", "eph-longitude", "tle-latitude", "tle-longitude", "tle-eciX", "tle-eciY", "tle-eciZ", "t0e", "t"];
|
||||
|
||||
// append the header row
|
||||
thead.append("tr")
|
||||
|
|
|
|||
184
html/doalles.js
|
|
@ -1,5 +1,7 @@
|
|||
var repeat;
|
||||
|
||||
moment.relativeTimeThreshold('m', 120);
|
||||
|
||||
function maketable(str, arr)
|
||||
{
|
||||
var table=d3.select(str);
|
||||
|
|
@ -12,7 +14,7 @@ function maketable(str, arr)
|
|||
enter().
|
||||
append("tr");
|
||||
|
||||
var columns = ["sv", "iod", "eph-age-m", "orbit-disco", "time-disco", "sisa", "health", "alma-dist", "osnma", "impinav", "sources", "db", "rtcm-eph-delta-cm","rtcm-clock-dclock0", "prres", "elev", "last-seen-s"];
|
||||
var columns = ["sv", "best-tle", "iod", "eph-age-m", "latest-disco", "time-disco", "sisa", "health", "tle-dist", "alma-dist", "delta-utc", "delta-gps", "sources", "db", "delta_hz_corr","prres", "elev", "last-seen-s"];
|
||||
|
||||
// append the header row
|
||||
thead.append("tr")
|
||||
|
|
@ -23,11 +25,6 @@ function maketable(str, arr)
|
|||
.html(function(column) {
|
||||
if(column == "delta_hz_corr")
|
||||
return "ΔHz";
|
||||
if(column == "rtcm-eph-delta-cm")
|
||||
return "Δrtcm";
|
||||
if(column == "rtcm-clock-dclock0")
|
||||
return "Δclk";
|
||||
|
||||
if(column == "delta-gps")
|
||||
return "ΔGPS ns";
|
||||
if(column == "delta-utc")
|
||||
|
|
@ -59,28 +56,10 @@ function maketable(str, arr)
|
|||
else if(row["gnssid"] == 6)
|
||||
img='ext/glo.png';
|
||||
|
||||
ret.value = '<img width="16" height="16" src="https://berthub.eu/tmp/'+ img +'"/>';
|
||||
ret.value = '<img width="16" height="16" src="https://ds9a.nl/tmp/'+ img +'"/>';
|
||||
// ret.value="";
|
||||
ret.value += " <a href='sv.html?gnssid="+row.gnssid+"&sv="+row.svid+"&sigid="+row.sigid+"'>"+row.sv+"</a>";
|
||||
}
|
||||
else if(column == "rtcm-eph-delta-cm") {
|
||||
if(row[column] != null)
|
||||
ret.value = row[column].toFixed(1)+" cm";
|
||||
else
|
||||
ret.value="";
|
||||
}
|
||||
else if(column == "rtcm-clock-dclock0") {
|
||||
if(row[column] != null) {
|
||||
if(Math.abs(row[column]) > 150)
|
||||
ret.color="#ff2222";
|
||||
else if(Math.abs(row[column]) > 100)
|
||||
ret.color="#ff4444";
|
||||
ret.value = row[column].toFixed(1)+" cm";
|
||||
}
|
||||
else
|
||||
ret.value="";
|
||||
}
|
||||
|
||||
else if(column == "aodc/e") {
|
||||
if(row["aodc"] != null && row["aode"] != null)
|
||||
ret.value = row["aodc"]+"/"+row["aode"];
|
||||
|
|
@ -102,44 +81,28 @@ function maketable(str, arr)
|
|||
|
||||
|
||||
if(row[column] != null) {
|
||||
ret.value = row[column].toFixed(0) + " minutes ago";
|
||||
var b = moment.duration(-row[column], 'm');
|
||||
ret.value = b.humanize(true);
|
||||
}
|
||||
else
|
||||
ret.value="";
|
||||
}
|
||||
else if(row[column] != null && (column == "delta_hz_corr" || column =="delta_hz")) {
|
||||
|
||||
ret.value = row[column];
|
||||
}
|
||||
else if((column == "tle-dist")) {
|
||||
if(row["best-tle-dist"] != null) {
|
||||
if(row["best-tle-dist"] != null)
|
||||
ret.value = row["best-tle-dist"].toFixed(1) + " km";
|
||||
|
||||
if (Math.abs(row["best-tle-dist"]) > 10000)
|
||||
ret.color="red";
|
||||
else if (Math.abs(row["best-tle-dist"]) > 1000)
|
||||
ret.color="#ffaaaa";
|
||||
}
|
||||
}
|
||||
else if((column == "alma-dist")) {
|
||||
if(row["alma-dist"] != null)
|
||||
ret.value = row["alma-dist"].toFixed(1) + " km";
|
||||
}
|
||||
|
||||
else if(column == "norad") {
|
||||
ret.value = row["best-tle-norad"];
|
||||
}
|
||||
else if(column == "osnma" && row["osnma"] != null) {
|
||||
if(row["osnma"]==true)
|
||||
ret.value="✅";
|
||||
else
|
||||
ret.value="";
|
||||
}
|
||||
else if(column == "impinav" && row["impinav"] != null) {
|
||||
if(row["impinav"]==true)
|
||||
ret.value="✅";
|
||||
else
|
||||
ret.value="";
|
||||
}
|
||||
|
||||
else if(column == "delta-utc" && row["delta-utc"] != null) {
|
||||
ret.value = row["delta-utc"]+'<span class="CellComment">a0: '+row["a0"]+'<br/>a1: '+row["a1"]+'<br/>wn0t: ' + row["wn0t"]+'<br/>t0t: '+row["t0t"]+'</span>';
|
||||
ret.Class = 'CellWithComment';
|
||||
|
|
@ -150,20 +113,11 @@ function maketable(str, arr)
|
|||
}
|
||||
|
||||
else if(column == "last-seen-s") {
|
||||
var seconds = row[column].toFixed(0);
|
||||
if(seconds < 30)
|
||||
ret.value = "a few seconds ago";
|
||||
else
|
||||
ret.value = (seconds/60) + " minutes ago";
|
||||
var b = moment.duration(row[column], 's');
|
||||
ret.value = b.humanize();
|
||||
}
|
||||
else if(column == "best-tle") {
|
||||
ret.value = "<small>"+row[column]+"</small>";
|
||||
if(row["best-tle-dist"] != null) {
|
||||
if (Math.abs(row["best-tle-dist"]) > 10000)
|
||||
ret.color="red";
|
||||
else if (Math.abs(row["best-tle-dist"]) > 1000)
|
||||
ret.color="#ffaaaa";
|
||||
}
|
||||
}
|
||||
else if(column == "gpshealth" && row[column] != null) {
|
||||
if(row[column]==0)
|
||||
|
|
@ -173,7 +127,7 @@ function maketable(str, arr)
|
|||
ret.color="red";
|
||||
}
|
||||
}
|
||||
else if(column == "orbit-disco" && row[column] != null)
|
||||
else if(column == "latest-disco" && row[column] != null)
|
||||
ret.value = ((100*row[column]).toFixed(1))+" cm";
|
||||
else if(column == "time-disco" && row[column] != null)
|
||||
ret.value = row[column].toFixed(1)+" ns";
|
||||
|
|
@ -196,7 +150,7 @@ function maketable(str, arr)
|
|||
if(column == "sisa" && myRe.test(row[column]))
|
||||
ret.color="#ff2222";
|
||||
|
||||
if(column == "sisa" && row[column]=="NO SISA AVAILABLE")
|
||||
if(column == "sisa" && row[column]=="NO SIS AVAILABLE")
|
||||
ret.color="#ff2222";
|
||||
return ret;
|
||||
})
|
||||
|
|
@ -221,68 +175,36 @@ function updateSats()
|
|||
o.db="";
|
||||
o.elev="";
|
||||
o.delta_hz_corr="";
|
||||
o.sources=0;
|
||||
let prrestot = 0, prresnum=0, dbtot=0, hztot=0, hznum=0;
|
||||
let mindb=1000, maxdb=0;
|
||||
let minelev=90, maxelev=-1;
|
||||
o.hqsources=0;
|
||||
o.prres="";
|
||||
Object.keys(o.perrecv).forEach(function(k) {
|
||||
if(o.perrecv[k]["last-seen-s"] < 30) {
|
||||
o.sources++;
|
||||
if(o.perrecv[k]["last-seen-s"] < 1800) {
|
||||
o.sources = o.sources + '<a href="observer.html?observer=' + k + '">'+k+'</a> ';
|
||||
|
||||
o.db = o.db + o.perrecv[k].db +" ";
|
||||
if(o.perrecv[k].elev != null)
|
||||
o.elev = o.elev + o.perrecv[k].elev.toFixed(0)+" ";
|
||||
else
|
||||
o.elev = o.elev + "? ";
|
||||
|
||||
if(o.delta_hz_corr == null)
|
||||
o.delta_hz_corr ="";
|
||||
if(o.perrecv[k].delta_hz_corr != null)
|
||||
o.delta_hz_corr = o.delta_hz_corr + o.perrecv[k].delta_hz_corr.toFixed(0)+" ";
|
||||
else
|
||||
o.delta_hz_corr = o.delta_hz_corr + "_ ";
|
||||
|
||||
if(o.prres == null)
|
||||
o.prres ="";
|
||||
if(o.perrecv[k].prres != null)
|
||||
o.prres = o.prres + o.perrecv[k].prres.toFixed(0)+" ";
|
||||
else
|
||||
o.prres = o.prres + "_ ";
|
||||
|
||||
|
||||
|
||||
dbtot += o.perrecv[k].db;
|
||||
if(o.perrecv[k].db != null) {
|
||||
let db=o.perrecv[k].db;
|
||||
if(db > maxdb)
|
||||
maxdb = db;
|
||||
if(db <mindb)
|
||||
mindb =db;
|
||||
}
|
||||
if(o.perrecv[k].elev != null) {
|
||||
let elev=o.perrecv[k].elev;
|
||||
if(elev > maxelev)
|
||||
maxelev = elev;
|
||||
if(elev <minelev)
|
||||
minelev =elev;
|
||||
}
|
||||
|
||||
if(o.perrecv[k].qi != null && o.perrecv[k].qi == 7 && o.perrecv[k].elev > 20
|
||||
&& (o.perrecv[k].prres != 0.000 || o.perrecv[k].used)) {
|
||||
o.hqsources++;
|
||||
|
||||
if(o.perrecv[k].delta_hz_corr != null) {
|
||||
hztot += o.perrecv[k].delta_hz_corr;
|
||||
hznum++;
|
||||
}
|
||||
|
||||
if(o.perrecv[k].prres != null) {
|
||||
prrestot+= o.perrecv[k].prres;
|
||||
prresnum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(mindb != 1000)
|
||||
o.db = mindb+" - " +maxdb;
|
||||
else
|
||||
o.db = "-";
|
||||
|
||||
if(maxelev != -1)
|
||||
o.elev = minelev.toFixed(0)+" - " +maxelev.toFixed(0);
|
||||
else
|
||||
o.elev = "-";
|
||||
|
||||
if(o.hqsources > 2) {
|
||||
if(prresnum)
|
||||
o.prres = (prrestot / prresnum).toFixed(2);
|
||||
else
|
||||
o.prres ="-";
|
||||
if(hznum)
|
||||
o.delta_hz_corr = (hztot / hznum).toFixed(2);
|
||||
else
|
||||
o.delta_hz_corr ="-";
|
||||
}
|
||||
|
||||
arr.push(o);
|
||||
});
|
||||
|
||||
|
|
@ -301,8 +223,6 @@ function updateSats()
|
|||
let wantIt = false;
|
||||
if(d3.select("#GalE1").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 1)
|
||||
wantIt = true;
|
||||
if(d3.select("#GalE5a").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 6)
|
||||
wantIt = true;
|
||||
if(d3.select("#GalE5b").property("checked") && arr[n].gnssid==2 && arr[n].sigid == 5)
|
||||
wantIt = true;
|
||||
if(d3.select("#GPSL1CA").property("checked") && arr[n].gnssid==0 && arr[n].sigid == 0)
|
||||
|
|
@ -342,29 +262,9 @@ function update()
|
|||
|
||||
|
||||
d3.json("global.json", function(d) {
|
||||
var str="Galileo-UTC offset: <b>"+d["gst-utc-offset-ns"].toFixed(2)+"</b> ns";
|
||||
str += ", Galileo-GPS offset: <b>"+d["gst-gps-offset-ns"].toFixed(2)+"</b> ns";
|
||||
str += ", GPS-UTC offset: <b>"+d["gps-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", BeiDou-UTC offset: <b>"+d["beidou-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", GLONASS-UTC offset: <b>"+d["glonass-utc-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", GLONASS-GPS offset: <b>"+d["glonass-gps-offset-ns"].toFixed(2)+" ns</b>";
|
||||
str += ", "+d["leap-seconds"]+"</b> leap seconds (GPS/Galileo)";
|
||||
|
||||
d3.select('#facts').html(str);
|
||||
var ageSeconds = (Date.now() - 1000*d["last-seen"])/1000;
|
||||
var ageString;
|
||||
if(ageSeconds < 60)
|
||||
ageString = "a few seconds ago";
|
||||
else
|
||||
ageString = (ageSeconds/60).toFixed(0) +" minutes ago";
|
||||
|
||||
d3.select("#freshness").html(ageString);
|
||||
|
||||
str = d["total-live-receivers"]+" receivers active, tracking ";
|
||||
str += d["total-live-svs"] + " satellites via " ;
|
||||
str += d["total-live-signals"] + " signals." ;
|
||||
d3.select("#allstats").text(str);
|
||||
|
||||
d3.select('#facts').html("Galileo-UTC offset: <b>"+d["utc-offset-ns"].toFixed(2)+"</b> ns, Galileo-GPS offset: <b>"+d["gps-offset-ns"].toFixed(2)+"</b> ns, GPS UTC offset: <b>"+d["gps-utc-offset-ns"].toFixed(2)+"</b>. "+d["leap-seconds"]+"</b> leap seconds");
|
||||
lastseen = moment(1000*d["last-seen"]);
|
||||
d3.select("#freshness").html(lastseen.fromNow());
|
||||
});
|
||||
|
||||
d3.json("svs.json", function(d) {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 399 B |
|
Before Width: | Height: | Size: 399 B |
|
Before Width: | Height: | Size: 940 B |
|
Before Width: | Height: | Size: 297 B |
2
html/ext/jquery.min.js
vendored
|
Before Width: | Height: | Size: 703 B |
|
Before Width: | Height: | Size: 332 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 820 B |
BIN
html/favicon.ico
|
Before Width: | Height: | Size: 198 B |
|
|
@ -1,46 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||
<head>
|
||||
<title>galmon.eu geo</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="geo.css">
|
||||
|
||||
<script src="../d3.v4.min.js"></script>
|
||||
<script src="../ext/topojson.v1.min.js"></script>
|
||||
<script src="../ext/d3-geo-projection.v2.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="galmongeo">
|
||||
<div id="galmoninfo">
|
||||
This is a live map from the <a href="/">galmon.eu</a> project. This map can plot if a fix is possible ('Coverage') or if the positioning, horizontal or vertical precision will be bad (<a href="https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation)">PDOP, HDOP, VDOP</a>). Red blocks indicate a problem even in perfect viewing conditions. Orange means a problem if there are obstructions below 10 degrees above of the horizon, yellow for less than 20 degrees ('urban canyon').
|
||||
<b>These <a href="https://github.com/ahupowerdns/galmon/blob/master/coverage.cc">calculations are very provisional</a> and might be wrong!</b>
|
||||
<div class="centered">
|
||||
<p>
|
||||
<input type="radio" name="kind" id="coverage" onclick="do_timer();" ><label for="coverage">Coverage</label>
|
||||
<input type="radio" name="kind" id="pdop" onclick="do_timer();" ><label for="pdop">PDOP > 10 or no fix</label>
|
||||
<input type="radio" name="kind" id="hdop" onclick="do_timer();" checked><label for="hdop">HDOP > 10 or no fix</label>
|
||||
<input type="radio" name="kind" id="vdop" onclick="do_timer();" ><label for="vdop">VDOP > 10 or no fix</label>
|
||||
</p>
|
||||
<hr/>
|
||||
<p>
|
||||
<input type="checkbox" id="GalE1" onclick="do_timer();" checked> <label for="GalE1">Galileo E1</label>
|
||||
<input type="checkbox" id="GPSL1CA" onclick="do_timer();"> <label for="GPSL1CA">GPS L1C/A</label>
|
||||
<input type="checkbox" id="Beidou" onclick="do_timer();"> <label for="Beidou">Beidou</label>
|
||||
<input type="checkbox" id="Glonass" onclick="do_timer();"> <label for="GloL1OF">Glonass</label>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="combined">
|
||||
<svg id="svgworld"></svg>
|
||||
<svg id="svggraticule"></svg>
|
||||
<svg id="svgobservers"></svg>
|
||||
<svg id="svgalmanac"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="coverage.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
|
@ -1,488 +0,0 @@
|
|||
'use strict';
|
||||
//
|
||||
|
||||
var wantGPS=0, wantGalileo=0, wantBeidou=0, wantGlonass=0;
|
||||
//
|
||||
//
|
||||
|
||||
var fileWorld = "world.geojson";
|
||||
var fileAlmanac = "../almanac.json" // "https://galmon.eu/almanac"
|
||||
var fileObservers = "../observers.json" // "https://galmon.eu/observers"
|
||||
|
||||
var projectionChoice = "Fahey";
|
||||
var projectionChoice = "CylindricalStereographic";
|
||||
var projectionChoice = "Equirectangular";
|
||||
//var projectionChoice = "Aitoff";
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
var svgWorld = d3.select("#svgworld");
|
||||
var idWorld = document.getElementById("svgworld");
|
||||
|
||||
var svgGraticule = d3.select("#svggraticule");
|
||||
var idGraticule = document.getElementById("svggraticule");
|
||||
|
||||
var svgObservers = d3.select("#svgobservers");
|
||||
var idObservers = document.getElementById("svgobservers");
|
||||
|
||||
var svgAlmanac = d3.select("#svgalmanac");
|
||||
var idAlmanac = document.getElementById("svgalmanac");
|
||||
|
||||
var geoPath;
|
||||
var aProjection;
|
||||
|
||||
function draw_world(data_world)
|
||||
{
|
||||
// console.log("draw_world() " + data_world.features.length);
|
||||
|
||||
svgWorld.selectAll("path")
|
||||
.data(data_world.features)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", "countries")
|
||||
.attr("d", geoPath);
|
||||
}
|
||||
|
||||
function draw_graticule()
|
||||
{
|
||||
// Graticule
|
||||
var graticule = d3.geoGraticule();
|
||||
|
||||
// console.log("draw_graticule()");
|
||||
|
||||
svgGraticule.selectAll("path")
|
||||
.data(graticule.lines())
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", "graticule line")
|
||||
.attr("id", function(d) {
|
||||
var c = d.coordinates;
|
||||
if (c[0][0] == c[1][0]) {
|
||||
return (c[0][0] < 0) ? -c[0][0] + "W" : +c[0][0] + "E";
|
||||
} else if (c[0][1] == c[1][1]) {
|
||||
return (c[0][1] < 0) ? -c[0][1] + "S" : c[0][1] + "N";
|
||||
}
|
||||
})
|
||||
.attr("d", geoPath);
|
||||
|
||||
svgGraticule.selectAll('text')
|
||||
.data(graticule.lines())
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(function(d) {
|
||||
var c = d.coordinates;
|
||||
if ((c[0][0] == c[1][0]) && (c[0][0] % 30 == 0)) {
|
||||
return (c[0][0]);
|
||||
} else if (c[0][1] == c[1][1]) {
|
||||
return (c[0][1]);
|
||||
}
|
||||
})
|
||||
.attr("class","label")
|
||||
.attr("style", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? "text-anchor: end" : "text-anchor: middle";
|
||||
})
|
||||
.attr("dx", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? -10 : 0;
|
||||
})
|
||||
.attr("dy", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? 4 : 10;
|
||||
})
|
||||
.attr('transform', function(d) {
|
||||
var c = d.coordinates;
|
||||
return ('translate(' + aProjection(c[0]) + ')')
|
||||
});
|
||||
|
||||
svgGraticule.append("path")
|
||||
.datum(graticule.outline)
|
||||
.attr("class", "graticule outline")
|
||||
.attr("d", geoPath);
|
||||
}
|
||||
|
||||
function get_almanac_valid(data_almanac)
|
||||
{
|
||||
var a=[];
|
||||
|
||||
|
||||
Object.keys(data_almanac).forEach(function(e) {
|
||||
var o = data_almanac[e];
|
||||
o.sv = e;
|
||||
if (o["eph-latitude"] != null && ((wantGalileo && o["gnssid"]==2) || (wantGPS && o["gnssid"]==0) || (wantBeidou && o["gnssid"]==3) ) ) {
|
||||
a.push(o);
|
||||
}
|
||||
});
|
||||
return a;
|
||||
}
|
||||
|
||||
function draw_coverage(data_coverage)
|
||||
{
|
||||
console.log(data_coverage.length);
|
||||
let coverage=[];
|
||||
|
||||
let mode = d3.select('input[name="kind"]:checked').node().id;
|
||||
console.log(mode);
|
||||
|
||||
var offset=4;
|
||||
if(mode=="pdop")
|
||||
offset=4;
|
||||
else if(mode=="hdop")
|
||||
offset=7;
|
||||
else if(mode=="vdop")
|
||||
offset=10;
|
||||
// console.log("Offset is "+offset+ " for mode "+mode);
|
||||
|
||||
|
||||
for(var i =0 ; i < data_coverage.length; ++i) {
|
||||
for(var j = 0 ; j < data_coverage[i][1].length; ++j) {
|
||||
if(mode == "coverage") {
|
||||
let numsats = data_coverage[i][1][j][1];
|
||||
if(numsats < 4)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], numsats: numsats, color: "red", opacity: 0.4});
|
||||
else {
|
||||
numsats = data_coverage[i][1][j][2];
|
||||
if(numsats < 4)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], numsats: numsats, color: "orange", opacity: null});
|
||||
else {
|
||||
numsats = data_coverage[i][1][j][3];
|
||||
if(numsats < 4)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], numsats: numsats, color: "yellow", opacity: 0.12});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(mode=="pdop" || mode=="hdop" || mode =="vdop") {
|
||||
let dop = data_coverage[i][1][j][offset];
|
||||
if(dop > 100 || dop < 0)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], dop: dop, color: "red", opacity: 0.4});
|
||||
else {
|
||||
dop = data_coverage[i][1][j][offset+1];
|
||||
if(dop > 100 || dop < 0)
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], dop: dop, color: "orange", opacity: null});
|
||||
else {
|
||||
dop = data_coverage[i][1][j][offset+2];
|
||||
if(dop > 100 || dop < 0 )
|
||||
coverage.push({latitude: data_coverage[i][0], longitude: data_coverage[i][1][j][0], dop: dop, color: "yellow", opacity: 0.12});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
svgAlmanac.selectAll("rect")
|
||||
.data(coverage)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("class", "sats")
|
||||
.style("opacity", d => d["opacity"])
|
||||
.attr("width", 20)
|
||||
.attr("height", 20)
|
||||
.attr("x", d => aProjection([d["longitude"],d["latitude"]])[0])
|
||||
.attr("y", d => aProjection([d["longitude"],d["latitude"]])[1])
|
||||
.attr("fill", function(d) {
|
||||
return d["color"];
|
||||
});
|
||||
|
||||
|
||||
// console.log(coverage);
|
||||
}
|
||||
|
||||
function draw_almanac(data_almanac)
|
||||
{
|
||||
var arr = get_almanac_valid(data_almanac);
|
||||
|
||||
console.log("draw_almanac() " + arr.length);
|
||||
|
||||
svgAlmanac.selectAll("circle")
|
||||
.data(arr)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("class", "sats")
|
||||
.attr("r", 3)
|
||||
.attr("cx", d => aProjection([d["eph-longitude"],d["eph-latitude"]])[0])
|
||||
.attr("cy", d => aProjection([d["eph-longitude"],d["eph-latitude"]])[1])
|
||||
.attr("fill", function(d) {
|
||||
switch (d.gnssid) {
|
||||
case 0: return "green"; // GPS
|
||||
case 1: return "gray"; // SBAS - not coded
|
||||
case 2: return "blue"; // Galileo
|
||||
case 3: return "red"; // BeiDou
|
||||
case 4: return "gray"; // IMES - not coded
|
||||
case 5: return "gray"; // QZSS - not coded
|
||||
case 6: return "yellow"; // GLONASS
|
||||
default: return "magenta"; // - should not happen
|
||||
}
|
||||
});
|
||||
|
||||
svgAlmanac.selectAll("text")
|
||||
.data(arr)
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("class", "labels")
|
||||
.text(d => d.sv)
|
||||
.attr("dx", d => 5+aProjection([d["eph-longitude"],d["eph-latitude"]])[0])
|
||||
.attr("dy", d => 5+aProjection([d["eph-longitude"],d["eph-latitude"]])[1])
|
||||
.attr("fill", function(d) {
|
||||
if (d.sisa != null && d.sisa == 255)
|
||||
return "red";
|
||||
if (d.health != null && d.health != 0) // GPS
|
||||
return "red";
|
||||
if (d.e1bhs != null && d.e1bhs != 0) // Galileo
|
||||
return "red";
|
||||
|
||||
if (d.observed==true)
|
||||
return "black";
|
||||
|
||||
return "#666666";
|
||||
})
|
||||
.attr("font-weight", function(d) {
|
||||
if (d.observed==true)
|
||||
return "bold";
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
function draw_observers(data_observers)
|
||||
{
|
||||
console.log("draw_observers() " + data_observers.length);
|
||||
|
||||
svgObservers.selectAll("rect")
|
||||
.data(data_observers)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("class", "sats")
|
||||
.attr("width", 8)
|
||||
.attr("height", 8)
|
||||
.attr("x", d => aProjection([d["longitude"],d["latitude"]])[0]-4)
|
||||
.attr("y", d => aProjection([d["longitude"],d["latitude"]])[1]-4)
|
||||
.attr("fill", function(d) { return "black"; });
|
||||
|
||||
}
|
||||
|
||||
function draw_observers_coverage(data_observers)
|
||||
{
|
||||
var radius = 55; // XXX fix
|
||||
var geoCircle = d3.geoCircle();
|
||||
svgObservers.selectAll("path")
|
||||
.data(data_observers)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", "coverage")
|
||||
.attr("d", function(r) {
|
||||
// console.log([r["longitude"], r["latitude"]] + " = " + aProjection([r["longitude"], r["latitude"]]));
|
||||
return geoPath(geoCircle.center([r["longitude"],r["latitude"]]).radius(radius)());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var display_observers_count = 0;
|
||||
|
||||
function do_update_almanac(error, results)
|
||||
{
|
||||
var data_almanac = results[0];
|
||||
var data_observers = results[1];
|
||||
var data_coverage = results[2];
|
||||
// console.log("do_update_almanac() " + Object.keys(data_almanac).length + " " + data_observers.length);
|
||||
|
||||
if (display_observers_count == 0) {
|
||||
// does not need that much updating!
|
||||
svgObservers.html("");
|
||||
// draw_observers(data_observers);
|
||||
// draw_observers_coverage(data_observers)
|
||||
display_observers_count = 10;
|
||||
}
|
||||
display_observers_count--;
|
||||
|
||||
// We write into the svgalmanac area - so clean it and rewrite it
|
||||
svgAlmanac.html("");
|
||||
draw_almanac(data_almanac);
|
||||
|
||||
draw_coverage(data_coverage);
|
||||
}
|
||||
|
||||
var repeat;
|
||||
|
||||
function do_timer()
|
||||
{
|
||||
var seconds = 60;
|
||||
clearTimeout(repeat);
|
||||
repeat = setTimeout(do_timer, 1000.0*seconds);
|
||||
|
||||
wantGPS = 0;
|
||||
wantGalileo = 0;
|
||||
wantBeidou = 0;
|
||||
wantGlonass = 0;
|
||||
if(d3.select("#GPSL1CA").property("checked"))
|
||||
wantGPS=1;
|
||||
if(d3.select("#GalE1").property("checked"))
|
||||
wantGalileo=1;
|
||||
if(d3.select("#Beidou").property("checked"))
|
||||
wantBeidou=1;
|
||||
if(d3.select("#Glonass").property("checked"))
|
||||
wantGlonass=1;
|
||||
|
||||
var galcovurl="../cov.json?gps="+wantGPS+"&galileo="+wantGalileo+"&beidou="+wantBeidou+"&glonass="+wantGlonass;
|
||||
|
||||
d3.queue(1)
|
||||
.defer(d3.json, fileAlmanac)
|
||||
.defer(d3.json, fileObservers)
|
||||
.defer(d3.json, galcovurl)
|
||||
.awaitAll(do_update_almanac);
|
||||
}
|
||||
|
||||
function set_projection(data_world)
|
||||
{
|
||||
// var aProjection = d3.geoMercator().scale(100).translate([250,250]);
|
||||
// all this complexity is so we can scale to full screen.
|
||||
// see: https://stackoverflow.com/questions/14492284/center-a-map-in-d3-given-a-geojson-object
|
||||
|
||||
var center = [0,0]; // This is very Euro-centric - but that's how these projections works.
|
||||
var scale = 210; // No idea what this does
|
||||
|
||||
var idCombined = document.getElementById("combined");
|
||||
|
||||
svgWorld = d3.select("#svgworld");
|
||||
idWorld = document.getElementById("svgworld");
|
||||
|
||||
svgGraticule = d3.select("#svggraticule");
|
||||
idGraticule = document.getElementById("svggraticule");
|
||||
|
||||
switch(projectionChoice) {
|
||||
default:
|
||||
console.log(projectionChoice + ": not coded");
|
||||
// fall thru to Equirectangular
|
||||
case 'Equirectangular':
|
||||
aProjection = d3.geoEquirectangular()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
case 'Aitoff':
|
||||
aProjection = d3.geoAitoff()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
case 'CylindricalStereographic':
|
||||
aProjection = d3.geoCylindricalStereographic()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
case 'Fahey':
|
||||
aProjection = d3.geoFahey()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
case 'Gilbert':
|
||||
aProjection = d3.geoGilbert()
|
||||
.scale(scale)
|
||||
.translate([idCombined.clientWidth/2,idCombined.clientHeight/2]);
|
||||
break;
|
||||
}
|
||||
|
||||
geoPath = d3.geoPath()
|
||||
.projection(aProjection);
|
||||
|
||||
// using the path determine the bounds of the current map and use
|
||||
// these to determine better values for the scale and translation
|
||||
var bounds = geoPath.bounds(data_world);;
|
||||
var hscale = scale*idCombined.clientWidth / (bounds[1][0] - bounds[0][0]);
|
||||
var vscale = scale*idCombined.clientHeight / (bounds[1][1] - bounds[0][1]);
|
||||
scale = (hscale < vscale) ? hscale : vscale;
|
||||
var offset = [
|
||||
idCombined.clientWidth - (bounds[0][0] + bounds[1][0])/2,
|
||||
idCombined.clientHeight - (bounds[0][1] + bounds[1][1])/2
|
||||
];
|
||||
|
||||
if (0) {
|
||||
|
||||
// new projection
|
||||
switch(projectionChoice) {
|
||||
default:
|
||||
console.log(projectionChoice + ": not coded");
|
||||
// fall thru to Equirectangular
|
||||
case 'Equirectangular':
|
||||
aProjection = d3.geoEquirectangular()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Aitoff':
|
||||
aProjection = d3.geoAitoff()
|
||||
.center(center) .scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'CylindricalStereographic':
|
||||
aProjection = d3.geoCylindricalStereographic()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Fahey':
|
||||
aProjection = d3.geoFahey()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Gilbert':
|
||||
aProjection = d3.geoGilbert()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
console.log("do_draw_world() " + "width=" + idCombined.clientWidth + "," + "height=" + idCombined.clientHeight);
|
||||
|
||||
svgWorld.attr("width", idCombined.clientWidth);
|
||||
svgWorld.attr("height", idCombined.clientHeight);
|
||||
|
||||
svgObservers.attr("width", idCombined.clientWidth);
|
||||
svgObservers.attr("height", idCombined.clientHeight);
|
||||
|
||||
svgGraticule.attr("height", idCombined.clientHeight);
|
||||
svgGraticule.attr("width", idCombined.clientWidth);
|
||||
|
||||
svgAlmanac.attr("height", idCombined.clientHeight);
|
||||
svgAlmanac.attr("width", idCombined.clientWidth);
|
||||
|
||||
}
|
||||
|
||||
function do_draw_world(data_world)
|
||||
{
|
||||
// console.log("do_draw_world()");
|
||||
|
||||
set_projection(data_world);
|
||||
|
||||
svgWorld.html("");
|
||||
draw_world(data_world);
|
||||
|
||||
svgGraticule.html("");
|
||||
draw_graticule();
|
||||
}
|
||||
|
||||
function read_world()
|
||||
{
|
||||
// console.log("read_world()");
|
||||
d3.json(fileWorld, function(result) {
|
||||
var data_world = result;
|
||||
do_draw_world(data_world);
|
||||
// after the world is read in and displayed - then start the timers!
|
||||
// we don't redraw the world!
|
||||
do_timer();
|
||||
});
|
||||
}
|
||||
|
||||
function geo_start()
|
||||
{
|
||||
// console.log("geo_start()");
|
||||
read_world();
|
||||
}
|
||||
|
||||
geo_start();
|
||||
|
||||
// d3.select("body").onresize = do_timer;
|
||||
140
html/geo/geo.css
|
|
@ -7,12 +7,6 @@ body {
|
|||
font-family: verdana, helvetica, arial, sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 5px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#galmongeo {
|
||||
border: blue 1px solid;
|
||||
margin: 5px;
|
||||
|
|
@ -25,20 +19,7 @@ h1 {
|
|||
|
||||
#galmoninfo {
|
||||
border-top: blue 1px solid;
|
||||
margin-bottom: 0px;
|
||||
display: block;
|
||||
}
|
||||
#galmontext {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
float: left;
|
||||
font-size: 20px;
|
||||
}
|
||||
#galmonchoice {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
float: right;
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#combined {
|
||||
|
|
@ -78,32 +59,15 @@ h1 {
|
|||
// border: blue 1px solid;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute; bottom: 0; right: 0;
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
}
|
||||
#rotation {
|
||||
position: absolute; bottom: 0; left: 0;
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
svg {
|
||||
//padding: 20px;
|
||||
}
|
||||
|
||||
// #Coverage_95: { fill: #800; fill-opacity: 0.2; }
|
||||
// #Coverage_95: { fill: #800; fill-opacity: 0.2; }
|
||||
// #Coverage_95: { fill: #800; fill-opacity: 0.2; }
|
||||
// #Coverage_98: { fill: #800; fill-opacity: 0.2; }
|
||||
// #Coverage_99: { fill: #800; fill-opacity: 0.2; }
|
||||
|
||||
path.countries {
|
||||
background: yellow;
|
||||
stroke-width: 1;
|
||||
stroke: #75739F;
|
||||
fill: #7ECFE6;
|
||||
fill: #5EAFC6;
|
||||
}
|
||||
path.coverage {
|
||||
stroke-width: 1;
|
||||
|
|
@ -124,29 +88,29 @@ path.observers {
|
|||
}
|
||||
path.observers.down {
|
||||
stroke-width: 1;
|
||||
stroke: red;
|
||||
stroke: black;
|
||||
fill: red;
|
||||
fill-opacity: .75;
|
||||
}
|
||||
text.observers {
|
||||
font-family: courier new, courier;
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
text.observers.down {
|
||||
color: red;
|
||||
rect.observers {
|
||||
stroke-width: 1;
|
||||
stroke: black;
|
||||
fill: black;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
circle.satellites {
|
||||
stroke-width: 1;
|
||||
stroke: #4F442B;
|
||||
//fill: #FCBC34;
|
||||
}
|
||||
.radials {
|
||||
stroke-width: 1;
|
||||
stroke: red;
|
||||
fill: none;
|
||||
}
|
||||
text.labels {
|
||||
// stroke-width: 1;
|
||||
// stroke: #4F442B;
|
||||
// fill: #FCBC34;
|
||||
font-family: courier new, courier;
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
@ -190,83 +154,3 @@ path.merged {
|
|||
border-radius: 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.mybutton {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding-left: 35px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
background-color: #eee;
|
||||
}
|
||||
.mybutton:hover input ~ .checkmark {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.mybutton input {
|
||||
display: none;
|
||||
}
|
||||
.mybutton input:checked ~ .checkmark {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
.mybutton input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
.checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
.mybutton .checkmark:after {
|
||||
left: 9px;
|
||||
top: 5px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.rotatetable {
|
||||
display: table;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0px;
|
||||
}
|
||||
.rotatetable td {
|
||||
vertical-align: middle;
|
||||
padding: 0px 3px 0px 3px;
|
||||
margin: 0px;
|
||||
}
|
||||
.myrotate {
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
#controls {
|
||||
margin: 0px 5px 5px 0px;
|
||||
}
|
||||
#rotation {
|
||||
margin: 0px 0px 5px 5px;
|
||||
border: 1px gray solid;
|
||||
background: #eee;
|
||||
}
|
||||
.centered {
|
||||
margin: auto 0px;
|
||||
text-align: center;
|
||||
}
|
||||
755
html/geo/geo.js
|
|
@ -3,25 +3,15 @@
|
|||
//
|
||||
|
||||
var fileWorld = 'world.geojson';
|
||||
var fileAlmanac = '../almanac.json'
|
||||
var fileObservers = '../observers.json'
|
||||
var fileAlmanac = '../almanac.json' // 'https://galmon.eu/almanac'
|
||||
var fileObservers = '../observers.json' // 'https://galmon.eu/observers'
|
||||
|
||||
var projectionChoices = [
|
||||
'Fahey', // The prefered one
|
||||
'Aitoff', // Interesting; but like Fahey
|
||||
// 'Orthographic', // 3d globe - not perfectly coded for now XXX
|
||||
'CylindricalStereographic', // Beyond square
|
||||
'Equirectangular', // The boring one
|
||||
]
|
||||
|
||||
var projectionChoice = 0;
|
||||
|
||||
var constellation_state = {G: true, E: true, C: true, I: false, J: false, R: true};
|
||||
var coverage_map_state = true;
|
||||
var observer_map_state = true;
|
||||
var display_all_state = false;
|
||||
var globe_rotate = {lambda: 0.0, phi: 0.0};
|
||||
var globe_rotate_center = {lambda: 0.0, phi: 0.0};
|
||||
var projectionChoice = 'Gilbert';
|
||||
var projectionChoice = 'CylindricalStereographic';
|
||||
var projectionChoice = 'Aitoff';
|
||||
var projectionChoice = 'Orthographic';
|
||||
var projectionChoice = 'Fahey';
|
||||
var projectionChoice = 'Equirectangular';
|
||||
|
||||
//
|
||||
//
|
||||
|
|
@ -82,46 +72,41 @@ function draw_graticule()
|
|||
}
|
||||
})
|
||||
.attr("d", geoPath);
|
||||
var draw_text = false;
|
||||
if (draw_text) {
|
||||
svgGraticule.selectAll('text')
|
||||
.data(graticule.lines())
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(function(d) {
|
||||
var c = d.coordinates;
|
||||
if ((c[0][0] == c[1][0]) && (c[0][0] % 30 == 0)) {
|
||||
return (c[0][0]);
|
||||
} else if (c[0][1] == c[1][1]) {
|
||||
return (c[0][1]);
|
||||
}
|
||||
})
|
||||
.attr("class", "label")
|
||||
.attr("style", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? "text-anchor: end" : "text-anchor: middle";
|
||||
})
|
||||
.attr("dx", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? -10 : 0;
|
||||
})
|
||||
.attr("dy", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? 4 : 10;
|
||||
})
|
||||
.attr('transform', function(d) {
|
||||
var c = d.coordinates;
|
||||
return ('translate(' + aProjection(c[0]) + ')')
|
||||
});
|
||||
}
|
||||
|
||||
var draw_outline = true;
|
||||
if (draw_outline) {
|
||||
svgGraticule.append("path")
|
||||
.datum(graticule.outline)
|
||||
.attr("class", "graticule outline")
|
||||
.attr("d", geoPath);
|
||||
}
|
||||
svgGraticule.selectAll('text')
|
||||
.data(graticule.lines())
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(function(d) {
|
||||
var c = d.coordinates;
|
||||
if ((c[0][0] == c[1][0]) && (c[0][0] % 30 == 0)) {
|
||||
return (c[0][0]);
|
||||
} else if (c[0][1] == c[1][1]) {
|
||||
return (c[0][1]);
|
||||
}
|
||||
})
|
||||
.attr("class", "label")
|
||||
.attr("style", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? "text-anchor: end" : "text-anchor: middle";
|
||||
})
|
||||
.attr("dx", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? -10 : 0;
|
||||
})
|
||||
.attr("dy", function(d) {
|
||||
var c = d.coordinates;
|
||||
return (c[0][1] == c[1][1]) ? 4 : 10;
|
||||
})
|
||||
.attr('transform', function(d) {
|
||||
var c = d.coordinates;
|
||||
return ('translate(' + aProjection(c[0]) + ')')
|
||||
});
|
||||
|
||||
svgGraticule.append("path")
|
||||
.datum(graticule.outline)
|
||||
.attr("class", "graticule outline")
|
||||
.attr("d", geoPath);
|
||||
}
|
||||
|
||||
function get_almanac_valid(data_almanac)
|
||||
|
|
@ -143,9 +128,6 @@ var Tooltip;
|
|||
|
||||
function create_tooltop()
|
||||
{
|
||||
if (Tooltip) {
|
||||
return;
|
||||
}
|
||||
// create a tooltip
|
||||
Tooltip = d3.select("#combined")
|
||||
.append("span")
|
||||
|
|
@ -159,75 +141,57 @@ function to2(num)
|
|||
return parseFloat(num).toFixed(2);
|
||||
}
|
||||
|
||||
function draw_radials(satellite, observer)
|
||||
{
|
||||
// in order to draw the line between observer and satellite we need long/lat vs x/y
|
||||
var observer_center = aProjection.invert(geoPath.centroid(observer)); // find the center of the projected rectangle
|
||||
var satellite_center = aProjection.invert([satellite.attr("cx"), satellite.attr("cy")]); // center of satellite
|
||||
svgObservers.append("path")
|
||||
.attr("class", "radials")
|
||||
.attr("d", function(r) { return geoPath({type: "LineString", coordinates: [observer_center, satellite_center]}); });
|
||||
}
|
||||
|
||||
function draw_satellite_to_operator(d)
|
||||
{
|
||||
// get list of observers for this satellite
|
||||
satellite = d3.select("#Satellite_" + d.name + "_bird");
|
||||
satellite = d3.select("#" + d.name);
|
||||
a = observers_list_almanac_raw(d);
|
||||
for (aa=0;aa<a.length;aa++) {
|
||||
observer_id = a[aa];
|
||||
observer = observer_shape[observer_id];
|
||||
if (!observer) {
|
||||
// should not happen
|
||||
continue;
|
||||
observer = d3.select("#Observer_" + a[aa]);
|
||||
|
||||
if (1) {
|
||||
svgAlmanac.append("line")
|
||||
.attr("class", "radials")
|
||||
.attr("x1", Math.round(satellite.attr("cx")))
|
||||
.attr("y1", Math.round(satellite.attr("cy")))
|
||||
.attr("x2", Math.round(observer.attr("x")) + 4)
|
||||
.attr("y2", Math.round(observer.attr("y")) + 4);
|
||||
|
||||
} else {
|
||||
svgAlmanac.append("line")
|
||||
.attr("class", "radials")
|
||||
.attr("d", function(d) {
|
||||
return {
|
||||
type: "LineString",
|
||||
coordinates: [
|
||||
[Math.round(satellite.attr("cx")), Math.round(satellite.attr("cy"))],
|
||||
[Math.round(observer.attr("x")) + 4, Math.round(observer.attr("y")) + 4]
|
||||
]
|
||||
};
|
||||
});
|
||||
}
|
||||
draw_radials(satellite, observer);
|
||||
}
|
||||
}
|
||||
|
||||
function draw_operator_to_satellite(d)
|
||||
{
|
||||
observer_id = d.id;
|
||||
// get list of satellites for this observer
|
||||
observer = observer_shape[observer_id];
|
||||
if (!observer) {
|
||||
// should not happen
|
||||
return;
|
||||
}
|
||||
|
||||
observer = d3.select("#Observer_" + d.id);
|
||||
a = svs_list_observer_raw(d);
|
||||
for (aa=0;aa<a.length;aa++) {
|
||||
satellite_id = a[aa];
|
||||
// check the satellite is seen in almanac data - ie. double check.
|
||||
if (!data_almanac[satellite_id] || !data_almanac[satellite_id].observed) {
|
||||
// no line needed
|
||||
continue;
|
||||
if (data_almanac[a[aa]] && data_almanac[a[aa]].observed) {
|
||||
satellite = d3.select("#" + a[aa]);
|
||||
svgAlmanac.append("line")
|
||||
.attr("class", "radials")
|
||||
.attr("x1", Math.round(satellite.attr("cx")))
|
||||
.attr("y1", Math.round(satellite.attr("cy")))
|
||||
.attr("x2", Math.round(observer.attr("x")) + 4)
|
||||
.attr("y2", Math.round(observer.attr("y")) + 4);
|
||||
}
|
||||
var s = $("[id^='Satellite_" + satellite_id + "_']");
|
||||
if (!s.is(":visible")) {
|
||||
continue;
|
||||
}
|
||||
satellite = d3.select("#Satellite_" + satellite_id + "_bird");
|
||||
draw_radials(satellite, observer);
|
||||
}
|
||||
}
|
||||
|
||||
function color_of(r)
|
||||
{
|
||||
switch (r.gnssid) {
|
||||
case 0: return "green"; // GPS
|
||||
case 1: return "gray"; // SBAS - not coded
|
||||
case 2: return "blue"; // Galileo
|
||||
case 3: return "red"; // BeiDou
|
||||
case 4: return "gray"; // IMES - not coded
|
||||
case 5: return "gray"; // QZSS - not coded
|
||||
case 6: return "yellow"; // GLONASS
|
||||
}
|
||||
return "magenta"; // - should not happen
|
||||
}
|
||||
|
||||
var tooltop_deltax = 10; // was 30
|
||||
|
||||
function draw_almanac(data_almanac)
|
||||
{
|
||||
var arr = get_almanac_valid(data_almanac);
|
||||
|
|
@ -240,23 +204,21 @@ function draw_almanac(data_almanac)
|
|||
s = d.name + " [" + to2(d.eph_longitude) + "," + to2(d.eph_latitude) + "] " + ((o == "") ? "" : " seen by " + o);
|
||||
Tooltip.html(s)
|
||||
.style("opacity", 1)
|
||||
.style("left", (d3.mouse(this)[0] + tooltop_deltax) + "px")
|
||||
.style("left", (d3.mouse(this)[0]+30) + "px")
|
||||
.style("top", (d3.mouse(this)[1]) + "px");
|
||||
d3.selectAll(".radials").remove();
|
||||
draw_satellite_to_operator(d);
|
||||
}
|
||||
var mousemove = function(d) {
|
||||
var o = observers_list_almanac(d);
|
||||
s = d.name + " [" + to2(d.eph_longitude) + "," + to2(d.eph_latitude) + "] " + ((o == "") ? "" : " seen by " + o);
|
||||
Tooltip.html(s)
|
||||
.style("left", (d3.mouse(this)[0] + tooltop_deltax) + "px")
|
||||
.style("left", (d3.mouse(this)[0]+30) + "px")
|
||||
.style("top", (d3.mouse(this)[1]) + "px");
|
||||
}
|
||||
var mouseleave = function(d) {
|
||||
Tooltip.html("")
|
||||
.style("opacity", 0);
|
||||
d3.selectAll(".radials").remove();
|
||||
display_all_refresh();
|
||||
}
|
||||
|
||||
// text first as we want the satellite circles to be always above them!
|
||||
|
|
@ -264,30 +226,38 @@ function draw_almanac(data_almanac)
|
|||
.data(arr)
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(r => r.sv)
|
||||
.attr("class", "labels")
|
||||
.attr("id", r => "Satellite_" + r.name + "_label")
|
||||
.attr("dx", r => (aProjection([r.eph_longitude, r.eph_latitude])[0] + 0))
|
||||
.attr("dy", r => (aProjection([r.eph_longitude, r.eph_latitude])[1] + 0))
|
||||
.attr("fill", r => ((r.observed) ? "black" : "#666666"))
|
||||
.attr("text-anchor", r => ((r.eph_longitude > 0) ? "start" : "end"))
|
||||
.attr("baseline-shift", r => ((r.eph_latitude > 0) ? "+30%" : "-90%"))
|
||||
.attr("fill-opacity", r => ((r.observed) ? "1.0" : ".5"))
|
||||
.attr("stroke-opacity", r => ((r.observed) ? "1.0" : ".5"))
|
||||
.attr("font-weight", r => ((r.observed) ? "bold" : null));
|
||||
.text(r => r.sv)
|
||||
.attr("dx", r => (aProjection([r.eph_longitude, r.eph_latitude])[0] + 5))
|
||||
.attr("dy", r => (aProjection([r.eph_longitude, r.eph_latitude])[1] + 5))
|
||||
.attr("fill", r => ((r.observed==true) ? "black" : "#666666"))
|
||||
.attr("fill-opacity", r => ((r.observed==true) ? "1.0" : ".5"))
|
||||
.attr("stroke-opacity", r => ((r.observed==true) ? "1.0" : ".5"))
|
||||
.attr("font-weight", r => ((r.observed==true) ? "bold" : null));
|
||||
|
||||
svgAlmanac.selectAll("circle")
|
||||
.data(arr)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("class", "satellites")
|
||||
.attr("id", r => "Satellite_" + r.name + "_bird")
|
||||
.attr("id", r => r["name"])
|
||||
.attr("r", 3)
|
||||
.attr("cx", r => aProjection([r.eph_longitude, r.eph_latitude])[0])
|
||||
.attr("cy", r => aProjection([r.eph_longitude, r.eph_latitude])[1])
|
||||
.attr("fill", r => color_of(r))
|
||||
.attr("fill-opacity", r => ((r.observed) ? "1.0" : ".5"))
|
||||
.attr("stroke-opacity", r => ((r.observed) ? "1.0" : ".5"))
|
||||
.attr("fill", function(r) {
|
||||
switch (r.gnssid) {
|
||||
case 0: return "green"; // GPS
|
||||
case 1: return "gray"; // SBAS - not coded
|
||||
case 2: return "blue"; // Galileo
|
||||
case 3: return "red"; // BeiDou
|
||||
case 4: return "gray"; // IMES - not coded
|
||||
case 5: return "gray"; // QZSS - not coded
|
||||
case 6: return "yellow"; // GLONASS
|
||||
default: return "magenta"; // - should not happen
|
||||
}
|
||||
})
|
||||
.attr("fill-opacity", r => ((r.observed==true) ? "1.0" : ".5"))
|
||||
.attr("stroke-opacity", r => ((r.observed==true) ? "1.0" : ".5"))
|
||||
.on("mouseover", mouseover)
|
||||
.on("mousemove", mousemove)
|
||||
.on("mouseleave", mouseleave);
|
||||
|
|
@ -352,23 +322,21 @@ function svs_list_observer(d)
|
|||
return a_to_s(svs_list_observer_raw(d));
|
||||
}
|
||||
|
||||
function observer_up(r)
|
||||
function observer_up(d)
|
||||
{
|
||||
var considered_old = 15 * 60; // 15 mins
|
||||
|
||||
if (age_in_seconds(r["last-seen"]) > considered_old) {
|
||||
if (age_in_seconds(d["last-seen"]) > considered_old) {
|
||||
// data is old
|
||||
return false;
|
||||
}
|
||||
if (Object.keys(r.svs).length == 0) {
|
||||
if (Object.keys(d.svs).length == 0) {
|
||||
// nothing visible
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var observer_shape = [];
|
||||
|
||||
function draw_observers(data_observers)
|
||||
{
|
||||
// Three function that change the tooltip when user hover / move / leave a cell
|
||||
|
|
@ -377,73 +345,62 @@ function draw_observers(data_observers)
|
|||
s = d.id + ": [" + to2(d.longitude) + "," + to2(d.latitude) + "]" + ((o == "") ? "" : " sees " + svs_list_observer(d));
|
||||
Tooltip.html(s)
|
||||
.style("opacity", 1)
|
||||
.style("left", (d3.mouse(this)[0] + tooltop_deltax) + "px")
|
||||
.style("left", (d3.mouse(this)[0]+30) + "px")
|
||||
.style("top", (d3.mouse(this)[1]) + "px");
|
||||
d3.selectAll(".radials").remove();
|
||||
draw_operator_to_satellite(d);
|
||||
|
||||
// redraw only the one coverage circle
|
||||
coverages = $("[id^='Coverage_']");
|
||||
coverages.hide();
|
||||
coverages = $("[id^='Coverage_" + d.id + "_area']");
|
||||
coverages.show();
|
||||
}
|
||||
var mousemove = function(d) {
|
||||
var o = svs_list_observer(d);
|
||||
s = d.id + ": [" + to2(d.longitude) + "," + to2(d.latitude) + "]" + ((o == "") ? "" : " sees " + svs_list_observer(d));
|
||||
Tooltip.html(s)
|
||||
.style("left", (d3.mouse(this)[0] + tooltop_deltax) + "px")
|
||||
.style("left", (d3.mouse(this)[0]+30) + "px")
|
||||
.style("top", (d3.mouse(this)[1]) + "px");
|
||||
}
|
||||
var mouseleave = function(d) {
|
||||
Tooltip.html("")
|
||||
.style("opacity", 0);
|
||||
d3.selectAll(".radials").remove();
|
||||
display_all_refresh();
|
||||
coverage_map_refresh();
|
||||
}
|
||||
|
||||
// text first as we want the observer rectangle to be always above them!
|
||||
svgAlmanac.selectAll("olables")
|
||||
.data(data_observers)
|
||||
.enter()
|
||||
.append("text")
|
||||
.text(r => r.id)
|
||||
.attr("class", r => (observer_up(r) ? "observers" : "observers down"))
|
||||
.attr("id", r => "Observer_" + r.id + "_label")
|
||||
.attr("dx", r => (aProjection([r.longitude, r.latitude])[0]))
|
||||
.attr("dy", r => (aProjection([r.longitude, r.latitude])[1]))
|
||||
.attr("fill", r => (observer_up(r) ? "black" : "#666666"))
|
||||
.attr("text-anchor", r => ((r.longitude > 0) ? "start" : "end"))
|
||||
.attr("baseline-shift", r => ((r.latitude > 0) ? "+30%" : "-90%"))
|
||||
.attr("fill-opacity", r => (observer_up(r) ? "1.0" : ".5"))
|
||||
.attr("stroke-opacity", r => (observer_up(r) ? "1.0" : ".5"))
|
||||
.attr("font-weight", r => (observer_up(r) ? "bold" : null));
|
||||
|
||||
// we draw a geo correct observer rectangle - mapped onto whatever globe we have projected
|
||||
var observer_degrees = 2.5;
|
||||
svgAlmanac.selectAll("div")
|
||||
.data(data_observers)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", r => (observer_up(r) ? "observers" : "observers down"))
|
||||
.attr("id", r => ("Observer_" + r.id + "_location"))
|
||||
.attr("d", function(r) {
|
||||
var path = {
|
||||
type: "LineString",
|
||||
coordinates: [
|
||||
[r.longitude - observer_degrees/2, r.latitude - observer_degrees/2],
|
||||
[r.longitude - observer_degrees/2, r.latitude + observer_degrees/2],
|
||||
[r.longitude + observer_degrees/2, r.latitude + observer_degrees/2],
|
||||
[r.longitude + observer_degrees/2, r.latitude - observer_degrees/2],
|
||||
]
|
||||
};
|
||||
observer_shape[r.id] = path; // save away for later
|
||||
return geoPath(path);
|
||||
})
|
||||
.on("mouseover", mouseover)
|
||||
.on("mousemove", mousemove)
|
||||
.on("mouseleave", mouseleave);
|
||||
if (0) {
|
||||
// we draw a geo correct rectangle
|
||||
var observer_degrees = 3.0;
|
||||
svgAlmanac.selectAll("div")
|
||||
.data(data_observers)
|
||||
.enter()
|
||||
.append("path")
|
||||
.attr("class", r => (observer_up(r) ? "observers" : "observers down"))
|
||||
.attr("id", r => ("Observer_" + r["id"]))
|
||||
.attr("d", function(d) {
|
||||
return geoPath({
|
||||
type: "LineString",
|
||||
coordinates: [
|
||||
[d.longitude - observer_degrees/2, d.latitude - observer_degrees/2],
|
||||
[d.longitude - observer_degrees/2, d.latitude + observer_degrees/2],
|
||||
[d.longitude + observer_degrees/2, d.latitude + observer_degrees/2],
|
||||
[d.longitude + observer_degrees/2, d.latitude - observer_degrees/2],
|
||||
]
|
||||
});
|
||||
})
|
||||
.on("mouseover", mouseover)
|
||||
.on("mousemove", mousemove)
|
||||
.on("mouseleave", mouseleave);
|
||||
} else {
|
||||
rect_size = 8;
|
||||
svgAlmanac.selectAll("div")
|
||||
.data(data_observers)
|
||||
.enter()
|
||||
.append("rect")
|
||||
.attr("class", r => (observer_up(r) ? "observers" : "observers down"))
|
||||
.attr("id", r => ("Observer_" + r["id"]))
|
||||
.attr("width", rect_size)
|
||||
.attr("height", rect_size)
|
||||
.attr("x", r => (aProjection([r.longitude, r.latitude])[0] - rect_size/2))
|
||||
.attr("y", r => (aProjection([r.longitude, r.latitude])[1] - rect_size/2))
|
||||
.on("mouseover", mouseover)
|
||||
.on("mousemove", mousemove)
|
||||
.on("mouseleave", mouseleave);
|
||||
}
|
||||
|
||||
// kick off the annimation - if needed
|
||||
annimate_down_observers();
|
||||
|
|
@ -459,16 +416,10 @@ function annimate_down_observers()
|
|||
if (down.length > 0) {
|
||||
// if we have an observer that is down - lets annoutate it!
|
||||
for (var ii=0, ll=down.length; ii<ll; ii++){
|
||||
//if (down[ii].classList.contains('down')) {
|
||||
// down[ii].classList.remove('down');
|
||||
//} else {
|
||||
// down[ii].classList.add('down');
|
||||
//}
|
||||
down[ii].style.fill = (down[ii].style.fill == "black") ? "red" : "black";
|
||||
down[ii].style.stroke = (down[ii].style.stroke == "black") ? "red" : "black";
|
||||
}
|
||||
}
|
||||
var seconds = 2;
|
||||
var seconds = 1;
|
||||
down_timer = setTimeout(annimate_down_observers, seconds * 1000.0);
|
||||
}
|
||||
|
||||
|
|
@ -485,29 +436,26 @@ function draw_observers_coverage(data_observers)
|
|||
.enter()
|
||||
.append("path")
|
||||
.attr("class", r => (observer_up(r) ? "coverage" : "coverage down"))
|
||||
.attr("id", r => ("Coverage_" + r.id + "_area"))
|
||||
.attr("d", function(r) { return geoPath(geoCircle.center([r.longitude, r.latitude]).radius(radius)());
|
||||
.attr("d", function(r) {
|
||||
return geoPath(geoCircle.center([r["longitude"], r["latitude"]]).radius(radius)());
|
||||
});
|
||||
}
|
||||
|
||||
var data_almanac = null;
|
||||
var data_observers = null;
|
||||
var time_last_data_received = 0;
|
||||
|
||||
function do_update_almanac_observers(error, results)
|
||||
{
|
||||
var updated_almanac = false;
|
||||
var updated_observers = false;
|
||||
|
||||
if (results && results.length > 0 && results[0]) {
|
||||
if (results.length > 0 && results[0]) {
|
||||
data_almanac = results[0];
|
||||
updated_almanac = true;
|
||||
time_last_data_received = now();
|
||||
}
|
||||
if (results.length > 1 && results[1]) {
|
||||
data_observers = results[1];
|
||||
updated_observers = true;
|
||||
time_last_data_received = now();
|
||||
}
|
||||
|
||||
// XXX cheat for now - we need until we fix d3/svg bug above
|
||||
|
|
@ -516,59 +464,38 @@ function do_update_almanac_observers(error, results)
|
|||
|
||||
if (updated_almanac) {
|
||||
// We write into the svgalmanac area - so clean it and rewrite it
|
||||
if (draw_almanac) {
|
||||
draw_almanac(data_almanac);
|
||||
}
|
||||
// now hide/show the ones that should be seen
|
||||
constellation_refresh();
|
||||
draw_almanac(data_almanac);
|
||||
}
|
||||
if (updated_observers) {
|
||||
if (data_observers) {
|
||||
draw_observers_coverage(data_observers)
|
||||
draw_observers(data_observers);
|
||||
}
|
||||
coverage_map_refresh();
|
||||
observer_map_refresh();
|
||||
draw_observers_coverage(data_observers)
|
||||
draw_observers(data_observers);
|
||||
}
|
||||
display_all_refresh();
|
||||
}
|
||||
|
||||
var redisplay_timer = null;
|
||||
var repeat_timer;
|
||||
var display_observers_count = 0;
|
||||
|
||||
function stop_redisplay_timer()
|
||||
function do_timer()
|
||||
{
|
||||
clearTimeout(redisplay_timer);
|
||||
redisplay_timer = null;
|
||||
}
|
||||
clearTimeout(repeat_timer);
|
||||
|
||||
function do_redisplay_timer()
|
||||
{
|
||||
stop_redisplay_timer();
|
||||
|
||||
if ((now() - time_last_data_received) >= 55) {
|
||||
// refresh data from afar
|
||||
if (display_observers_count == 0) {
|
||||
// observers does not need that much updating!
|
||||
d3.queue(1)
|
||||
.defer(d3.json, fileAlmanac + '?t=' + now())
|
||||
.defer(d3.json, fileObservers + '?t=' + now())
|
||||
.awaitAll(do_update_almanac_observers);
|
||||
display_observers_count = 10;
|
||||
} else {
|
||||
// just queue an update to almanac
|
||||
d3.queue(1)
|
||||
.defer(d3.json, fileAlmanac + '?t=' + now())
|
||||
.awaitAll(do_update_almanac_observers);
|
||||
}
|
||||
display_observers_count--;
|
||||
if (display_observers_count == 0) {
|
||||
// observers does not need that much updating!
|
||||
d3.queue(1)
|
||||
.defer(d3.json, fileAlmanac + '?t=' + now())
|
||||
.defer(d3.json, fileObservers + '?t=' + now())
|
||||
.awaitAll(do_update_almanac_observers);
|
||||
display_observers_count = 10;
|
||||
} else {
|
||||
// just use existing data
|
||||
do_update_almanac_observers(null, []);
|
||||
// just queue an update to almanac
|
||||
d3.queue(1)
|
||||
.defer(d3.json, fileAlmanac + '?t=' + now())
|
||||
.awaitAll(do_update_almanac_observers);
|
||||
}
|
||||
display_observers_count--;
|
||||
|
||||
var seconds = 60;
|
||||
redisplay_timer = setTimeout(do_redisplay_timer, seconds * 1000.0);
|
||||
repeat_timer = setTimeout(do_timer, seconds * 1000.0);
|
||||
}
|
||||
|
||||
function set_projection(data_world)
|
||||
|
|
@ -591,8 +518,9 @@ function set_projection(data_world)
|
|||
|
||||
var offset = [idCombined.clientWidth/2, idCombined.clientHeight/2];
|
||||
|
||||
switch(projectionChoices[projectionChoice]) {
|
||||
switch(projectionChoice) {
|
||||
default:
|
||||
console.log(projectionChoice + ': not coded');
|
||||
// fall thru to Equirectangular
|
||||
case 'Equirectangular':
|
||||
aProjection = d3.geoEquirectangular()
|
||||
|
|
@ -614,6 +542,11 @@ function set_projection(data_world)
|
|||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Gilbert':
|
||||
aProjection = d3.geoGilbert()
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Orthographic':
|
||||
aProjection = d3.geoOrthographic()
|
||||
.scale(scale)
|
||||
|
|
@ -637,8 +570,9 @@ function set_projection(data_world)
|
|||
|
||||
if (0) {
|
||||
// new projection
|
||||
switch(projectionChoices[projectionChoice]) {
|
||||
switch(projectionChoice) {
|
||||
default:
|
||||
console.log(projectionChoice + ': not coded');
|
||||
// fall thru to Equirectangular
|
||||
case 'Equirectangular':
|
||||
aProjection = d3.geoEquirectangular()
|
||||
|
|
@ -663,6 +597,12 @@ function set_projection(data_world)
|
|||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Gilbert':
|
||||
aProjection = d3.geoGilbert()
|
||||
.center(center)
|
||||
.scale(scale)
|
||||
.translate(offset);
|
||||
break;
|
||||
case 'Orthographic':
|
||||
aProjection = d3.geoOrthographic()
|
||||
.center(center)
|
||||
|
|
@ -695,344 +635,43 @@ function do_draw_world(data_world)
|
|||
{
|
||||
set_projection(data_world);
|
||||
|
||||
aProjection.rotate([globe_rotate.lambda, globe_rotate.phi]); // show globe where it belongs for current timezone
|
||||
|
||||
create_tooltop();
|
||||
|
||||
draw_world(data_world);
|
||||
draw_graticule();
|
||||
|
||||
if (0) {
|
||||
// XXX recode - this is bad.
|
||||
if (projectionChoice == 'Orthographic') {
|
||||
// rotating globe is done with a constant timer
|
||||
d3.timer(function() {
|
||||
var lambda = speed * (Date.now() - start); // λ
|
||||
var phi = -15; // φ
|
||||
aProjection.rotate([lambda + 180, -phi]);
|
||||
if (projectionChoice == 'Orthographic') {
|
||||
// rotating globe is done with a constant timer
|
||||
d3.timer(function() {
|
||||
var lambda = speed * (Date.now() - start); // λ
|
||||
var phi = -15; // φ
|
||||
aProjection.rotate([lambda + 180, -phi]);
|
||||
|
||||
draw_world(data_world);
|
||||
draw_graticule();
|
||||
do_update_almanac_observers(null, [])
|
||||
});
|
||||
}
|
||||
// XXX this will work one day ...
|
||||
// var node = {id: "rotate_west"}; // The "id" is the only part used.
|
||||
// rotate_globe(node)
|
||||
draw_world(data_world);
|
||||
draw_graticule();
|
||||
do_update_almanac_observers(null, [])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var data_world = null;
|
||||
}
|
||||
|
||||
function read_world()
|
||||
{
|
||||
if (data_world == null) {
|
||||
d3.json(fileWorld, function(result) {
|
||||
data_world = result;
|
||||
do_draw_world(data_world);
|
||||
// after the world is read in and displayed - then start the timers!
|
||||
// we don't redraw the world!
|
||||
do_redisplay_timer();
|
||||
});
|
||||
} else {
|
||||
d3.json(fileWorld, function(result) {
|
||||
var data_world = result;
|
||||
do_draw_world(data_world);
|
||||
// after the world is read in and displayed - then start the timers!
|
||||
// we don't redraw the world!
|
||||
do_draw_world(data_world);
|
||||
do_redisplay_timer();
|
||||
}
|
||||
}
|
||||
|
||||
function set_rotate_from_tz()
|
||||
{
|
||||
var offset = new Date().getTimezoneOffset(); // in minutes from UTC
|
||||
|
||||
globe_rotate_center.lambda = (offset/(60.0*24.0)) * 360.0;
|
||||
globe_rotate_center.phi = 0.0;
|
||||
|
||||
globe_rotate.lambda = globe_rotate_center.lambda;
|
||||
globe_rotate.phi = globe_rotate_center.phi;
|
||||
}
|
||||
|
||||
function load_settings()
|
||||
{
|
||||
if(typeof(Storage) !== "undefined")
|
||||
{
|
||||
var storage_constellations = localStorage.getItem("geo-settings-constellations");
|
||||
if(storage_constellations != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
constellation_state = JSON.parse(storage_constellations);
|
||||
/* Set Menubar checkboxes */
|
||||
for(const constellation in constellation_state)
|
||||
{
|
||||
if(document.getElementById('constellation-' + constellation))
|
||||
{
|
||||
document.getElementById('constellation-' + constellation).childNodes[1].checked = constellation_state[constellation];
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.log("Error parsing storage_constellations!",e);
|
||||
}
|
||||
}
|
||||
|
||||
var storage_map = localStorage.getItem("geo-settings-map");
|
||||
if(storage_map != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var _map_settings = JSON.parse(storage_map);
|
||||
coverage_map_state = _map_settings["coverage"];
|
||||
observer_map_state = _map_settings["observer"];
|
||||
display_all_state = _map_settings["display_all"];
|
||||
/* Set Menubar checkboxes */
|
||||
document.getElementById("coverage_map").childNodes[1].checked = coverage_map_state;
|
||||
document.getElementById("observer_map").childNodes[1].checked = observer_map_state;
|
||||
document.getElementById("display_all").childNodes[1].checked = display_all_state;
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.log("Error parsing storage_map!",e);
|
||||
}
|
||||
}
|
||||
|
||||
var storage_globe = localStorage.getItem("geo-settings-globe");
|
||||
if(storage_globe != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var _globe_settings = JSON.parse(storage_globe);
|
||||
globe_rotate = _globe_settings["rotate"];
|
||||
globe_rotate_center = _globe_settings["rotate_center"];
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.log("Error parsing storage_globe!",e);
|
||||
}
|
||||
}
|
||||
|
||||
var storage_projection = localStorage.getItem("geo-settings-projection");
|
||||
if(storage_projection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
projectionChoice = JSON.parse(storage_projection);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.log("Error parsing storage_projection!",e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Save defaults even if we didn't load anything */
|
||||
save_settings();
|
||||
}
|
||||
}
|
||||
|
||||
function save_settings()
|
||||
{
|
||||
if(typeof(Storage) !== "undefined")
|
||||
{
|
||||
localStorage.setItem("geo-settings-constellations", JSON.stringify(constellation_state));
|
||||
|
||||
var _map_settings = {
|
||||
"coverage": coverage_map_state,
|
||||
"observer": observer_map_state,
|
||||
"display_all": display_all_state
|
||||
};
|
||||
localStorage.setItem("geo-settings-map", JSON.stringify(_map_settings));
|
||||
|
||||
var _globe_settings = {
|
||||
"rotate": globe_rotate,
|
||||
"rotate_center": globe_rotate_center
|
||||
};
|
||||
localStorage.setItem("geo-settings-globe", JSON.stringify(_globe_settings));
|
||||
|
||||
localStorage.setItem("geo-settings-projection", JSON.stringify(projectionChoice));
|
||||
}
|
||||
do_timer();
|
||||
});
|
||||
}
|
||||
|
||||
function geo_start()
|
||||
{
|
||||
set_rotate_from_tz();
|
||||
load_settings();
|
||||
read_world();
|
||||
}
|
||||
|
||||
geo_start();
|
||||
|
||||
// d3.select("body").onresize = do_redisplay_timer;
|
||||
|
||||
function constellation_click(node)
|
||||
{
|
||||
// Satellites are named LETTER+NUMBERS
|
||||
var c = node.innerText.trim();
|
||||
if (c == "GPS") { c = "G"; } // G = GPS (American)
|
||||
else if (c == "Galileo") { c = "E"; } // E = Europe
|
||||
else if (c == "BeiDou") { c = "C"; } // C = China
|
||||
else if (c == "IMES") { c = "I"; } // I = Indoor QZSS (doubtfully seen)
|
||||
else if (c == "QZSS") { c = "J"; } // J = Japan
|
||||
else if (c == "GLONASS") { c = "R"; } // R = Russia
|
||||
|
||||
satellites = $("[id^='Satellite_" + c + "']");
|
||||
|
||||
if (node.childNodes[1].checked) {
|
||||
satellites.show();
|
||||
constellation_state[c] = true;
|
||||
} else {
|
||||
satellites.hide();
|
||||
constellation_state[c] = false;
|
||||
}
|
||||
d3.selectAll(".radials").remove();
|
||||
display_all_refresh();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
function constellation_refresh()
|
||||
{
|
||||
for (c in constellation_state) {
|
||||
satellites = $("[id^='Satellite_" + c + "']");
|
||||
if (constellation_state[c]) {
|
||||
satellites.show();
|
||||
} else {
|
||||
satellites.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function coverage_map_click(node)
|
||||
{
|
||||
if (node.childNodes[1].checked) {
|
||||
coverage_map_state = true;
|
||||
} else {
|
||||
coverage_map_state = false;
|
||||
}
|
||||
coverage_map_refresh();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
function coverage_map_refresh()
|
||||
{
|
||||
coverages = $("[id^='Coverage_']");
|
||||
if (coverage_map_state) {
|
||||
coverages.show();
|
||||
} else {
|
||||
coverages.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function observer_map_click(node)
|
||||
{
|
||||
if (node.childNodes[1].checked) {
|
||||
observer_map_state = true;
|
||||
} else {
|
||||
observer_map_state = false;
|
||||
}
|
||||
observer_map_refresh();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
function observer_map_refresh()
|
||||
{
|
||||
observers = $("[id^='Observer_']");
|
||||
if (observer_map_state) {
|
||||
observers.show();
|
||||
} else {
|
||||
observers.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function display_all_click(node)
|
||||
{
|
||||
if (node.childNodes[1].checked) {
|
||||
display_all_state = true;
|
||||
} else {
|
||||
display_all_state = false;
|
||||
}
|
||||
display_all_refresh();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
function display_all_refresh()
|
||||
{
|
||||
if (display_all_state) {
|
||||
for (d in data_observers) {
|
||||
draw_operator_to_satellite(data_observers[d]);
|
||||
}
|
||||
} else {
|
||||
d3.selectAll(".radials").remove();
|
||||
}
|
||||
}
|
||||
|
||||
function show_history_click(node)
|
||||
{
|
||||
var c = node.innerText.trim();
|
||||
if (node.childNodes[1].checked) {
|
||||
// XXX
|
||||
console.log(c + " :checked");
|
||||
} else {
|
||||
// XXX
|
||||
console.log(c + " :not checked");
|
||||
}
|
||||
}
|
||||
|
||||
function show_animate_click(node)
|
||||
{
|
||||
var c = node.innerText.trim();
|
||||
if (node.childNodes[1].checked) {
|
||||
// XXX
|
||||
console.log(c + " :checked");
|
||||
} else {
|
||||
// XXX
|
||||
console.log(c + " :not checked");
|
||||
}
|
||||
}
|
||||
|
||||
function update_projection_click(node)
|
||||
{
|
||||
// cycle thru known globe projections
|
||||
projectionChoice++;
|
||||
if (projectionChoice >= projectionChoices.length)
|
||||
projectionChoice = 0;
|
||||
// now redraw everything!
|
||||
read_world();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
function rotate_globe(node)
|
||||
{
|
||||
var t = node.id;
|
||||
if (t == "rotate_north") {
|
||||
globe_rotate.phi += 10; // North
|
||||
} else if (t == "rotate_west") {
|
||||
globe_rotate.lambda -= 5; // West
|
||||
} else if (t == "rotate_center") {
|
||||
globe_rotate.lambda = globe_rotate_center.lambda;
|
||||
globe_rotate.phi = globe_rotate_center.phi; // Reset to center
|
||||
} else if (t == "rotate_east") {
|
||||
globe_rotate.lambda += 5; // East
|
||||
} else if (t == "rotate_south") {
|
||||
globe_rotate.phi -= 10; // South
|
||||
}
|
||||
|
||||
read_world();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
// JQuery also has a startup
|
||||
|
||||
$(document).ready(function () {
|
||||
$("[id^='constellation']").click(function() { constellation_click(this); });
|
||||
$('#coverage_map').click(function() { coverage_map_click(this); });
|
||||
$('#observer_map').click(function() { observer_map_click(this); });
|
||||
|
||||
$('#display_all').click(function() { display_all_click(this); });
|
||||
$('#show_history').click(function() { show_history_click(this); });
|
||||
$('#show_animate').click(function() { show_animate_click(this); });
|
||||
$('#update_projection').click(function() { update_projection_click(this); });
|
||||
|
||||
$("[id^='rotate_']").click(function() { rotate_globe(this); });
|
||||
});
|
||||
// d3.select("body").onresize = do_timer;
|
||||
|
||||
|
|
|
|||