diff --git a/.dockerignore b/.dockerignore index bbd458dcf488e8c522fbae7923909f96a4327383..63410b57cc6cdbaa2fce4f58ae2a0a05bc1c8503 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,10 @@ .coverage .DS_Store +.eggs .git -node_modules +.mypy_cache +.pytest_cache +coverage.xml +build +dist *.pyc -coverage.xml \ No newline at end of file diff --git a/.gitignore b/.gitignore index a8cadd0b80f9e0d36ca345bfa239763035ff08b1..574fe32b04533cd4854491fea23fa54a18abd6d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ .coverage cov.xml .DS_Store -node_modules +.eggs *.pyc coverage.xml .ipynb_checkpoints* +.mypy_cache .pytest_cache htmlcov .vscode diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 53f95126c619dd95e1a1734c76cf7b083c622d26..ae1ce65c7a448f942397fa41d56f06708bde29e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,19 +1,29 @@ ---- -include: - - project: 'ghsc/hazdev/pipeline-build-template' - ref: 'master' - file: 'templates/library.yml' - image: ${DEVOPS_REGISTRY}usgs/centos:latest +cache: + paths: + # cache pip installed dependencies, see PIP_CACHE_DIR variable below + - .cache/pip + stages: - test - integration + - scan - deploy variables: CI_REGISTRY: ${CODE_REGISTRY} CI_REGISTRY_IMAGE: ${CODE_REGISTRY_IMAGE} + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + TRIVY_VERSION: "0.13.0" + # docker variables + DOCKER_DRIVER: overlay2 + FROM_IMAGE: ${CODE_REGISTRY}/devops/images/usgs/python:3.8-obspy + # environment variables + APP_NAME: geomag-algorithms + DATA_HOST: "cwbpub.cr.usgs.gov" + DATA_PORT: "2060" + DATA_TYPE: "edge" ## -------------------------------------------------- # Templates @@ -30,7 +40,11 @@ variables: cache: {} extends: - .adjust_image_names - - .dind + image: docker:19.03-git + only: + - master@ghsc/geomag/geomag-algorithms + - production@ghsc/geomag/geomag-algorithms + - tags@ghsc/geomag/geomag-algorithms script: - docker build --pull @@ -46,36 +60,32 @@ variables: - docker tag local/${IMAGE_NAME} ${INTERNAL_IMAGE_NAME} - docker push ${INTERNAL_IMAGE_NAME} - docker image rm ${INTERNAL_IMAGE_NAME} + services: + - docker:19.03-dind stage: integration tags: - build - variables: - FROM_IMAGE: ${CODE_REGISTRY}/devops/images/usgs/centos:latest .check_code: - cache: {} - image: ${DEVOPS_REGISTRY}usgs/conda:latest - script: - # Add conda to path - - source /etc/profile.d/conda.sh - # Install Project Dependencies - - conda config --add channels conda-forge - - conda install python=${PYTHON_VERSION} obspy pycurl - - pip install pipenv - - pipenv --site-packages install --dev --pre --skip-lock - # Run Code Checks - - pipenv run black --check . - - pipenv run pytest --cov-report xml:cov.xml --cov=geomagio artifacts: - paths: - - cov.xml reports: - junit: cov.xml + cobertura: coverage.xml + junit: junit.xml + before_script: + # install dependencies + - poetry config virtualenvs.create false + - poetry install + - which python + image: ${DEVOPS_REGISTRY}usgs/python:3.8-build + script: + # run checks + - black --check . + - pytest --cov=geomagio --junitxml junit.xml + - coverage xml + - safety check stage: test tags: - development - variables: - PYTHON_VERSION: 3.8 .deploy: cache: {} @@ -89,6 +99,7 @@ variables: generic-deploy/default.funcs.sh generic-deploy/deploy.sh scripts/. + - export APP_NAME=${APP_NAME} - export IMAGE_NAME=${IMAGE_NAME} - export REGISTRY=${CI_REGISTRY_IMAGE} @@ -98,81 +109,187 @@ variables: tags: - deploy - swarm - variables: - APP_NAME: geomag-algorithms + # these variables are defined in Gitlab, or set in scripts/custom.config.sh + # variables: + # DATABASE_URL + # OPENID_CLIENT_ID + # OPENID_CLIENT_SECRET + # OPENID_METADATA_URL + # SECRET_KEY + # SECRET_SALT + # ADMIN_GROUP + # REVIEWER_GROUP -.mage: +.deploy-library: + cache: {} + image: "docker:stable-git" + script: + - PREFIX_LENGTH=${#REQUIRED_PREFIX}; + - if [[ "${APP_DEPLOY_DIR:0:${PREFIX_LENGTH}}" != "${REQUIRED_PREFIX}" ]]; then + echo "APP_DEPLOY_DIR does not contain correct path"; + exit 255; + fi + - if [ ! -d "${APP_DEPLOY_DIR}" ]; then + cd "$(dirname "${APP_DEPLOY_DIR}")"; + git clone "${CI_REPOSITORY_URL}" "$(basename "${APP_DEPLOY_DIR}")"; + fi + - cd "${APP_DEPLOY_DIR}"; + - git checkout "${CI_COMMIT_REF_NAME}" || git checkout -b "${CI_COMMIT_REF_NAME}"; + - git pull --ff-only "${CI_REPOSITORY_URL}" "${CI_COMMIT_REF_NAME}"; + stage: deploy + tags: + - deploy + - swarm variables: - DATA_HOST: 'cwbpub.cr.usgs.gov' - DATA_PORT: '2060' - DATA_TYPE: 'edge' + APP_DEPLOY_DIR: "/geomag/geomag-algorithms" + REQUIRED_PREFIX: "/geomag" -.mage01: - tags: - - mage01 -.mage02: - tags: - - mage02 + +.staging: + only: + - master@ghsc/geomag/geomag-algorithms + - tags@ghsc/geomag/geomag-algorithms + +.production: + except: + - ^.*beta.*$ + - ^.*-rc.*$ + only: + - tags@ghsc/geomag/geomag-algorithms + - production@ghsc/geomag/geomag-algorithms + when: manual ## -------------------------------------------------- # Test Stage ## -------------------------------------------------- -Check Python 3.6: +# Numpy 1.20 requires python 3.7 or newer +# Check Python 3.6: +# extends: +# - .check_code +# image: ${DEVOPS_REGISTRY}usgs/obspy:3 + +Check Python 3.8: extends: - .check_code - variables: - PYTHON_VERSION: '3.6' + image: ${DEVOPS_REGISTRY}usgs/python:3.8-build + +## -------------------------------------------------- +# Integration Stage +## -------------------------------------------------- -Check Python 3.7: +Build Docker Image: extends: - - .check_code + - .build_docker_image variables: - PYTHON_VERSION: '3.7' + APP_NAME: geomag-algorithms -Check Python 3.8: +## -------------------------------------------------- +# Scanning Stage (e.g. OWASP ZAP etc...) +## -------------------------------------------------- + +Scan Docker Image: + cache: {} + # temporarily allow while cryptography dependency has CVE + # new version no less secure than old + allow_failure: true extends: - - .check_code - variables: - PYTHON_VERSION: '3.8' + - .adjust_image_names + image: docker:19.03-git + only: + - master@ghsc/geomag/geomag-algorithms + - production@ghsc/geomag/geomag-algorithms + - tags@ghsc/geomag/geomag-algorithms + script: + # install trivy + - wget https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz + - tar zxvf trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz + # run trivy + - ./trivy image --no-progress --exit-code 1 --severity HIGH,CRITICAL ${INTERNAL_IMAGE_NAME} + services: + - docker:19.03-dind + stage: scan + tags: + - build ## -------------------------------------------------- -# Integration Stage +# Deploy Stage ## -------------------------------------------------- -Build Staging Docker Image: +Mage01 Library: extends: - - .build_docker_image + - .deploy-library - .staging + tags: + - deploy + - swarm + - mage01 variables: APP_NAME: geomag-algorithms -Build Production Docker Image: +Mage01 Web Service: extends: - - .build_docker_image + - .deploy + - .staging + tags: + - deploy + - swarm + - mage01 + variables: + APP_NAME: geomag-algorithms + +Mage02 Library: + extends: + - .deploy-library - .production + tags: + - deploy + - swarm + - mage02 variables: APP_NAME: geomag-algorithms -## -------------------------------------------------- -# Deploy Stage -## -------------------------------------------------- +Mage02 Web Service: + extends: + - .deploy + - .production + tags: + - deploy + - swarm + - mage02 + variables: + APP_NAME: geomag-algorithms -Deploy Mage01: +Production01 Web Service: extends: - .deploy - - .staging - - .mage - - .mage01 + - .production + tags: + - deploy + - swarm + - production01 variables: APP_NAME: geomag-algorithms -Deploy Mage02: +Production02 Web Service: extends: - .deploy - .production - - .mage - - .mage02 + tags: + - deploy + - swarm + - production02 + variables: + APP_NAME: geomag-algorithms + +Staging01 Web Service: + extends: + - .deploy + - .staging + tags: + - deploy + - swarm + - staging01 variables: APP_NAME: geomag-algorithms diff --git a/.travis.yml b/.travis.yml index 75742effa65b0013dd8469a8e7bf9baf371bf888..3c5ff7756c94292cf8872ecae06dbce72627ab5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,24 @@ -language: python -python: - - 3.6 - - 3.7 -before_install: +language: minimal +# run in container +arch: amd64 +dist: focal +env: + - PYTHON_VERSION=3.6 + - PYTHON_VERSION=3.7 + - PYTHON_VERSION=3.8 +install: ## courtesy of http://conda.pydata.org/docs/travis.html - - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - wget https://repo.anaconda.com/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; - else - wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - fi + - wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" - hash -r - conda config --set always_yes yes --set changeps1 no - conda update --yes conda - conda info -a -install: - - conda config --add channels conda-forge - - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION obspy pycurl black pytest pytest-cov webtest pydantic openpyxl - - source activate test-environment - - pip install authlib flask flask-login flask-migrate flask-session flask-sqlalchemy psycopg2-binary script: - - black --check . - - pytest + # pass PYTHON_VERSION to check code script + - export PYTHON_VERSION=$TRAVIS_PYTHON_VERSION + # run linting and tests + - scripts/ci_check_code.sh after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/CODE_OF_CONDUCT.MD b/CODE_OF_CONDUCT.MD new file mode 100644 index 0000000000000000000000000000000000000000..c53a8b19cccd2ee9de82d9097c88d6daa1ead1d7 --- /dev/null +++ b/CODE_OF_CONDUCT.MD @@ -0,0 +1,6 @@ +# Code of Conduct + +All contributions to- and interactions surrounding- this project will abide by +the [USGS Code of Scientific Conduct][1]. + +[1]: https://www.usgs.gov/about/organization/science-support/science-quality-and-integrity/code-scientific-conduct \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3eb3f50a9f825d9073f066bc1b228bbdf523eb7a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing + +Contributions are welcome from the community. Questions can be asked on the +[issues page][1]. Before creating a new issue, please take a moment to search +and make sure a similar issue does not already exist. If one does exist, you +can comment (most simply even with just a `:+1:`) to show your support for that +issue. + +If you have direct contributions you would like considered for incorporation +into the project you can [fork this repository][2] and +[submit a merge request][3] for review. + +[1]: https://code.usgs.gov/ghsc/geomag/geomag-algorithms/issues +[2]: https://docs.gitlab.com/ee/workflow/forking_workflow.html#creating-a-fork +[3]: https://docs.gitlab.com/ee/gitlab-basics/add-merge-request.html diff --git a/DISCLAIMER.md b/DISCLAIMER.md new file mode 100644 index 0000000000000000000000000000000000000000..fb9a9721c01b7d5d544ebf162c429b766582f2b4 --- /dev/null +++ b/DISCLAIMER.md @@ -0,0 +1,10 @@ +# Disclaimer + +This software is preliminary or provisional and is subject to revision. It is +being provided to meet the need for timely best science. The software has not +received final approval by the U.S. Geological Survey (USGS). No warranty, +expressed or implied, is made by the USGS or the U.S. Government as to the +functionality of the software and related material nor shall the fact of release +constitute any such warranty. The software is provided on the condition that +neither the USGS nor the U.S. Government shall be held liable for any damages +resulting from the authorized or unauthorized use of the software. diff --git a/Dockerfile b/Dockerfile index 1a4e4487ce93bf4ca4e7dde9c795494e09a652de..2b59aeb98a1d0263d01dc39010d0f7f11f6e58a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,83 +1,41 @@ -ARG FROM_IMAGE=usgs/centos:latest +ARG FROM_IMAGE=usgs/python:3.8-obspy -FROM ${FROM_IMAGE} as conda -LABEL maintainer="Jeremy Fee <jmfee@usgs.gov>" + +FROM ${FROM_IMAGE} ARG GIT_BRANCH_NAME=none ARG GIT_COMMIT_SHA=none +ARG WEBSERVICE="false" # set environment variables ENV GIT_BRANCH_NAME=${GIT_BRANCH_NAME} \ GIT_COMMIT_SHA=${GIT_COMMIT_SHA} \ - LANG='en_US.UTF-8' \ - LC_ALL='en_US.UTF-8' \ - PATH=/conda/bin:$PATH \ - PIP_CERT=${SSL_CERT_FILE} \ - PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - REQUESTS_CA_BUNDLE=${SSL_CERT_FILE} - -# install conda -RUN echo 'export PATH=/conda/bin:$PATH' > /etc/profile.d/conda.sh \ - && curl \ - https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \ - -o ~/miniconda.sh \ - && /bin/bash ~/miniconda.sh -b -p /conda \ - && rm ~/miniconda.sh - -# install dependencies via conda -RUN conda config --set ssl_verify $SSL_CERT_FILE \ - && conda config --add channels conda-forge \ - && conda install --yes jupyter obspy pycurl \ - && conda clean --all -y \ - && export PIP_CERT=$SSL_CERT_FILE \ - && pip install pipenv 'virtualenv!=20.0.22' \ - && yum install -y which \ - && yum clean all - - -################################################################################ -## Development image + WEBSERVICE=${WEBSERVICE} -# build by running -# docker build -t geomag-algorithms-development --target development . +# install to system python +USER root -FROM conda as development +# install packages when dependencies change +COPY pyproject.toml poetry.lock /geomag-algorithms/ +RUN cd /geomag-algorithms \ + # install into system python + && poetry export -o requirements.txt --dev --without-hashes \ + # only install dependencies, not project + && pip install -r requirements.txt -# install dependencies via pipenv -WORKDIR /geomag-algorithms -COPY Pipfile /geomag-algorithms -RUN pipenv --site-packages install --dev --skip-lock - -# copy library (ignores set in .dockerignore) +# install rest of library separate from dependencies COPY . /geomag-algorithms - - -################################################################################ -## Production image - -FROM conda - -RUN useradd \ - -c 'Docker image user' \ - -m \ - -r \ - -s /sbin/nologin \ - geomag_user \ +RUN cd /geomag-algorithms \ + && pip install . \ + # add data directory owned by usgs-user && mkdir -p /data \ - && chown -R geomag_user:geomag_user /data - -USER geomag_user + && chown -R usgs-user:usgs-user /data +# configure python path, so project can be volume mounted +ENV PYTHONPATH="/geomag-algorithms" -# install dependencies via pipenv +# run as usgs-user +USER usgs-user WORKDIR /data -COPY Pipfile /data/ -RUN pipenv --site-packages install --skip-lock - -# copy library (ignores set in .dockerignore) -COPY . /geomag-algorithms - -EXPOSE 8000 - # entrypoint needs double quotes ENTRYPOINT [ "/geomag-algorithms/docker-entrypoint.sh" ] +EXPOSE 8000 diff --git a/LICENSE.md b/LICENSE.md index 58fc160d28b191cf7158a016c853a8c063ead65e..7a7729ff532bd7f53239e912cb08d56df7ffd409 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,23 +1,39 @@ -Unless otherwise noted, This software is in the public domain because it -contains materials that originally came from the United States Geological -Survey, an agency of the United States Department of Interior. For more -information, see the official USGS copyright policy at -http://www.usgs.gov/visual-id/credit_usgs.html#copyright - - -Disclaimers ------------ -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. - -Information provided by this software may be preliminary or provisional and is -subject to revision. It is being provided to meet the need for timely best -science. The information has not received final approval by the U.S. Geological -Survey (USGS) and is provided on the condition that neither the USGS nor the -U.S. Government shall be held liable for any damages resulting from the -authorized or unauthorized use of the information. +# License + +Unless otherwise noted, This project is in the public domain in the United +States because it contains materials that originally came from the United +States Geological Survey, an agency of the United States Department of +Interior. For more information, see the official USGS copyright policy at +https://www.usgs.gov/information-policies-and-instructions/copyrights-and-credits + +Additionally, we waive copyright and related rights in the work +worldwide through the CC0 1.0 Universal public domain dedication. + +## CC0 1.0 Universal Summary + +This is a human-readable summary of the +[Legal Code (read the full text)][1]. + +### No Copyright + +The person who associated a work with this deed has dedicated the work to +the public domain by waiving all of his or her rights to the work worldwide +under copyright law, including all related and neighboring rights, to the +extent allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +### Other Information + +In no way are the patent or trademark rights of any person affected by CC0, +nor are the rights that other persons may have in the work or in how the +work is used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with +this deed makes no warranties about the work, and disclaims liability for +all uses of the work, to the fullest extent permitted by applicable law. +When using or citing the work, you should not imply endorsement by the +author or the affirmer. + +[1]: https://creativecommons.org/publicdomain/zero/1.0/legalcode diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 474d57489f323ac51da89ff2501cddeb5d1dca87..0000000000000000000000000000000000000000 --- a/Pipfile +++ /dev/null @@ -1,33 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.org/simple" -verify_ssl = true - -[dev-packages] -black = "*" -pre-commit = "*" -pytest = "*" -pytest-cov = "*" -webtest = "*" - -[packages] -numpy = "*" -scipy = "*" -obspy = "*" -pycurl = "*" -authlib = "*" -cryptography = "*" -databases = {extras = ["postgresql", "sqlite"],version = "*"} -fastapi = "*" -httpx = "==0.11.1" -openpyxl = "*" -pydantic = "*" -sqlalchemy = "*" -sqlalchemy-utc = "*" -typing = "*" -typing-extensions = "*" -uvicorn = "*" -gunicorn = "*" - -[pipenv] -allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 70ede93436834f2771487cb3190efe5bd95b6335..0000000000000000000000000000000000000000 --- a/Pipfile.lock +++ /dev/null @@ -1,1011 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "8987d05d0f53664d2a1f1cadaaa9c6fcb8eb76582e46d1c5b9ed785fe2500818" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "aiosqlite": { - "hashes": [ - "sha256:50688c40632ae249f986ab3ae2c66a45c0535b84a5d4aae0e0be572b5fed6909", - "sha256:6e92961ae9e606b43b05e29b129e346b29e400fcbd63e3c0c564d89230257645" - ], - "version": "==0.13.0" - }, - "asyncpg": { - "hashes": [ - "sha256:058baec9d6b75612412baa872a1aa47317d0ff88c318a49f9c4a2389043d5a8d", - "sha256:0c336903c3b08e970f8af2f606332f1738dba156bca83ed0467dc2f5c70da796", - "sha256:1388caa456070dab102be874205e3ae8fd1de2577d5de9fa22e65ba5c0f8b110", - "sha256:25edb0b947eb632b6b53e5a4b36cba5677297bb34cbaba270019714d0a5fed76", - "sha256:2af6a5a705accd36e13292ea43d08c20b15e52d684beb522cb3a7d3c9c8f3f48", - "sha256:391aea89871df8c1560750af6c7170f2772c2d133b34772acf3637e3cf4db93e", - "sha256:394bf19bdddbba07a38cd6fb526ebf66e120444d6b3097332b78efd5b26495b0", - "sha256:5664d1bd8abe64fc60a0e701eb85fa1d8c9a4a8018a5a59164d27238f2caf395", - "sha256:57666dfae38f4dbf84ffbf0c5c0f78733fef0e8e083230275dcb9ccad1d5ee09", - "sha256:74510234c294c6a6767089ba9c938f09a491426c24405634eb357bd91dffd734", - "sha256:95cd2df61ee00b789bdcd04a080e6d9188693b841db2bf9a87ebaed9e53147e0", - "sha256:a981500bf6947926e53c48f4d60ae080af1b4ad7fa78e363465a5b5ad4f2b65e", - "sha256:a9e6fd6f0f9e8bd77e9a4e1ef9a4f83a80674d9136a754ae3603e915da96b627", - "sha256:ad5ba062e09673b1a4b8d0facaf5a6d9719bf7b337440d10b07fe994d90a9552", - "sha256:ba90d3578bc6dddcbce461875672fd9bdb34f0b8215b68612dd3b65a956ff51c", - "sha256:c773c7dbe2f4d3ebc9e3030e94303e45d6742e6c2fc25da0c46a56ea3d83caeb", - "sha256:da238592235717419a6a7b5edc8564da410ebfd056ca4ecc41e70b1b5df86fba", - "sha256:e39aac2b3a2f839ce65aa255ce416de899c58b7d38d601d24ca35558e13b48e3", - "sha256:ec6e7046c98730cb2ba4df41387e10cb8963a3ac2918f69ae416f8aab9ca7b1b", - "sha256:f0c9719ac00615f097fe91082b785bce36dbf02a5ec4115ede0ebfd2cd9500cb", - "sha256:f7184689177eeb5a11fa1b2baf3f6f2e26bfd7a85acf4de1a3adbd0867d7c0e2" - ], - "version": "==0.20.1" - }, - "authlib": { - "hashes": [ - "sha256:270e778201590af8873cf7d5e8e8ca5b625a16f7afba6a4280b6fb4efdd791bf", - "sha256:cc52908e9e996f3de2ac2f61bf1ee6c6f1c5ce8e67c89ff2ca473008fffc92f6" - ], - "index": "pypi", - "version": "==0.14.3" - }, - "certifi": { - "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" - ], - "version": "==2020.6.20" - }, - "cffi": { - "hashes": [ - "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", - "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", - "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", - "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", - "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", - "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", - "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", - "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", - "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", - "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", - "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", - "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", - "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", - "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", - "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", - "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", - "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", - "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", - "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", - "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", - "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", - "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", - "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", - "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", - "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", - "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", - "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", - "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" - ], - "version": "==1.14.0" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" - ], - "version": "==7.1.2" - }, - "cryptography": { - "hashes": [ - "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6", - "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b", - "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5", - "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf", - "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e", - "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b", - "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae", - "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b", - "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0", - "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b", - "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d", - "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229", - "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3", - "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365", - "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55", - "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270", - "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e", - "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785", - "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0" - ], - "index": "pypi", - "version": "==2.9.2" - }, - "cycler": { - "hashes": [ - "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", - "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" - ], - "version": "==0.10.0" - }, - "databases": { - "extras": [ - "postgresql", - "sqlite" - ], - "hashes": [ - "sha256:78b758884ca585b81272af1de697e0c8a3034de92bdd08e9ac47436ef0cdca56", - "sha256:ee8dcece15a86359ef06414a6afcc15da15f5d078dc09af2e3a5f9dbfee4dce9" - ], - "index": "pypi", - "version": "==0.3.2" - }, - "decorator": { - "hashes": [ - "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", - "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" - ], - "version": "==4.4.2" - }, - "et-xmlfile": { - "hashes": [ - "sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b" - ], - "version": "==1.0.1" - }, - "fastapi": { - "hashes": [ - "sha256:92e59b77eef7d6eaa80b16d275adda06b5f33b12d777e3fc5521b2f7f4718e13", - "sha256:d7499761d5ca901cdf5b6b73018d14729593f8ab1ea22d241f82fa574fc406ad" - ], - "index": "pypi", - "version": "==0.58.1" - }, - "future": { - "hashes": [ - "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" - ], - "version": "==0.18.2" - }, - "gunicorn": { - "hashes": [ - "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", - "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" - ], - "index": "pypi", - "version": "==20.0.4" - }, - "h11": { - "hashes": [ - "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1", - "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1" - ], - "version": "==0.9.0" - }, - "h2": { - "hashes": [ - "sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5", - "sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14" - ], - "version": "==3.2.0" - }, - "hpack": { - "hashes": [ - "sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89", - "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2" - ], - "version": "==3.0.0" - }, - "hstspreload": { - "hashes": [ - "sha256:35db8d932228c2782bf0e3fdb143a54263238593f6df431458c89b006898e5f2", - "sha256:81225e82207ec316a774e5d130454327752853dfaf347b2bf4d21e524cc49efa" - ], - "version": "==2020.6.30" - }, - "httptools": { - "hashes": [ - "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be", - "sha256:3592e854424ec94bd17dc3e0c96a64e459ec4147e6d53c0a42d0ebcef9cb9c5d", - "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce", - "sha256:56b6393c6ac7abe632f2294da53f30d279130a92e8ae39d8d14ee2e1b05ad1f2", - "sha256:86c6acd66765a934e8730bf0e9dfaac6fdcf2a4334212bd4a0a1c78f16475ca6", - "sha256:96da81e1992be8ac2fd5597bf0283d832287e20cb3cfde8996d2b00356d4e17f", - "sha256:96eb359252aeed57ea5c7b3d79839aaa0382c9d3149f7d24dd7172b1bcecb009", - "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce", - "sha256:ac0aa11e99454b6a66989aa2d44bca41d4e0f968e395a0a8f164b401fefe359a", - "sha256:bc3114b9edbca5a1eb7ae7db698c669eb53eb8afbbebdde116c174925260849c", - "sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4", - "sha256:fea04e126014169384dee76a153d4573d90d0cbd1d12185da089f73c78390437" - ], - "markers": "sys_platform != 'win32' and sys_platform != 'cygwin' and platform_python_implementation != 'PyPy'", - "version": "==0.1.1" - }, - "httpx": { - "hashes": [ - "sha256:1d3893d3e4244c569764a6bae5c5a9fbbc4a6ec3825450b5696602af7a275576", - "sha256:7d2bfb726eeed717953d15dddb22da9c2fcf48a4d70ba1456aa0a7faeda33cf7" - ], - "index": "pypi", - "version": "==0.11.1" - }, - "hyperframe": { - "hashes": [ - "sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40", - "sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f" - ], - "version": "==5.2.0" - }, - "idna": { - "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" - ], - "version": "==2.10" - }, - "jdcal": { - "hashes": [ - "sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba", - "sha256:472872e096eb8df219c23f2689fc336668bdb43d194094b5cc1707e1640acfc8" - ], - "version": "==1.4.1" - }, - "kiwisolver": { - "hashes": [ - "sha256:03662cbd3e6729f341a97dd2690b271e51a67a68322affab12a5b011344b973c", - "sha256:18d749f3e56c0480dccd1714230da0f328e6e4accf188dd4e6884bdd06bf02dd", - "sha256:247800260cd38160c362d211dcaf4ed0f7816afb5efe56544748b21d6ad6d17f", - "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74", - "sha256:4eadb361baf3069f278b055e3bb53fa189cea2fd02cb2c353b7a99ebb4477ef1", - "sha256:556da0a5f60f6486ec4969abbc1dd83cf9b5c2deadc8288508e55c0f5f87d29c", - "sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec", - "sha256:60a78858580761fe611d22127868f3dc9f98871e6fdf0a15cc4203ed9ba6179b", - "sha256:7cc095a4661bdd8a5742aaf7c10ea9fac142d76ff1770a0f84394038126d8fc7", - "sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113", - "sha256:c955791d80e464da3b471ab41eb65cf5a40c15ce9b001fdc5bbc241170de58ec", - "sha256:d069ef4b20b1e6b19f790d00097a5d5d2c50871b66d10075dab78938dc2ee2cf", - "sha256:d52b989dc23cdaa92582ceb4af8d5bcc94d74b2c3e64cd6785558ec6a879793e", - "sha256:e586b28354d7b6584d8973656a7954b1c69c93f708c0c07b77884f91640b7657", - "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8", - "sha256:fccefc0d36a38c57b7bd233a9b485e2f1eb71903ca7ad7adacad6c28a56d62d2" - ], - "version": "==1.2.0" - }, - "lxml": { - "hashes": [ - "sha256:06748c7192eab0f48e3d35a7adae609a329c6257495d5e53878003660dc0fec6", - "sha256:0790ddca3f825dd914978c94c2545dbea5f56f008b050e835403714babe62a5f", - "sha256:1aa7a6197c1cdd65d974f3e4953764eee3d9c7b67e3966616b41fab7f8f516b7", - "sha256:22c6d34fdb0e65d5f782a4d1a1edb52e0a8365858dafb1c08cb1d16546cf0786", - "sha256:2754d4406438c83144f9ffd3628bbe2dcc6d62b20dbc5c1ec4bc4385e5d44b42", - "sha256:27ee0faf8077c7c1a589573b1450743011117f1aa1a91d5ae776bbc5ca6070f2", - "sha256:2b02c106709466a93ed424454ce4c970791c486d5fcdf52b0d822a7e29789626", - "sha256:2d1ddce96cf15f1254a68dba6935e6e0f1fe39247de631c115e84dd404a6f031", - "sha256:4f282737d187ae723b2633856085c31ae5d4d432968b7f3f478a48a54835f5c4", - "sha256:51bb4edeb36d24ec97eb3e6a6007be128b720114f9a875d6b370317d62ac80b9", - "sha256:7eee37c1b9815e6505847aa5e68f192e8a1b730c5c7ead39ff317fde9ce29448", - "sha256:7fd88cb91a470b383aafad554c3fe1ccf6dfb2456ff0e84b95335d582a799804", - "sha256:9144ce36ca0824b29ebc2e02ca186e54040ebb224292072250467190fb613b96", - "sha256:925baf6ff1ef2c45169f548cc85204433e061360bfa7d01e1be7ae38bef73194", - "sha256:a636346c6c0e1092ffc202d97ec1843a75937d8c98aaf6771348ad6422e44bb0", - "sha256:a87dbee7ad9dce3aaefada2081843caf08a44a8f52e03e0a4cc5819f8398f2f4", - "sha256:a9e3b8011388e7e373565daa5e92f6c9cb844790dc18e43073212bb3e76f7007", - "sha256:afb53edf1046599991fb4a7d03e601ab5f5422a5435c47ee6ba91ec3b61416a6", - "sha256:b26719890c79a1dae7d53acac5f089d66fd8cc68a81f4e4bd355e45470dc25e1", - "sha256:b7462cdab6fffcda853338e1741ce99706cdf880d921b5a769202ea7b94e8528", - "sha256:b77975465234ff49fdad871c08aa747aae06f5e5be62866595057c43f8d2f62c", - "sha256:c47a8a5d00060122ca5908909478abce7bbf62d812e3fc35c6c802df8fb01fe7", - "sha256:c79e5debbe092e3c93ca4aee44c9a7631bdd407b2871cb541b979fd350bbbc29", - "sha256:d8d40e0121ca1606aa9e78c28a3a7d88a05c06b3ca61630242cded87d8ce55fa", - "sha256:ee2be8b8f72a2772e72ab926a3bccebf47bb727bda41ae070dc91d1fb759b726", - "sha256:f95d28193c3863132b1f55c1056036bf580b5a488d908f7d22a04ace8935a3a9", - "sha256:fadd2a63a2bfd7fb604508e553d1cf68eca250b2fbdbd81213b5f6f2fbf23529" - ], - "version": "==4.5.1" - }, - "matplotlib": { - "hashes": [ - "sha256:1a05eeb5a5f9f35a0d69a01c9efc9ca48e6d6fdc907da8fbad28b33bc839905b", - "sha256:2382d2a2df7ec99f92843161ce4c7bb54189a4aeb139c16baf92fa3f5c930ade", - "sha256:28c3b07079343d7be300cf4e33f95a700a2bb0fefa7cce05e8016a0c15e3f7bc", - "sha256:2ae5f279e6d9afc25cde907c514b2819bbd8a59551c883aa96777077635127ef", - "sha256:44d5f9c556a06566e572c31e977fe750cc5fc25ad1983b99dbb0c9cb373ecc78", - "sha256:6ca6bf7e10c9387d3ce4b6c2faae28cba404d0824c3dc40072f00919dff9015e", - "sha256:7010a0cc96303b7f7b436367fe58f37ca92a89e864202c43c3a284bdde26baf1", - "sha256:9da4ad25ed5e1786b24ee19aa2caedcde6a31844521d6b546dafd418b87c3c6c", - "sha256:9fef911a7cd3f36707c3cd812030e09a7d4f557b4d3d964e6a4ca782dddb11de", - "sha256:a53494c7e29139d96eccbbc91dd281382b6219645d91422046b494b3f24a2774", - "sha256:b2425f5ab2914ccc7dde37d7811a7fdf5284bf9e6dfa72ac649da1f1a13db550", - "sha256:bcc4803720e58ace1e51009015899248b7d64eecb02e4838516a47a9934a190f", - "sha256:d3e7a68de6e74f1f2936ea64d327733876d50aa6b1c0e0c7d51de19391204426", - "sha256:e5c4a30195bf262284ec3a4e28f0f8c31898f66d7124e143f851ffa981af291b", - "sha256:e7c54447ffd658099f2fb14e0e70a19eced523ce8caae38e65cec2fa807c3050" - ], - "version": "==3.3.0rc1" - }, - "numpy": { - "hashes": [ - "sha256:13af0184177469192d80db9bd02619f6fa8b922f9f327e077d6f2a6acb1ce1c0", - "sha256:26a45798ca2a4e168d00de75d4a524abf5907949231512f372b217ede3429e98", - "sha256:26f509450db547e4dfa3ec739419b31edad646d21fb8d0ed0734188b35ff6b27", - "sha256:30a59fb41bb6b8c465ab50d60a1b298d1cd7b85274e71f38af5a75d6c475d2d2", - "sha256:33c623ef9ca5e19e05991f127c1be5aeb1ab5cdf30cb1c5cf3960752e58b599b", - "sha256:356f96c9fbec59974a592452ab6a036cd6f180822a60b529a975c9467fcd5f23", - "sha256:3c40c827d36c6d1c3cf413694d7dc843d50997ebffbc7c87d888a203ed6403a7", - "sha256:4d054f013a1983551254e2379385e359884e5af105e3efe00418977d02f634a7", - "sha256:63d971bb211ad3ca37b2adecdd5365f40f3b741a455beecba70fd0dde8b2a4cb", - "sha256:658624a11f6e1c252b2cd170d94bf28c8f9410acab9f2fd4369e11e1cd4e1aaf", - "sha256:76766cc80d6128750075378d3bb7812cf146415bd29b588616f72c943c00d598", - "sha256:7b57f26e5e6ee2f14f960db46bd58ffdca25ca06dd997729b1b179fddd35f5a3", - "sha256:7b852817800eb02e109ae4a9cef2beda8dd50d98b76b6cfb7b5c0099d27b52d4", - "sha256:8cde829f14bd38f6da7b2954be0f2837043e8b8d7a9110ec5e318ae6bf706610", - "sha256:a2e3a39f43f0ce95204beb8fe0831199542ccab1e0c6e486a0b4947256215632", - "sha256:a86c962e211f37edd61d6e11bb4df7eddc4a519a38a856e20a6498c319efa6b0", - "sha256:a8705c5073fe3fcc297fb8e0b31aa794e05af6a329e81b7ca4ffecab7f2b95ef", - "sha256:b6aaeadf1e4866ca0fdf7bb4eed25e521ae21a7947c59f78154b24fc7abbe1dd", - "sha256:be62aeff8f2f054eff7725f502f6228298891fd648dc2630e03e44bf63e8cee0", - "sha256:c2edbb783c841e36ca0fa159f0ae97a88ce8137fb3a6cd82eae77349ba4b607b", - "sha256:cbe326f6d364375a8e5a8ccb7e9cd73f4b2f6dc3b2ed205633a0db8243e2a96a", - "sha256:d34fbb98ad0d6b563b95de852a284074514331e6b9da0a9fc894fb1cdae7a79e", - "sha256:d97a86937cf9970453c3b62abb55a6475f173347b4cde7f8dcdb48c8e1b9952d", - "sha256:dd53d7c4a69e766e4900f29db5872f5824a06827d594427cf1a4aa542818b796", - "sha256:df1889701e2dfd8ba4dc9b1a010f0a60950077fb5242bb92c8b5c7f1a6f2668a", - "sha256:fa1fe75b4a9e18b66ae7f0b122543c42debcf800aaafa0212aaff3ad273c2596" - ], - "index": "pypi", - "version": "==1.19.0" - }, - "obspy": { - "hashes": [ - "sha256:0a512e87a4a27b4257846c58ff215857d68f5908dfa57cdae98ce6b7afa4da8c", - "sha256:160312d65f883a6fe92dec103b288434a32d92e0e89b9d200d43dd71909f027f", - "sha256:31cab86fbf0a2e2e490dc871fe47fe44241f12637d58bdaab5fa3698ac9abe85", - "sha256:503b7eebbd8eb4479235d2271ab410f22b54d1e742ffdd662f93698655b28d35", - "sha256:5cb684a43e3d8e10f6176a715aa4c3d2460bec33a6be21fb58675868cf4e0d63", - "sha256:67ae3aab54551a9a0ee1b3d8b504b4b4648f55fc73672d50369ad7b636baa2e9", - "sha256:86e8e891a10258b1f37aa31122c69f62ad22f8d0a86a19a4da28c9efe686f541", - "sha256:a0f2b0915beeb597762563fa0358aa1b4d6b09ffda49909c760b5cdf5bdc419e", - "sha256:afac75ceb23c139ae974b326c683bdfff5de04d2887497e53de845394e46dc81", - "sha256:c6ea8694d9af6ec1406b2643d4ffb77eec37f364445bfd23f51c50549c2c0eac", - "sha256:ce36135d01d59c1fff50b60b90b7c8d1ca8e1f7ac1b9ca53a0aadec487bc268d" - ], - "index": "pypi", - "version": "==1.2.2" - }, - "openpyxl": { - "hashes": [ - "sha256:6e62f058d19b09b95d20ebfbfb04857ad08d0833190516c1660675f699c6186f", - "sha256:d88dd1480668019684c66cfff3e52a5de4ed41e9df5dd52e008cbf27af0dbf87" - ], - "index": "pypi", - "version": "==3.0.4" - }, - "pillow": { - "hashes": [ - "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f", - "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8", - "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad", - "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f", - "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae", - "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d", - "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5", - "sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b", - "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8", - "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233", - "sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6", - "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727", - "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f", - "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38", - "sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4", - "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626", - "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d", - "sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6", - "sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63", - "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f", - "sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41", - "sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1", - "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d", - "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9", - "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a", - "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce" - ], - "version": "==7.2.0" - }, - "psycopg2-binary": { - "hashes": [ - "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac", - "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a", - "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5", - "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04", - "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1", - "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5", - "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce", - "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434", - "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9", - "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057", - "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98", - "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522", - "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505", - "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa", - "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3", - "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f", - "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4", - "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4", - "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266", - "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66", - "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38", - "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3", - "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389", - "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab", - "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb", - "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6", - "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d", - "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162", - "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e", - "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd" - ], - "version": "==2.8.5" - }, - "pycparser": { - "hashes": [ - "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", - "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" - ], - "version": "==2.20" - }, - "pycurl": { - "hashes": [ - "sha256:1957c867e2a341f5526c107c7bbc5014d6e75fdc2a14294fcb8a47663fbd2e15", - "sha256:50aee0469511a9708a1f1a50d510b5ec2013fc6f5e720c32bbcb3b9c7b0f45b1", - "sha256:667db26516e50ce4a853745906f3b149c24756d85061b9d966eb7ec43a8c48a4", - "sha256:7cc13d3421cbd31921d77e22d1f57c0e1a8d0fb461938a587689a93162ccef2f", - "sha256:a0c62dbc66b9b947832307d6cf7bdb5e4da906ce7b3efe6f74292e8f3dc5abe3", - "sha256:a6966e8d9ccda31c6d077c4f8673aaa88141cc73d50e110e93e627b816d17fd1", - "sha256:beadfa7f052626864d70eb33cec8f2aeece12dfb483c2424cc07b057f83b7d35", - "sha256:c5c379c8cc777dda397f86f0d0049480250ae84a82a9d99d668f461d368fb39c", - "sha256:ec7dd291545842295b7b56c12c90ffad2976cc7070c98d7b1517b7b6cd5994b3" - ], - "index": "pypi", - "version": "==7.43.0.5" - }, - "pydantic": { - "hashes": [ - "sha256:0a1cdf24e567d42dc762d3fed399bd211a13db2e8462af9dfa93b34c41648efb", - "sha256:2007eb062ed0e57875ce8ead12760a6e44bf5836e6a1a7ea81d71eeecf3ede0f", - "sha256:20a15a303ce1e4d831b4e79c17a4a29cb6740b12524f5bba3ea363bff65732bc", - "sha256:2a6904e9f18dea58f76f16b95cba6a2f20b72d787abd84ecd67ebc526e61dce6", - "sha256:3714a4056f5bdbecf3a41e0706ec9b228c9513eee2ad884dc2c568c4dfa540e9", - "sha256:473101121b1bd454c8effc9fe66d54812fdc128184d9015c5aaa0d4e58a6d338", - "sha256:68dece67bff2b3a5cc188258e46b49f676a722304f1c6148ae08e9291e284d98", - "sha256:70f27d2f0268f490fe3de0a9b6fca7b7492b8fd6623f9fecd25b221ebee385e3", - "sha256:8433dbb87246c0f562af75d00fa80155b74e4f6924b0db6a2078a3cd2f11c6c4", - "sha256:8be325fc9da897029ee48d1b5e40df817d97fe969f3ac3fd2434ba7e198c55d5", - "sha256:93b9f265329d9827f39f0fca68f5d72cc8321881cdc519a1304fa73b9f8a75bd", - "sha256:9be755919258d5d168aeffbe913ed6e8bd562e018df7724b68cabdee3371e331", - "sha256:ab863853cb502480b118187d670f753be65ec144e1654924bec33d63bc8b3ce2", - "sha256:b96ce81c4b5ca62ab81181212edfd057beaa41411cd9700fbcb48a6ba6564b4e", - "sha256:da8099fca5ee339d5572cfa8af12cf0856ae993406f0b1eb9bb38c8a660e7416", - "sha256:e2c753d355126ddd1eefeb167fa61c7037ecd30b98e7ebecdc0d1da463b4ea09", - "sha256:f0018613c7a0d19df3240c2a913849786f21b6539b9f23d85ce4067489dfacfa" - ], - "index": "pypi", - "version": "==1.5.1" - }, - "pyparsing": { - "hashes": [ - "sha256:1060635ca5ac864c2b7bc7b05a448df4e32d7d8c65e33cbe1514810d339672a2", - "sha256:56a551039101858c9e189ac9e66e330a03fb7079e97ba6b50193643905f450ce" - ], - "version": "==3.0.0a2" - }, - "python-dateutil": { - "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" - ], - "version": "==2.8.1" - }, - "requests": { - "hashes": [ - "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", - "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" - ], - "version": "==2.24.0" - }, - "rfc3986": { - "hashes": [ - "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d", - "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50" - ], - "version": "==1.4.0" - }, - "scipy": { - "hashes": [ - "sha256:0f62f3be924a4314cf2384c6f77d3a0e3bf4ada370530a7d0a6d5a61192bec9e", - "sha256:11d8bfcea08e971968d07495bdf1de37b3b1bb5ca78e4ddbe8d60791f0456942", - "sha256:1b23129e9838694c36475b296ac251ae0b0247455352f2bed0b246c30b61c822", - "sha256:244b4a23a8cb6707e19f5579502112954659bb983852b1c381243fe46d9b6818", - "sha256:2aec0f81a29440e0c000222ac0447748a840d328b5698dd900ece4113bca5cde", - "sha256:2e99f50d0061c385f8f46c5a99bb07ad013ec2dcf95ccba3be4e081594e7ab19", - "sha256:38cdb4eafe727b324f295ab69eaa0788a8eefe08d59360968928753ab7f68ba9", - "sha256:40969696eb13c2f1b6fc8e99ce597a47627f0167a7feb6086c2ccdfb55835cfc", - "sha256:4e1a790fdf82a67619a38f017105df4bb66dfcdaea45df793ece27fd534720c2", - "sha256:4ff72877d19b295ee7f7727615ea8238f2d59159df0bdd98f91754be4a2767f0", - "sha256:69eb6245ff472db406c3e9b3e13bd0dc6e2ff48e7e758a7bfca296ecdb1ae8b9", - "sha256:8f354e92246de33c44ddfc5fef61bce8c19d5aeeb2130b6a672b15db619453e4", - "sha256:ae24f16056c87e7f086a56a7aaf6e65e4626ac70b7e08d7f54e078693abcf778", - "sha256:d266d955c3fd12336c948abb368de230bf8dc20efad428abb908165d9c87f53f", - "sha256:eb48915142f2dbd4b84df8ca66e433946df1a13eff36015c2b7843aa39dbc30d", - "sha256:f662ad49ef930325161bd5edac39932c3f1b55547eaa0afce08367522f6314df" - ], - "index": "pypi", - "version": "==1.5.0" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "version": "==1.15.0" - }, - "sniffio": { - "hashes": [ - "sha256:20ed6d5b46f8ae136d00b9dcb807615d83ed82ceea6b2058cecb696765246da5", - "sha256:8e3810100f69fe0edd463d02ad407112542a11ffdc29f67db2bf3771afb87a21" - ], - "version": "==1.1.0" - }, - "sqlalchemy": { - "hashes": [ - "sha256:0942a3a0df3f6131580eddd26d99071b48cfe5aaf3eab2783076fbc5a1c1882e", - "sha256:0ec575db1b54909750332c2e335c2bb11257883914a03bc5a3306a4488ecc772", - "sha256:109581ccc8915001e8037b73c29590e78ce74be49ca0a3630a23831f9e3ed6c7", - "sha256:16593fd748944726540cd20f7e83afec816c2ac96b082e26ae226e8f7e9688cf", - "sha256:427273b08efc16a85aa2b39892817e78e3ed074fcb89b2a51c4979bae7e7ba98", - "sha256:50c4ee32f0e1581828843267d8de35c3298e86ceecd5e9017dc45788be70a864", - "sha256:512a85c3c8c3995cc91af3e90f38f460da5d3cade8dc3a229c8e0879037547c9", - "sha256:57aa843b783179ab72e863512e14bdcba186641daf69e4e3a5761d705dcc35b1", - "sha256:621f58cd921cd71ba6215c42954ffaa8a918eecd8c535d97befa1a8acad986dd", - "sha256:6ac2558631a81b85e7fb7a44e5035347938b0a73f5fdc27a8566777d0792a6a4", - "sha256:716754d0b5490bdcf68e1e4925edc02ac07209883314ad01a137642ddb2056f1", - "sha256:736d41cfebedecc6f159fc4ac0769dc89528a989471dc1d378ba07d29a60ba1c", - "sha256:8619b86cb68b185a778635be5b3e6018623c0761dde4df2f112896424aa27bd8", - "sha256:87fad64529cde4f1914a5b9c383628e1a8f9e3930304c09cf22c2ae118a1280e", - "sha256:89494df7f93b1836cae210c42864b292f9b31eeabca4810193761990dc689cce", - "sha256:8cac7bb373a5f1423e28de3fd5fc8063b9c8ffe8957dc1b1a59cb90453db6da1", - "sha256:8fd452dc3d49b3cc54483e033de6c006c304432e6f84b74d7b2c68afa2569ae5", - "sha256:adad60eea2c4c2a1875eb6305a0b6e61a83163f8e233586a4d6a55221ef984fe", - "sha256:c26f95e7609b821b5f08a72dab929baa0d685406b953efd7c89423a511d5c413", - "sha256:cbe1324ef52ff26ccde2cb84b8593c8bf930069dfc06c1e616f1bfd4e47f48a3", - "sha256:d05c4adae06bd0c7f696ae3ec8d993ed8ffcc4e11a76b1b35a5af8a099bd2284", - "sha256:d98bc827a1293ae767c8f2f18be3bb5151fd37ddcd7da2a5f9581baeeb7a3fa1", - "sha256:da2fb75f64792c1fc64c82313a00c728a7c301efe6a60b7a9fe35b16b4368ce7", - "sha256:e4624d7edb2576cd72bb83636cd71c8ce544d8e272f308bd80885056972ca299", - "sha256:e89e0d9e106f8a9180a4ca92a6adde60c58b1b0299e1b43bd5e0312f535fbf33", - "sha256:f11c2437fb5f812d020932119ba02d9e2bc29a6eca01a055233a8b449e3e1e7d", - "sha256:f57be5673e12763dd400fea568608700a63ce1c6bd5bdbc3cc3a2c5fdb045274", - "sha256:fc728ece3d5c772c196fd338a99798e7efac7a04f9cb6416299a3638ee9a94cd" - ], - "index": "pypi", - "version": "==1.3.18" - }, - "sqlalchemy-utc": { - "hashes": [ - "sha256:ec5395dfa4d237239c162a1b83283d88c8e2a94219512708634c55329c900278", - "sha256:fed53af37d250168b99eba8f9908a50e34e10dab3c32d38df3e65601ac951baf" - ], - "index": "pypi", - "version": "==0.10.0" - }, - "starlette": { - "hashes": [ - "sha256:04fe51d86fd9a594d9b71356ed322ccde5c9b448fc716ac74155e5821a922f8d", - "sha256:0fb4b38d22945b46acb880fedee7ee143fd6c0542992501be8c45c0ed737dd1a" - ], - "version": "==0.13.4" - }, - "typing": { - "hashes": [ - "sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", - "sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", - "sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714" - ], - "index": "pypi", - "version": "==3.7.4.1" - }, - "typing-extensions": { - "hashes": [ - "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5", - "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae", - "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392" - ], - "index": "pypi", - "version": "==3.7.4.2" - }, - "urllib3": { - "hashes": [ - "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", - "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" - ], - "version": "==1.25.9" - }, - "uvicorn": { - "hashes": [ - "sha256:50577d599775dac2301bac8bd5b540d19a9560144143c5bdab13cba92783b6e7", - "sha256:596eaa8645b6dbc24d6610e335f8ddf5f925b4c4b86fdc7146abb0bf0da65d17" - ], - "index": "pypi", - "version": "==0.11.5" - }, - "uvloop": { - "hashes": [ - "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd", - "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e", - "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09", - "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726", - "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891", - "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7", - "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5", - "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95", - "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362" - ], - "markers": "sys_platform != 'win32' and sys_platform != 'cygwin' and platform_python_implementation != 'PyPy'", - "version": "==0.14.0" - }, - "websockets": { - "hashes": [ - "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", - "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", - "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", - "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", - "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", - "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", - "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", - "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", - "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", - "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", - "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", - "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", - "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", - "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", - "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", - "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", - "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", - "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", - "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", - "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", - "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", - "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" - ], - "version": "==8.1" - } - }, - "develop": { - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "version": "==1.4.4" - }, - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "version": "==19.3.0" - }, - "beautifulsoup4": { - "hashes": [ - "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7", - "sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8", - "sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c" - ], - "version": "==4.9.1" - }, - "black": { - "hashes": [ - "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", - "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" - ], - "index": "pypi", - "version": "==19.10b0" - }, - "cfgv": { - "hashes": [ - "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53", - "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513" - ], - "version": "==3.1.0" - }, - "click": { - "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" - ], - "version": "==7.1.2" - }, - "coverage": { - "hashes": [ - "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a", - "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355", - "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65", - "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7", - "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9", - "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1", - "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0", - "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55", - "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c", - "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6", - "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef", - "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019", - "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e", - "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0", - "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf", - "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24", - "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2", - "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c", - "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4", - "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0", - "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd", - "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04", - "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e", - "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730", - "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2", - "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768", - "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796", - "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7", - "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a", - "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489", - "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052" - ], - "version": "==5.1" - }, - "distlib": { - "hashes": [ - "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", - "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" - ], - "version": "==0.3.1" - }, - "filelock": { - "hashes": [ - "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", - "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" - ], - "version": "==3.0.12" - }, - "identify": { - "hashes": [ - "sha256:c4d07f2b979e3931894170a9e0d4b8281e6905ea6d018c326f7ffefaf20db680", - "sha256:dac33eff90d57164e289fb20bf4e131baef080947ee9bf45efcd0da8d19064bf" - ], - "version": "==1.4.21" - }, - "importlib-metadata": { - "hashes": [ - "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83", - "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070" - ], - "markers": "python_version < '3.8'", - "version": "==1.7.0" - }, - "more-itertools": { - "hashes": [ - "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5", - "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2" - ], - "version": "==8.4.0" - }, - "nodeenv": { - "hashes": [ - "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc" - ], - "version": "==1.4.0" - }, - "packaging": { - "hashes": [ - "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", - "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" - ], - "version": "==20.4" - }, - "pathspec": { - "hashes": [ - "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", - "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" - ], - "version": "==0.8.0" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "version": "==0.13.1" - }, - "pre-commit": { - "hashes": [ - "sha256:1657663fdd63a321a4a739915d7d03baedd555b25054449090f97bb0cb30a915", - "sha256:e8b1315c585052e729ab7e99dcca5698266bedce9067d21dc909c23e3ceed626" - ], - "index": "pypi", - "version": "==2.6.0" - }, - "py": { - "hashes": [ - "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", - "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" - ], - "version": "==1.9.0" - }, - "pyparsing": { - "hashes": [ - "sha256:1060635ca5ac864c2b7bc7b05a448df4e32d7d8c65e33cbe1514810d339672a2", - "sha256:56a551039101858c9e189ac9e66e330a03fb7079e97ba6b50193643905f450ce" - ], - "version": "==3.0.0a2" - }, - "pytest": { - "hashes": [ - "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1", - "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8" - ], - "index": "pypi", - "version": "==5.4.3" - }, - "pytest-cov": { - "hashes": [ - "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87", - "sha256:6e6d18092dce6fad667cd7020deed816f858ad3b49d5b5e2b1cc1c97a4dba65c" - ], - "index": "pypi", - "version": "==2.10.0" - }, - "pyyaml": { - "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "version": "==5.3.1" - }, - "regex": { - "hashes": [ - "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a", - "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938", - "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29", - "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae", - "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387", - "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a", - "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf", - "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610", - "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9", - "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5", - "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3", - "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89", - "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded", - "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754", - "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f", - "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868", - "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd", - "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910", - "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3", - "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac", - "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c" - ], - "version": "==2020.6.8" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "version": "==1.15.0" - }, - "soupsieve": { - "hashes": [ - "sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55", - "sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232" - ], - "version": "==2.0.1" - }, - "toml": { - "hashes": [ - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" - ], - "version": "==0.10.1" - }, - "typed-ast": { - "hashes": [ - "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", - "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", - "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", - "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", - "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", - "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", - "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", - "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", - "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", - "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", - "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", - "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", - "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", - "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", - "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", - "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", - "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", - "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", - "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", - "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", - "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" - ], - "version": "==1.4.1" - }, - "virtualenv": { - "hashes": [ - "sha256:f332ba0b2dfbac9f6b1da9f11224f0036b05cdb4df23b228527c2a2d5504aeed", - "sha256:ffffcb3c78a671bb3d590ac3bc67c081ea2188befeeb058870cba13e7f82911b" - ], - "version": "==20.0.25" - }, - "waitress": { - "hashes": [ - "sha256:1bb436508a7487ac6cb097ae7a7fe5413aefca610550baf58f0940e51ecfb261", - "sha256:3d633e78149eb83b60a07dfabb35579c29aac2d24bb803c18b26fb2ab1a584db" - ], - "version": "==1.4.4" - }, - "wcwidth": { - "hashes": [ - "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", - "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" - ], - "version": "==0.2.5" - }, - "webob": { - "hashes": [ - "sha256:a3c89a8e9ba0aeb17382836cdb73c516d0ecf6630ec40ec28288f3ed459ce87b", - "sha256:aa3a917ed752ba3e0b242234b2a373f9c4e2a75d35291dcbe977649bd21fd108" - ], - "version": "==1.8.6" - }, - "webtest": { - "hashes": [ - "sha256:44ddfe99b5eca4cf07675e7222c81dd624d22f9a26035d2b93dc8862dc1153c6", - "sha256:aac168b5b2b4f200af4e35867cf316712210e3d5db81c1cbdff38722647bb087" - ], - "index": "pypi", - "version": "==2.0.35" - }, - "zipp": { - "hashes": [ - "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", - "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" - ], - "version": "==3.1.0" - } - } -} diff --git a/README.md b/README.md index cab1035e1ae30cb3fdaaebc43c7882ef3c1d12e4..01d7d68ca73ed92dec3fd9e8943783143292808f 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,42 @@ -Geomag Algorithms -================= -[](https://travis-ci.org/usgs/geomag-algorithms) +# Geomag Algorithms +[](https://code.usgs.gov/ghsc/geomag/geomag-algorithms/-/commits/master) +[](https://code.usgs.gov/ghsc/geomag/geomag-algorithms/-/commits/master) Geomag Algorithms is an open source library for processing -Geomagnetic timeseries data. It includes algorithms and input/output factories +Geomagnetic timeseries data. It includes algorithms and input/output factories used by the [USGS Geomagnetism Program](http://geomag.usgs.gov) to - translate between data formats, - generate derived data and indices in near-realtime, - and research and develop new algorithms. - +translate between data formats, +generate derived data and indices in near-realtime, +and research and develop new algorithms. - Accesses USGS data services. - Built using established open source python libraries - [NumPy](http://www.numpy.org/), - [SciPy](http://www.scipy.org/), and - [ObsPy](http://www.obspy.org/). + [NumPy](http://www.numpy.org/), + [SciPy](http://www.scipy.org/), and + [ObsPy](http://www.obspy.org/). - Common geomagnetic formats including - IAGA2002, - IMFV122, - IMFV283 (read only), and - PCDCP. + IAGA2002, + IMFV122, + IMFV283 (read only), and + PCDCP. - Defines command line interface, `geomag.py`. - Embeddable Python API, `import geomagio`. - ## Examples + > [More Examples in docs/example/](./docs/example/) The following examples request data from USGS for - `BOU` observatory, - `H`, `E`, `Z`, and `F` component, - `minute` interval, - and `variation` type data - for the day `2016-07-04`, +`BOU` observatory, +`H`, `E`, `Z`, and `F` component, +`minute` interval, +and `variation` type data +for the day `2016-07-04`, then write `IAGA2002` formatted output to the console. ### Command Line Interface Example + ``` geomag.py \ --input edge \ @@ -49,9 +49,11 @@ geomag.py \ --starttime 2016-07-04T00:00:00Z \ --endtime 2016-07-04T23:59:00Z ``` + [Command Line Interface documentation](./docs/cli.md) ### Python API Example + ``` import sys import geomagio @@ -72,66 +74,55 @@ output_factory.write_file( fh = sys.stdout, timeseries = timeseries) ``` -[Python API documentation](./docs/api.md) +[Python API documentation](./docs/api.md) ## Install + > [More Install options in docs/install.md](./docs/install.md). ### Docker + Docker is the simplest install option. 1. Create and start a new container - named `geomagio`, - listening on local port `8000`, - from the image `usgs/geomag-algorithms` on docker hub - - ``` - docker run -d --name geomagio -p 8000:8000 usgs/geomag-algorithms - ``` + named `geomagio`, + listening on local port `8000`, + from the image `usgs/geomag-algorithms` on docker hub -2. Find the token, (the token improves security) + ``` + docker run -d --name geomagio -p 8000:8000 usgs/geomag-algorithms + ``` - ``` - docker logs geomagio - ``` +2. Use the running container - This outputs a URL to copy/paste into your browser address bar: - ``` - Copy/paste this URL into your browser when you connect for the first time, - to login with a token: - http://localhost:8000/?token=TOKEN - ``` +- Run the `geomag.py` command line interface: -3. Use the running container + > To work with files outside the container, + > use a volume mount when starting the container - - Run the `geomag.py` command line interface: + ``` + docker exec -it geomagio geomag.py + ``` - ``` - docker exec -it geomagio geomag.py - ``` - - - Run python interactively in a web browser (using the token found in step 2): - - ``` - http://localhost:8000/?token=TOKEN - ``` - - > In the top right corner, choose "New" then "Python 2" +- Or, to run an interactive python prompt: + ``` + docker exec -it geomagio python + ``` ## Algorithms -[Algorithms described in docs/algorithms/](./docs/algorithms) +[Algorithms described in docs/algorithms/](./docs/algorithms) ## Developing -[Developing described in docs/develop.md](./docs/develop.md). +[Developing described in docs/develop.md](./docs/develop.md). ## License -[License described in LICENSE.md](./LICENSE.md) +[License described in LICENSE.md](./LICENSE.md) ## Problems or Questions? @@ -139,7 +130,6 @@ Docker is the simplest install option. - [Join the USGS geomag-data mailing list](https://geohazards.usgs.gov/mailman/listinfo/geomag-data) - [Email jmfee at usgs.gov](mailto:jmfee@usgs.gov) - ## Additional Links - [USGS Geomagnetism Program Home Page](http://geomag.usgs.gov/) diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000000000000000000000000000000000000..f01c9e86fc024525a788247f6068fafd7e19f9ed --- /dev/null +++ b/alembic.ini @@ -0,0 +1,87 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations/ + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date +# within the migration file as well as the filename. +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; this defaults +# to migrations//versions. When using multiple version +# directories, initial revisions must be specified with --version-path +# version_locations = %(here)s/bar %(here)s/bat migrations//versions + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks=black +# black.type=console_scripts +# black.entrypoint=black +# black.options=-l 79 + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/bin/geomag_webservice.py b/bin/geomag_webservice.py deleted file mode 100755 index 1f027d3e2f1bf1091c34e61382173c5cd2c0f38e..0000000000000000000000000000000000000000 --- a/bin/geomag_webservice.py +++ /dev/null @@ -1,37 +0,0 @@ -#! /usr/bin/env python - -from __future__ import absolute_import, print_function - -import os -import sys -from wsgiref.simple_server import make_server - -# ensure geomag is on the path before importing -try: - import geomagio # noqa (tells linter to ignore this line.) -except ImportError: - path = os.path - script_dir = path.dirname(path.abspath(__file__)) - sys.path.append(path.normpath(path.join(script_dir, ".."))) - import geomagio - - -if __name__ == "__main__": - # read configuration from environment - edge_host = os.getenv("EDGE_HOST", "cwbpub.cr.usgs.gov") - edge_port = int(os.getenv("EDGE_PORT", "2060")) - factory_type = os.getenv("GEOMAG_FACTORY_TYPE", "edge") - webservice_host = os.getenv("GEOMAG_WEBSERVICE_HOST", "") - webservice_port = int(os.getenv("GEOMAG_WEBSERVICE_PORT", "7981")) - version = os.getenv("GEOMAG_VERSION", None) - - # configure factory - if factory_type == "edge": - factory = geomagio.edge.EdgeFactory(host=edge_host, port=edge_port) - else: - raise "Unknown factory type '%s'" % factory_type - - print("Starting webservice on %s:%d" % (webservice_host, webservice_port)) - app = geomagio.WebService(factory, version) - httpd = make_server(webservice_host, webservice_port, app) - httpd.serve_forever() diff --git a/bin/make_cal.py b/bin/make_cal.py deleted file mode 100755 index 6a9bee3aa6a7af8458638426f1e1b40c4edb5683..0000000000000000000000000000000000000000 --- a/bin/make_cal.py +++ /dev/null @@ -1,169 +0,0 @@ -#! /usr/bin/env python - -""" -Usage: - python make_cal.py OBSERVATORY YEAR -""" -from __future__ import print_function - -from datetime import datetime -import itertools -import json -import os -import sys -import urllib2 - - -############################################################################ -# CONFIGURATION - -# format used to output files -# "{OBSERVATORY}" and "{YEAR}" are replaced with argument values -FILENAME_FORMAT = "./{OBSERVATORY}{YEAR}WebAbsMaster.cal" - -# url for observation web service -SERVICE_URL = "https://geomag.usgs.gov/baselines/observation.json.php" - -############################################################################ -# DO NOT EDIT BELOW THIS LINE - - -# parse observatory and year arguments -if len(sys.argv) != 3: - cmd = sys.argv[0] - print("Usage: {} OBSERVATORY YEAR".format(cmd), file=sys.stderr) - print("Example: {} BOU 2016".format(cmd), file=sys.stderr) - sys.exit(1) - -OBSERVATORY = sys.argv[1] -YEAR = int(sys.argv[2]) - - -# request observations from service -url = ( - SERVICE_URL - + "?" - + "&".join( - [ - "observatory=" + OBSERVATORY, - "starttime=" + str(YEAR) + "-01-01", - "endtime=" + str(YEAR + 1) + "-01-01", - ] - ) -) - -try: - print(f"Loading data from web service\n\t{url}", file=sys.stderr) - response = urllib2.urlopen( - url, - # allow environment certificate bundle override - cafile=os.environ.get("SSL_CERT_FILE"), - ) - data = response.read() - observations = json.loads(data) -except Exception as e: - print(f"Error loading data ({e})", file=sys.stderr) - sys.exit(1) - - -# extract all valid cal values -cals = [] -for observation in observations["data"]: - for reading in observation["readings"]: - for channel in ["H", "D", "Z"]: - cal = reading[channel] - if ( - not cal["absolute"] - or not cal["baseline"] - or not cal["end"] - or not cal["start"] - or not cal["valid"] - ): - # not a valid cal value - continue - # convert D values from degrees to minutes - multiplier = 60 if channel == "D" else 1 - absolute = cal["absolute"] * multiplier - baseline = cal["baseline"] * multiplier - end = datetime.utcfromtimestamp(cal["end"]) - start = datetime.utcfromtimestamp(cal["start"]) - cals.append( - { - "absolute": absolute, - "baseline": baseline, - "channel": channel, - "end": end, - "start": start, - } - ) - - -# format calfile -CAL_HEADER_FORMAT = "--{date:%Y %m %d} ({channel})" -CAL_LINE_FORMAT = "{start:%H%M}-{end:%H%M} c{baseline:9.1f}{absolute:9.1f}" - -calfile = [] -# output by date in order -cals = sorted(cals, key=lambda c: c["start"]) -# group by date -for date, cals in itertools.groupby(cals, key=lambda c: c["start"].date()): - # convert group to list so it can be reused - cals = list(cals) - # within each day, order by H, then D, then Z - for channel in ["H", "D", "Z"]: - channel_cals = [c for c in cals if c["channel"] == channel] - if not channel_cals: - # no matching values - continue - # add channel header - calfile.append(CAL_HEADER_FORMAT.format(channel=channel, date=date)) - calfile.extend([CAL_LINE_FORMAT.format(**c) for c in channel_cals]) -calfile.append("") - - -# write calfile -filename = FILENAME_FORMAT.format(OBSERVATORY=OBSERVATORY, YEAR=YEAR) -print("Writing cal file to {}".format(filename), file=sys.stderr) -with open(filename, "wb", -1) as f: - f.write(os.linesep.join(calfile)) - - -""" -CAL format example: -- ordered by date -- within date, order by H, then D, then Z component -- component values order by start time -- D component values in minutes. - - ---2015 03 30 (H) -2140-2143 c 175.0 12531.3 -2152-2156 c 174.9 12533.3 -2205-2210 c 174.8 12533.1 -2220-2223 c 174.9 12520.7 ---2015 03 30 (D) -2133-2137 c 1128.3 1118.5 -2145-2149 c 1128.4 1116.4 -2159-2203 c 1128.3 1113.1 -2212-2216 c 1128.4 1113.5 ---2015 03 30 (Z) -2140-2143 c -52.9 55403.4 -2152-2156 c -52.8 55403.8 -2205-2210 c -52.8 55404.0 -2220-2223 c -52.8 55410.5 ---2015 07 27 (H) -2146-2151 c 173.5 12542.5 -2204-2210 c 173.8 12542.5 -2225-2229 c 173.8 12547.2 -2240-2246 c 173.6 12538.7 ---2015 07 27 (D) -2137-2142 c 1127.8 1109.2 -2154-2158 c 1128.3 1106.3 -2213-2220 c 1128.0 1106.3 -2232-2237 c 1128.3 1104.7 ---2015 07 27 (Z) -2146-2151 c -53.9 55382.7 -2204-2210 c -54.0 55382.5 -2225-2229 c -54.1 55383.7 -2240-2246 c -54.1 55389.0 -""" diff --git a/code.json b/code.json new file mode 100644 index 0000000000000000000000000000000000000000..2d425280da575c507156903990bfd344f4d3d75d --- /dev/null +++ b/code.json @@ -0,0 +1,40 @@ +[ + { + "name": "geomag-algorithms", + "organization": "U.S. Geological Survey", + "description": "Library for processing Geomagnetic timeseries data.", + "version": "1.4.2", + "status": "Development", + + "permissions": { + "usageType": "openSource", + "licenses": [ + { + "name": "Public Domain, CC0-1.0", + "URL": "https://code.usgs.gov/ghsc/geomag/geomag-algorithms/raw/master/LICENSE.md" + } + ] + }, + + "homepageURL": "https://code.usgs.gov/ghsc/geomag/geomag-algorithms", + "downloadURL": "https://code.usgs.gov/ghsc/geomag/geomag-algorithms/-/archive/master/geomag-algorithms-master.zip", + "disclaimerURL": "https://code.usgs.gov/ghsc/geomag/geomag-algorithms/raw/master/DISCLAIMER.md", + "repositoryURL": "https://code.usgs.gov/ghsc/geomag/geomag-algorithms.git", + "vcs": "git", + + "laborHours": 0, + + "tags": ["usgs", "geomagnetism", "timeseries", "processing"], + + "languages": ["Shell", "Python"], + + "contact": { + "name": "HazDev Team", + "email": "gs-haz_dev_team_group@usgs.gov" + }, + + "date": { + "metadataLastUpdated": "2021-08-26" + } + } +] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index b79af220e6c9d2b15e486c0bdc78d5500306bd10..7c05ad6ed9246666d9d9260d5a89bf58d8ace9b0 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,22 +1,16 @@ #! /bin/bash + # Environment variable to determine whether to start webservice export WEBSERVICE=${WEBSERVICE:-false} -# add geomagio to notebook path -export PYTHONPATH=/geomag-algorithms - -if [ $WEBSERVICE = 'false' ]; then - # run jupyter notebook server - exec jupyter notebook \ - --ip='*' \ - --notebook-dir=/data \ - --no-browser \ - --port=8000 +if [ "${WEBSERVICE}" = "false" ]; then + # run arguments as command, or bash prompt if no arguments + exec "${@:-/bin/bash}" else # run gunicorn server for web service - exec pipenv run gunicorn \ + exec gunicorn \ --access-logfile - \ --bind 0.0.0.0:8000 \ --threads 2 \ diff --git a/docs/algorithms/Adjusted_usage.md b/docs/algorithms/Adjusted_usage.md index 4db49e5c509c398b8aa61f6675dd28781a47326e..b3d9db952d05dd4fce24e6b05be167736bdcaddd 100644 --- a/docs/algorithms/Adjusted_usage.md +++ b/docs/algorithms/Adjusted_usage.md @@ -8,7 +8,7 @@ the [Adjusted Algorithm](./Adjusted.md). `geomag.py --algorithm sqdist` -### Example +### Command Line Example This example uses a state file to produce X, Y, Z and F channels from raw H, E, Z and F channels using the EDGE channel naming @@ -27,7 +27,6 @@ contained in the statefile. --outchannels X Y Z F \ --output-iaga-stdout - ### Statefile Example Content This is the content of /etc/adjusted/adjbou_state_.json: @@ -41,6 +40,56 @@ This is the content of /etc/adjusted/adjbou_state_.json: "M12": -0.15473074200902157, "M14": -1276.1646811919759, "M31": -0.006725053082782385} +### API Example + +This example uses an AdjustedMatrix object to produce X, Y, Z and F channels +from raw H, E, Z and F channels using the EDGE channel naming +convention. Absolutes were used to compute the transform matrix and pier correction +contained in the object. + +```python +from geomagio.algorithm import AdjustedAlgorithm +from geomagio.adjusted.AdjustedMatrix import AdjustedMatrix +from geomagio.iaga2002 import IAGA2002Factory + +with open("etc/adjusted/BOU201601vmin.min") as f: + raw = IAGA2002Factory().parse_string(f.read()) + +a = AdjustedAlgorithm( + matrix=AdjustedMatrix( + matrix=[ + [ + 0.9834275767090617, + -0.15473074200902157, + 0.027384986324932026, + -1276.164681191976, + ], + [ + 0.16680172992706568, + 0.987916201012128, + -0.0049868332295851525, + -0.8458192581350419, + ], + [ + -0.006725053082782385, + -0.011809351484171948, + 0.9961869012493976, + 905.3800885796844, + ], + [0, 0, 0, 1], + ], + pier_correction=-22, + ) + ) +# definition can also use statefiles +# a = adj(statefile="etc/adjusted/adjbou_state_.json") + +result = adjusted.process(raw) +``` + + + + ### Library Notes > Note: this library internally represents data gaps as NaN, and diff --git a/docs/develop.md b/docs/develop.md index b9cf979e60df9f8996b50d8ee846479c7372dd0f..d7f5ba63d64f44c6216b607416fda4872bb5ba9e 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -22,16 +22,16 @@ https://guides.github.com/activities/forking/ ## Install Dependencies -- Using `pipenv` - https://pipenv.kennethreitz.org/en/latest/ +- Using `poetry` + https://github.com/python-poetry/poetry > `pyenv` is also useful for installing specific/multiple versions of python > > - https://github.com/pyenv/pyenv > - https://github.com/pyenv-win/pyenv-win - pipenv install --dev - pipenv shell + poetry install + poetry shell - Or, using Miniconda/Anaconda https://conda.io/miniconda.html diff --git a/docs/elements.md b/docs/elements.md new file mode 100644 index 0000000000000000000000000000000000000000..d4340c308c55d41c0caed68645d75983e2d853dc --- /dev/null +++ b/docs/elements.md @@ -0,0 +1,129 @@ +# Geomagnetic Elements + + + +## Obsrio +Elements generated by observatories running the Obsrio data acquisition system. + +### Acquired elements + +- 10Hz volt + bin + - U, V, W + +- 1Hz engineering units + - F, T1-4 + +### Derived variation + +- 1Hz engineering units + + - U, V, W + + uses instrument metadata from `geomagio.Metadata.get_instrument` to convert volt+bin to engineering units. + + uses `geomagio.algorithm.FilterAlgorithm` to filter from 10Hz to 1Hz. + +- 1 minute engineering units + + - T1-4 + + uses `geomagio.algorithm.FilterAlgorithm` to filter from 1Hz to 1 minute. + + +## PCDCP +Elements generated by observatories running the PCDCP data acquisition system. + +### Acquired elements + +- 1Hz legacy engineering units + + - H, E, Z, F + + - H, E, Z volt + bin + +- 1 minute legacy engineering units + + - T1-4 + + - H, E, Z, F + + - instrument battery voltage + +### Derived variation + +- 1Hz engineering units + + - U, V, W, F copied from legacy engineering units + +- 1 minute engineering units + + - T1-4 copied from legacy engineering units + + +## Common + +Derived elements calculated for all observatories. + +### Derived variation + +- 1Hz engineering units + - D, X, Y + + uses `geomagio.algorithm.XYZAlgorithm` to rotate coordinates from observatory (U,V) to observatory D and geographic (X,Y). + + - G + + uses `geomagio.algorithm.DeltaFAlgorithm` to generate delta F from U,V,W,F. + +- 1 minute/hour/day engineering units + + - U, V, W, F, T1-4 + + uses `geomagio.algorithm.FilterAlgorithm` to filter from 1Hz to 1 minute or 1 minute to 1 hour/day. + + - D, X, Y + + uses `geomagio.algorithm.XYZAlgorithm` to rotate coordinates from observatory (U,V) to observatory D and geographic (X,Y). + + - G + + uses `geomagio.algorithm.DeltaFAlgorithm` to generate delta F from U,V,W,F. + + - H_SQ, H_SV, H_Dist + + uses `geomagio.algorithm.SqDistAlgorithm` to produce solar quiet(SQ), secular variation(SV) and + magnetic disturbance(Dist) at 1 minute interval. + +### Derived adjusted + +- 1Hz engineering units + - X, Y, Z, F + + uses `geomagio.algorithm.AdjustedAlgorithm` with per-observatory matrices stored in filesystem. + + - H, D + + uses `geomagio.algorithm.XYZAlgorithm` to rotate coordinates from geographic (X,Y) to magnetic (H,D). + + - G + + uses `geomagio.algorithm.DeltaFAlgorithm` to generate delta F from X,Y,Z,F. + +- 1 minute/hour/day engineering units + + - X, Y, Z, F + + uses `geomagio.algorithm.FilterAlgorithm` to filter from 1Hz to 1 minute or 1 minute to 1 hour/day. + + - H, D + + uses `geomagio.algorithm.XYZAlgorithm` to rotate coordinates from geographic (X,Y) to magnetic (H,D). + + - G + + uses `geomagio.algorithm.DeltaFAlgorithm` to generate delta F from X,Y,Z,F. + + - H_SQ, H_SV, H_Dist + + uses `geomagio.algorithm.SqDistAlgorithm` to produce solar quiet(SQ), secular variation(SV) and + magnetic disturbance(Dist) at 1 minute interval. \ No newline at end of file diff --git a/docs/images/residual_null_validation.png b/docs/images/residual_null_validation.png new file mode 100644 index 0000000000000000000000000000000000000000..3c6c3e31bc6bbe139c3ccd82d6ed4c3c6543673a Binary files /dev/null and b/docs/images/residual_null_validation.png differ diff --git a/docs/install_docker.md b/docs/install_docker.md index 02beede915bb904b4b3084e664176f4b9b1fd17a..f87b9193f6b6c49a44fb39c46c71dfe6416403c5 100644 --- a/docs/install_docker.md +++ b/docs/install_docker.md @@ -25,13 +25,8 @@ The following command creates and starts a container in the background. - Run an interactive python prompt - docker exec -it geomagio python -- Use the Jupyter Notebook server - - Check the output for a URL like this, that can be opened in a web browser: `http://127.0.0.1:8000/?token=...` - - Use the `geomag.py` command line interface docker exec -it geomagio geomag.py \ diff --git a/docs/metadata_webservice.md b/docs/metadata_webservice.md new file mode 100644 index 0000000000000000000000000000000000000000..9c0b5141becaac55e0c93bcad125ba2381376895 --- /dev/null +++ b/docs/metadata_webservice.md @@ -0,0 +1,72 @@ +# Running the Metadata Webservice Locally + +## Run mysql in a container (for local development) + +``` +docker run --rm --name mysql-db -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql:5.7 +``` + +This exposes port 3306 so python can connect locally. When running the webservice in a container, container links should be used so the container can access the database container. + +## Set up schema in database + +> This is only needed the first time the database is created. Volume mounts can make this more persistent. + +``` +export DATABASE_URL=mysql://root:password@localhost/geomag_operations +``` + +### Create mysql database +``` +docker exec -it mysql-db mysql -uroot -ppassword +``` +> Inside mysql container: +``` +CREATE DATABASE geomag_operations; +exit +``` + +``` +poetry run python create_db.py +``` + +### Add some testing data (depends on DATABASE_URL environment set above). + +``` +poetry run python test_metadata.py +``` + +## Set up OpenID application in code.usgs.gov. + +- Under your account, go to settings +- Applications -> Add New Application: + + Callback URLs for local development: + + ``` + http://127.0.0.1:8000/ws/secure/authorize + http://127.0.0.1:4200/ws/secure/authorize + ``` + + Confidential: `Yes` + + Scopes: `openid`, `profile`, `email` + +## Start webservice + +- Export variables used for authentication: + +``` +export DATABASE_URL=mysql://root:password@localhost/geomag_operations +export OPENID_CLIENT_ID={Application ID} +export OPENID_CLIENT_SECRET={Secret} +export OPENID_METADATA_URL=https://code.usgs.gov/.well-known/openid-configuration +export SECRET_KEY=changeme +export SECRET_SALT=salt +``` + +- Run app + +``` +poetry run uvicorn geomagio.api:app +``` diff --git a/docs/overview.drawio b/docs/overview.drawio new file mode 100644 index 0000000000000000000000000000000000000000..c818995414b53076f4db586475835ab7ba72d9eb --- /dev/null +++ b/docs/overview.drawio @@ -0,0 +1,95 @@ +<mxfile host="65bd71144e" modified="2021-01-21T17:16:38.931Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.1 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36" etag="W-LIp3zateXR4xu9K7Xh" version="13.10.0" type="embed"> + <diagram id="Vd_ur7joLU8uI5hJ4403" name="Page-1"> + <mxGraphModel dx="832" dy="510" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0"> + <root> + <mxCell id="0"/> + <mxCell id="1" parent="0"/> + <mxCell id="31" value="geomag-web-absolutes" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fontStyle=2" parent="1" vertex="1"> + <mxGeometry x="720" y="380" width="200" height="120" as="geometry"/> + </mxCell> + <mxCell id="19" value="Web Applications<br>(geomag-plots)" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;strokeWidth=1;" parent="1" vertex="1"> + <mxGeometry x="720" y="80" width="200" height="260" as="geometry"/> + </mxCell> + <mxCell id="18" value="Web Services<br>(geomag-algorithms)" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;strokeWidth=1;" parent="1" vertex="1"> + <mxGeometry x="480" y="80" width="200" height="260" as="geometry"/> + </mxCell> + <mxCell id="17" value="VM" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;" parent="1" vertex="1"> + <mxGeometry x="240" y="80" width="200" height="260" as="geometry"/> + </mxCell> + <mxCell id="13" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=none;startFill=0;entryPerimeter=0;" parent="1" source="3" target="37" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="340" y="180" as="targetPoint"/> + </mxGeometry> + </mxCell> + <mxCell id="27" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=none;startFill=0;dashed=1;" parent="1" source="3" target="5" edge="1"> + <mxGeometry relative="1" as="geometry"/> + </mxCell> + <mxCell id="3" value="Data Processing<br>(geomag-algorithms)" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1"> + <mxGeometry x="280" y="240" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="12" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4" target="37" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="400" y="150" as="targetPoint"/> + </mxGeometry> + </mxCell> + <mxCell id="4" value="Data Web Service" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1"> + <mxGeometry x="520" y="140" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="29" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;startArrow=none;startFill=0;" parent="1" source="5" target="36" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="580" y="360" as="targetPoint"/> + </mxGeometry> + </mxCell> + <mxCell id="5" value="Metadata Web Service" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;" parent="1" vertex="1"> + <mxGeometry x="520" y="240" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="10" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="6" target="4" edge="1"> + <mxGeometry relative="1" as="geometry"/> + </mxCell> + <mxCell id="11" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;" parent="1" source="6" target="5" edge="1"> + <mxGeometry relative="1" as="geometry"/> + </mxCell> + <mxCell id="6" value="Plots" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1"> + <mxGeometry x="760" y="140" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;" parent="1" source="7" target="5" edge="1"> + <mxGeometry relative="1" as="geometry"/> + </mxCell> + <mxCell id="9" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="7" target="4" edge="1"> + <mxGeometry relative="1" as="geometry"/> + </mxCell> + <mxCell id="7" value="Operations" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;" parent="1" vertex="1"> + <mxGeometry x="760" y="240" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="16" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=classic;startFill=1;endArrow=none;endFill=0;" parent="1" source="15" target="37" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="280" y="150" as="targetPoint"/> + </mxGeometry> + </mxCell> + <mxCell id="15" value="Observatories" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1"> + <mxGeometry x="40" y="140" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="32" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;startArrow=none;startFill=0;" parent="1" source="30" target="36" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="640" y="450" as="targetPoint"/> + </mxGeometry> + </mxCell> + <mxCell id="30" value="Web Absolutes" style="rounded=0;whiteSpace=wrap;html=1;fontStyle=2" parent="1" vertex="1"> + <mxGeometry x="760" y="420" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="33" value="MagProc" style="rounded=0;whiteSpace=wrap;html=1;fontStyle=2" vertex="1" parent="1"> + <mxGeometry x="280" y="440" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="34" value="Residual Spreadsheets" style="rounded=0;whiteSpace=wrap;html=1;fontStyle=2" vertex="1" parent="1"> + <mxGeometry x="280" y="380" width="120" height="60" as="geometry"/> + </mxCell> + <mxCell id="36" value="MySQL" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;strokeWidth=1;" vertex="1" parent="1"> + <mxGeometry x="550" y="410" width="60" height="80" as="geometry"/> + </mxCell> + <mxCell id="37" value="Edge CWB<br>Timeseries Data" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;strokeWidth=1;" vertex="1" parent="1"> + <mxGeometry x="280" y="130" width="120" height="80" as="geometry"/> + </mxCell> + </root> + </mxGraphModel> + </diagram> +</mxfile> \ No newline at end of file diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000000000000000000000000000000000000..98e84289c01e0ee3fa6eeb64c2d5869d950e06a5 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,95 @@ +# Overview + +## Background + +Geomag observatories produce raw time series data based on measurements of +Earth’s magnetic field. These raw time series are adjusted in near-real time to +eliminate spikes and apply baseline corrections, and later adjusted again to +produce definitive data. Additionally, other algorithms combine time series to +produce derived time series. + +## Architecture + + + +Observatories use a new ObsRIO system which records vector magnetometer data at +10Hz, generates MiniSEED formatted output, and supports SEEDLink for data +transfer. Data are reported using double precision floating point, which mirrors +the ObsRIO internal processing format, and MiniSEED blocks are generated every +few seconds to decrease latency. + +Observatory data is acquired and stored using Edge Continuous Waveform Buffer +(CWB) software. Edge CWB acquires MiniSEED data using SEEDLink and other +protocols and provides TCP query services for data access. This software and +format is widely used by the Seismic community for high volume timeseries data +and supported by another team. + +The [Geomag Algorithms](https://code.usgs.gov/ghsc/geomag/geomag-plots) Python +library includes algorithms for data processing as well as web services for data +access. Geomag Algorithms uses ObsPy for timeseries data, and FastAPI for web +services. + +The [Geomag Plots](https://code.usgs.gov/ghsc/geomag/geomag-plots) Angular +project includes web applications that access web services to visualize and +eventually manage data and metadata. + +Web Absolutes is a legacy application used to enter regular calibration +measurements recorded at observatories. This supports the "null" method, but +does not support the "residual" method used at high latitudes; which use +spreadsheet macros. + +MagProc definitive data processing software was developed to process one-minute +data. It provides tools for reviewers to identify and remove spikes from data, +and requires manual effort to prepare and process data. A separate version of +MagProc was created to support one second data, but requires outputs from +processing one-minute data; instead of filtering processed one second data to +generate one minute data. + +The Operations web application is under development to replace the legacy Web +Absolutes and MagProc applications. It will let users enter absolute +measurements, flag timeseries data, and manage other metadata used in data +processing. Reviewers confirm metadata is correct, and can manually process +quasi-definitive and definitive data using a new Adjusted algorithm. + +## Adjusted Algorithm + +The Adjusted algorithm applies a baseline correction transformation matrix that +both scales and rotates from raw data to corrected data. These transformation +matrices are computed manually, updated infrequently, and require additional +effort to deploy. + +The initial plan is to create these transformation matrix "keyframe"s more +regularly, and build a web service to access multiple versions. The algorithm is +being updated to support a weighted least squares regression that is more +reliable. New matrices will be uploaded to the web service and stored for +reproducibility. An existing matrix can be "closed" in preparation for changes +at observatories, so adjusted data is not produced until a stable baseline +correction can be calculated. + +Near-real time adjusted data will use the web service to look up the current +transformation matrix and process data as usual. When a new matrix is deployed +in real time, existing data is not recomputed and the change may result in small +steps or similar data artifacts. + +Quasi-definitive data is processed at least one to two weeks after collection, +providing time for data to be manually flagged and additional observations to be +collected. Matrices used during Quasi-definitive can incorporate additional +observations when calculating keyframes. Each keyframe will use a consistent +window of observations - for example, 7 weeks before keyframe time and 1 week +after. A new algorithm to interpolate between keyframes is being developed to +prevent steps in data at keyframes. + +Definitive data is processed at least one to two months after the end of the +calendar year, providing time for additional observations to be collected. +Matrices used during Definitive processing can incorporate a larger window of +observations, but processing of data once the matrices are calculated is +otherwise identical to Quasi-definitive. + +## Flagging + +Flagging information will be tracked in the Metadata web service database, +allowing automatic flags to be used for near-real time adjusted data. Manual +flags can be added and automatic flags reviewed using the Operations web +application before Quasi-definitive processing. In addition to flagging data +spikes, absolute observations and data offsets should be flagged/reviewed in a +similar manner for use when computing baseline transformation matrices. diff --git a/docs/overview.png b/docs/overview.png new file mode 100644 index 0000000000000000000000000000000000000000..37b8612f030e946ca4d0c1943f1ef11a0efe0ec9 Binary files /dev/null and b/docs/overview.png differ diff --git a/docs/residual/Residual Examples.ipynb b/docs/residual/Residual Examples.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..efccdbb8791f765b8eb73337d888ecd8008b3ab8 --- /dev/null +++ b/docs/residual/Residual Examples.ipynb @@ -0,0 +1,189 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "cd ../.." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Gathering Reading from Spreadsheet:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# import factory from residual package\n", + "from geomagio.residual import SpreadsheetAbsolutesFactory" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# initialize factory\n", + "saf = SpreadsheetAbsolutesFactory()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# get reading via spreadsheet parsing\n", + "reading = saf.parse_spreadsheet(path=\"etc/residual/DED-20140952332.xlsm\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Gathering Readings from Web Absolutes" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# import web absolutes factory from residual package\n", + "from geomagio.residual import WebAbsolutesFactory\n", + "# import UTCDateTime from obspy for time interpretation\n", + "from obspy.core import UTCDateTime" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# initialize factory\n", + "waf = WebAbsolutesFactory()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# specify required observatory, starttime, and endtime\n", + "observatory = \"BOU\"\n", + "starttime = UTCDateTime(\"2020-01-01T00:00:00Z\")\n", + "endtime = UTCDateTime(\"2020-07-01T00:00:00Z\")\n", + "\n", + "readings = waf.get_readings(observatory=observatory, starttime=starttime, endtime=endtime)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It should be noted that the get_readings tool has the optional parameter include_measurements that is turned off by default. To get readings with field measurements, users should change this parameter to True." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "readings = waf.get_readings(observatory=observatory, starttime=starttime, endtime=endtime, include_measurements=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calculation from Reading" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# import calculation method from residual package\n", + "from geomagio.residual.Calculation import calculate" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# perform calculation with reading from spreadsheet factory\n", + "output_reading = calculate(reading)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Method Validation\n", + "\n", + "The residual method utilizes fundamental theories from the null method with slight improvements via small angle approximations. Resulting calculations should only vary slightly from original values." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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Ue9RSG51d3P3V+aHR3X3G9x9wN273X0fd399Qa4L3H2lu59aoqy73X1BVNZb3P2JaH7ZspqNmXHmmWdueX7BBRewbNmyMV+zYcMGDjjgAAAymQzHH398LasoIiIiIiItIgjClhedneFjye4hsVYSqY2658CQ+KZNm8bVV1/N448/3uiqiIiIiIhIi0unw24jy5ePMbpIrJVEakMBjCqq9njIU6ZMYenSpXzta18btWzJkiVcddVVW57vuOOO1dmoiIiIiIi0rTRZeuglzRgXNek09PQoeCF115BhVFtRrcZD/uAHP8grX/lKPvnJT06+MBERERERaVvZbJiyIgjKXKvU6qJGpErUAqNKYiW8mYCddtqJxYsXc9FFF1WnQBERERERaTv52MTZZ4ePJVuN1+qiRqRKFMCoklrmsjnjjDO49NJL+fvftw7EMmXKFEZGRgAYGRkhl8tVb4MiIiIiItJSYsUmlKBTEk4BjCqpZS6bXXfdlVNOOYVLL710y7zZs2ezbt06AK699lqGhoaqt0EREREREWkpsWITStApCaccGFWUTtfuf/zMM8/kG9/4xpbn73nPezjxxBOZP38+xxxzDDvssENtNiwiIiIiIk0vnYY7L8wysDpD16KAeeUuXGp5USMySebuja5Dwy1YsMDXrl27zbz77ruPuXPnNqhGzUufm4iIiIhIY4yZpFMJOqWJmNk6d19QPF8tMERERERERJpcxfhEqSQYCmBIk1EODBERERERkSZXMUmnEnRKC1ALDBERERERkSaXj0/kW2CMik/kE3SW7WMiknwKYIiIiIiIiDS5WEk6laBTmpwCGCIiIlLRmInhRESk8bJZ5p0RJcG4JQXzlKRTWo8CGCIiIjImJa4XEWkCStIpbUBJPBOss7OTAw88kAMOOIA3v/nNPPnkk+MuY+3atXzkIx8puWz27Nk8/vjjE6rbsmXLuOCCCyb0WhERaS4VE8OJiEjjKUmntAEFMBJsu+224+677+bee+9l11135eKLLx53GQsWLOCiiy6qQe1ERKRd6JxYRKQJ5JN0Ll+upnLSshTAqKZsFnp7w8cqS6fTPPzwwwCsX7+eY445hoMPPpjDDz+c3/3udwBceeWVHHDAAcyfP58jjjgCgEwmw/HHHw/AwMAAb3zjG9l///1597vfjbsDsGHDBg444IAt27rgggtYtmwZAN/+9rd59atfzfz581m0aBHPPvts1d+biEizqeHhPpF10TmxiEiTSKehp0cHamlZCmBUS76D8Nlnh49VPJMcHh6mr6+PE044AYClS5fy9a9/nXXr1nHBBRfwgQ98AIDzzjuPn/3sZ9xzzz1ce+21o8o599xzOeyww/jNb37DW9/6Vh588MGK2z7ppJP45S9/yT333MPcuXO59NJLq/a+RESaUTYLPUGWZ87qpSfIlj7c1ynCUcOfnlF0TiwiIiKNpiSe1VKDpDnPPfccBx54IA8//DBz587lDW94A8888wy33347b3vb27asNzg4CMDChQtZsmQJp5xyCieddNKo8m6++WauvvpqAI477jh22WWXinW49957+exnP8uTTz7JM888w9FHHz2p9yQi0uzuX5Xlhlw3KXLkcimuWtVHuvB4X8eMl3F/evpXFAyrt1QRCBEREWlOaoFRLTXoIJzPgbFx40bcnYsvvpiRkRF23nln7r777i3TfffdB8All1zC5z//eR566CEOPvhgBgYGYm1nypQpjIyMbHm+efPmLX8vWbKEb3zjG/T393POOedssyyRktSuWxpOu4PUwpFkSJFjCsNMJceRZLZdoY4ZL+P89PSvyLL3e7s57Maz2fu93fSv0D+EiIiINCcFMKqlhh2Et99+ey666CK+8pWvsP322zNnzhyuvPJKANyde+65BwhzYxxyyCGcd9557L777jz00EPblHPEEUfw/e9/H4Cf/OQnPPHEEwDssccePProowwMDDA4OMh111235TVPP/00e+65J0NDQ1x++eVVe081Uc+21JJ42h2kVmYtDrBpKYatk45pKWYtDrZdoY4ZL+P89Ays3jbgMrA6U7P6iIiIiNSSupBUUzpds2bCBx10EK985Su54ooruPzyy3n/+9/P5z//eYaGhjj11FOZP38+n/jEJ7j//vtxd7q7u5k/fz5r1qzZUsY555zD29/+dvbff38OPfRQ9tprLwCmTp3K5z73OV7zmtcwY8YM9t133y2vWb58OYcccgi77747hxxyCE8//XRN3l9VaOxrKaDdQWomnabzpr5wpwqC0TtWPqpQbnn1qzPmJroWBeRuTOHkGCJF16KgpvUREZmMbLZuh08RaUKWH4minS1YsMDXrl27zbz77ruPuXPnNqhGzauhn1sd+51L8ml3kLYR42w/Tg4M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c+dy6aWXVu19gbqQtKxKLZM1GsckBAE2LQW5HJZKhZk8RUSq7LqBNNd39IXd1ToCjhtIM6/RlRIZL2W9FhEpLZMJu4kOD4ePmcykj5PPPfccBx54IA8//DBz587lDW94A8888wy33347b3vb27asNzg4CMDChQtZsmQJp5xyCieddNKo8m6++WauvvpqAI477jh22WWXinW49957+exnP8uTTz7JM888w9FHHz2p91RMAYw2pSHSJiGdDvui64RMRGooCGD5tDR35NKkUvDloNE1Ehkn5dsSESkvCMIcV/lcV1W4KZrPgfHss89y9NFHc/HFF7NkyRJ23nnnLbkxCl1yySXceeedXH/99Rx88MGsW7cu1namTJnCyMjIluebN2/e8veSJUu45pprmD9/PitXriRT5S6w6kLSxiY7Gkdbq/ThiYhMUj5Wuny58ndKc1K+LRGRMdTwh3777bfnoosu4itf+Qrbb789c+bM4corrwTA3bnnnnuAMDfGIYccwnnnncfuu+/OQw89tE05RxxxBN///vcB+MlPfsITTzwBwB577MGjjz7KwMAAg4ODXHfddVte8/TTT7PnnnsyNDTE5ZdfXrX3lKcWGFJWpdE4RESktip1BxRJslijoomItLMa/tAfdNBBvPKVr+SKK67g8ssv5/3vfz+f//znGRoa4tRTT2X+/Pl84hOf4P7778fd6e7uZv78+axZs2ZLGeeccw5vf/vb2X///Tn00EPZa6+9AJg6dSqf+9zneM1rXsOMGTPYd999t7xm+fLlHHLIIey+++4ccsghPP3001V9X5bPJNrOFixY4GvXrm10NRJJOTBERERkIrJZ6AmyW/NtadhxEWlh9913H3Pnzm10NZpSqc/OzNa5+6jxXNUCQ8Y0b2kaFLgQERGRcYqTb0tERGQ8FMAQERERkZpQNygREakmJfEUERERERERmSSlZxi/8X5mCmCIiIhIdWSz0NsbPoqIiLSR6dOnMzAwoCDGOLg7AwMDTJ8+PfZr1IVEREREJi+bhe7urePZa+xXERFpIzNnzmTTpk089thjja5KU5k+fTozZ86Mvb4CGCIiIjJ5mQw+mMNGhsPHTEYBDBERaRtTp05lzpw5ja5Gy1MXEhEREZm0/q6A50ZSDNHJcyMp+ruCRldJREREWoxaYIiIiMikXTeQ5vqOPg4fyXBLR8Bx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d3PIIYewyy67cNJJJxHMpQHLli1j1113XXusCy+8kFmzZgHw05/+lD333JNJkyYxdepU3nzzzVjPSwEMEVGTd5EYTZyRYullndx9yPksvayTiTNS9c6SiIiIJE1XF0yZAmefHTzGGMTo7e2ls7OTI444AoAZM2bwwx/+kMWLF3PhhRfyhS98AYDzzjuP2267jYceeoibbrppg3TOPfdc9ttvPx599FE+9rGP8cwzz1Q89tFHH83999/PQw89xIQJE7jiiitiOy9I6DSqIlJb3fMzTAibvHuuybsqXSIDNnFGSt8hERERKS2TgWwWenuDx0wGUoP77fDWW2+x22678dxzzzFhwgQ++MEP8vrrr3PPPffw8Y9/fO1+q1evBmDy5MlMnz6dY489lqOPPnqD9O68806uv/56AD7ykY+w+eabV8zDI488wllnncXKlSt5/fXXOfTQQwd1ToXUAkNE1ORdRERERKSW0mloa4OWluAxnR50krkxMJ5++mncnUsvvZS+vj5Gjx7Ngw8+uHZ5/PHHAfjJT37Ct7/9bZ599ln22GMPuru7Ix2ntbWVvr6+tc9XrVq19u/p06fzox/9iCVLlnDOOeesty0OCmCIiJq8i4iIiIjUUioFnZ1w/vnB4yBbX+QbNWoUl1xyCd///vcZNWoU48eP57rrrgPA3XnooYeAYGyMvffem/POO4+tttqKZ599dr10DjjgAH75y18C8Lvf/Y5XXnkFgK233poXX3yR7u5uVq9ezc0337z2Na+99hrbbLMNPT09XHPNNbGdU466kIgIoCbvIiIiIiI1lUrFGrjI9/73v5/3ve99XHvttVxzzTV8/vOf59vf/jY9PT0cd9xxTJo0ia9+9as8+eSTuDtTpkxh0qRJLFy4cG0a55xzDscffzy77LIL++67L9tuuy0Aw4cP51vf+hZ77bUXY8aMYaeddlr7mvPPP5+9996brbbair333pvXXnst1vOy3EiiQ1lHR4cvWrSo3tkQERERERGRBvT4448zYcKEemejIRV778xssbt3FO6rLiQiIiIiIiIikngKYIiIiIiIiIhI4imAISIiIiIiIiKJpwCGiIiIiPTbkjldZA6dzZI5XfXOiohIImh8yf7r73umWUhEREREhpAlc7ronp+hfWp6wNNmL5nTxfanTGECWbIL2liCpuAWkaFtxIgRdHd3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\n", + "text/plain": [ + "<Figure size 1080x1008 with 6 Axes>" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "%run 'docs/residual/validation_plots.py'" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "GEOMAG", + "language": "python", + "name": "geomag" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/docs/residual/residuals.md b/docs/residual/residuals.md new file mode 100644 index 0000000000000000000000000000000000000000..614cc695a3fa2ae4d58eb1ce962bfe9279ecb1ec --- /dev/null +++ b/docs/residual/residuals.md @@ -0,0 +1,48 @@ +# Tools: + + +## Spreadsheet Absolutes Factory: +Tool for gathering a single reading from a formatted spreadsheet or several readings from a directory of spreadsheets. Outputs include measurements and absolutes. Format examples can be found at https://code.usgs.gov/ghsc/geomag/geomag-algorithms/-/tree/master/etc/residual. + +### Usage: +```python +from geomagio.residual import SpreadsheetAbsolutesFactory + +saf = SpreadsheetAbsolutesFactory() + +reading = saf.parse_spreadsheet(path="../../etc/residual/DED-20140952332.xlsm") +``` + +## Web Absolutes Factory: +Tool for gathering several readings from webservice queries. Output can optionally include measurements, but will always return absolutes as long as they are available. + +### Usage: +```python +from geomagio.residual import WebAbsolutesFactory +from obspy.core import UTCDateTime + +waf = WebAbsolutesFactory() + +starttime = UTCDateTime("2020-01-01") +endtime = UTCDateTime("2020-10-01") + +readings = waf.get_readings(observatory="BOU", starttime=starttime, endtime=endtime, include_measurements=True) +``` + +## Calculation: +Implements residual method for a reading to derive new absolute values. See https://gi.copernicus.org/articles/6/419/2017/gi-6-419-2017.pdf for theoretical information. + +### Usage: +```python +from geomagio.residual.Calculation import calculate +# assumes that a reading is gathered from either of the package's factories +output_reading = calculate(reading) +``` + +NOTE: Input readings require measurements. Current measurements are made using the null method and will not include residual values. The residual method is backwards compatible with the null method, but resulting absolute values will not be identical. Residuals utilize small angle approximations, which account for small changes in output values. + +# Validation: +Backwards compatibility allows for legacy null measurements to have their absolute values recalculated by the residual method. The following figure serves to display the change in values when recalculated with the residual method. Data is gathered from the Boulder magnetic observatory and includes readings from a six month time span.(01/2020 - 07/2020) + + + diff --git a/docs/residual/validation_plots.py b/docs/residual/validation_plots.py new file mode 100644 index 0000000000000000000000000000000000000000..9122a1b386b67868be7a0811e9d27c6e9937d428 --- /dev/null +++ b/docs/residual/validation_plots.py @@ -0,0 +1,107 @@ +import matplotlib.pyplot as plt +import numpy as np +from obspy.core import UTCDateTime + +from geomagio.residual.Calculation import calculate +from geomagio.residual import WebAbsolutesFactory + +observatory = "BOU" +starttime = UTCDateTime("2020-01-01") +endtime = UTCDateTime("2020-07-01") + +readings = WebAbsolutesFactory().get_readings( + observatory=observatory, + starttime=starttime, + endtime=endtime, + include_measurements=True, +) + +# define orignal absolute and baseline arrays for each type +H_o_abs = [reading.absolutes[1].absolute for reading in readings] +H_o_baseline = [reading.absolutes[1].baseline for reading in readings] +D_o_abs = [reading.absolutes[0].absolute for reading in readings] +D_o_baseline = [reading.absolutes[0].baseline for reading in readings] +Z_o_abs = [reading.absolutes[2].absolute for reading in readings] +Z_o_baseline = [reading.absolutes[2].baseline for reading in readings] + +# define calculated absolute and baseline arrays for each type +H_c_abs = [] +H_c_baseline = [] +D_c_abs = [] +D_c_baseline = [] +Z_c_abs = [] +Z_c_baseline = [] +# loop through 6 months of readings +for reading in readings: + # skip calculation when not enough information exists within reading + try: + reading = calculate(reading) + except TypeError: + D_c_abs.append(np.nan) + D_c_baseline.append(np.nan) + + H_c_abs.append(np.nan) + H_c_baseline.append(np.nan) + + Z_c_abs.append(np.nan) + Z_c_baseline.append(np.nan) + continue + # append calculated values from null method calculations + D_c_abs.append(reading.absolutes[0].absolute) + D_c_baseline.append(reading.absolutes[0].baseline) + + H_c_abs.append(reading.absolutes[1].absolute) + H_c_baseline.append(reading.absolutes[1].baseline) + + Z_c_abs.append(reading.absolutes[2].absolute) + Z_c_baseline.append(reading.absolutes[2].baseline) + +t = np.arange(1, len(readings) + 1) + +plt.figure(figsize=(15, 14)) + +plt.subplot(6, 1, 1) +plt.plot(t, H_o_abs, "b.", label="Null") +plt.plot(t, H_c_abs, "r.", label="Residual") +plt.legend() +plt.ylabel("nT", fontsize=15) +plt.title("H Absolute(2020-01-01 - 2020-07-01)", fontsize=20) + +plt.subplot(6, 1, 2) +plt.plot(t, D_o_abs, "b.", label="Null") +plt.plot(t, D_c_abs, "r.", label="Residual") +plt.legend() +plt.ylabel("deg", fontsize=15) +plt.title("D Absolute(2020-01-01 - 2020-07-01)", fontsize=20) + +plt.subplot(6, 1, 3) +plt.plot(t, Z_o_abs, "b.", label="Null") +plt.plot(t, Z_c_abs, "r.", label="Residual") +plt.legend() +plt.ylabel("nT", fontsize=15) +plt.title("Z Absolute(2020-01-01 - 2020-07-01)", fontsize=20) + +plt.subplot(6, 1, 4) +plt.plot(t, H_o_baseline, "b.", label="Null") +plt.plot(t, H_c_baseline, "r.", label="Residual") +plt.legend() +plt.ylabel("nT", fontsize=15) +plt.title("H Baseline(2020-01-01 - 2020-07-01)", fontsize=20) + +plt.subplot(6, 1, 5) +plt.plot(t, D_o_baseline, "b.", label="Null") +plt.plot(t, D_c_baseline, "r.", label="Residual") +plt.legend() +plt.ylabel("deg", fontsize=15) +plt.title("D Baseline(2020-01-01 - 2020-07-01)", fontsize=20) + +plt.subplot(6, 1, 6) +plt.plot(t, Z_o_baseline, "b.", label="Null") +plt.plot(t, Z_c_baseline, "r.", label="Residual") +plt.legend() +plt.ylabel("nT", fontsize=15) +plt.xlabel("reading #", fontsize=15) +plt.title("Z Baseline(2020-01-01 - 2020-07-01)", fontsize=20) + +plt.tight_layout() +plt.show() diff --git a/etc/adjusted/BOU201601adj.min b/etc/adjusted/BOU201601adj.min index 7d9262b9ad3cd6ace9916009e5be409dad16a35d..14a73fb7de70ae4dcc28e24f91a7dba8daa97fbe 100644 --- a/etc/adjusted/BOU201601adj.min +++ b/etc/adjusted/BOU201601adj.min @@ -9,7 +9,7 @@ Sensor Orientation HDZF | Digital Sampling 100.0 second | Data Interval Type filtered 1-minute (00:15-01:45) | - Data Type variation | + Data Type adjusted | # DECBAS 5527 (Baseline declination value in | # tenths of minutes East (0-216,000)). | # Vector 1-minute values are computed from 1-second values using | diff --git a/etc/adjusted/BOU_expected.json b/etc/adjusted/BOU_expected.json new file mode 100644 index 0000000000000000000000000000000000000000..ebe0a6c9bab156e2e39fc6ca9f158ee514421096 --- /dev/null +++ b/etc/adjusted/BOU_expected.json @@ -0,0 +1,1128 @@ +{ + "short_causal": [ + [ + [ + 0.9866182703892618, + -0.1630471972592658, + 0, + -27.369267454349902 + ], + [ + 0.16304719725926584, + 0.9866182703892618, + 0, + -297.949848870806 + ], + [ + 0, + 0, + 1, + 577.0756736370693 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9827596367862292, + -0.1848877938204662, + 0, + 50.92414208813067 + ], + [ + 0.18488779382046627, + 0.9827596367862291, + 0, + -749.5591946991251 + ], + [ + 0, + 0, + 1, + 576.7942725719031 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.98275963678623, + -0.18488779382046625, + 0, + 50.92414208811485 + ], + [ + 0.18488779382046644, + 0.9827596367862292, + 0, + -749.5591946991285 + ], + [ + 0, + 0, + 1, + 576.7942725719031 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9845500718568356, + -0.17510327240431828, + 0, + 13.185197065480011 + ], + [ + 0.1751032724043181, + 0.9845500718568351, + 0, + -549.3879444331118 + ], + [ + 0, + 0, + 1, + 577.3916751331093 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9842903665237368, + -0.17655728353304653, + 0, + 18.546917935074664 + ], + [ + 0.1765572835330465, + 0.9842903665237366, + 0, + -578.6071590432969 + ], + [ + 0, + 0, + 1, + 577.5584135956137 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9898393903663861, + -0.14218994788346112, + 0, + -92.33456674373527 + ], + [ + 0.1421899478834611, + 0.9898393903663864, + 0, + 135.51382413798652 + ], + [ + 0, + 0, + 1, + 577.1018879032138 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9898393903663859, + -0.14218994788346095, + 0, + -92.33456674373062 + ], + [ + 0.14218994788346098, + 0.9898393903663858, + 0, + 135.51382413798888 + ], + [ + 0, + 0, + 1, + 577.1018879032138 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9917263086784076, + -0.12837027956307018, + 0, + -130.31394542470372 + ], + [ + 0.12837027956307048, + 0.9917263086784077, + 0, + 423.70009715679157 + ], + [ + 0, + 0, + 1, + 577.279558981481 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9901787602781031, + -0.1398070910008474, + 0, + -98.59480536365572 + ], + [ + 0.1398070910008473, + 0.9901787602781035, + 0, + 185.20481576509107 + ], + [ + 0, + 0, + 1, + 577.8077879580178 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9901787602781033, + -0.13980709100084734, + 0, + -98.59480536365794 + ], + [ + 0.13980709100084726, + 0.9901787602781036, + 0, + 185.20481576509152 + ], + [ + 0, + 0, + 1, + 577.8077879580178 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9909339795671953, + -0.13434972325658456, + 0, + -114.2470610962384 + ], + [ + 0.1343497232565845, + 0.9909339795671954, + 0, + 299.8344159990737 + ], + [ + 0, + 0, + 1, + 578.1774527764944 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9911420902561328, + -0.13280571118255682, + 0, + -117.70015122361568 + ], + [ + 0.13280571118255713, + 0.9911420902561328, + 0, + 331.4835690346877 + ], + [ + 0, + 0, + 1, + 578.0939187358629 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9918269815246076, + -0.12759011999283956, + 0, + -131.26902819780057 + ], + [ + 0.12759011999283976, + 0.991826981524607, + 0, + 438.6342317530304 + ], + [ + 0, + 0, + 1, + 578.4413328140095 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9882317252453721, + -0.1529642350961639, + 0, + -58.67791780164316 + ], + [ + 0.15296423509616386, + 0.9882317252453724, + 0, + -88.67011298731849 + ], + [ + 0, + 0, + 1, + 578.2320125532879 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + ], + "short_acausal": [ + [ + [ + 0.9838554746643297, + -0.1789648149023343, + 0, + 28.440806951355448 + ], + [ + 0.1789648149023341, + 0.98385547466433, + 0, + -629.4703071224347 + ], + [ + 0, + 0, + 1, + 577.1726804427047 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9859896708845204, + -0.16680638149973595, + 0, + -15.91108372686219 + ], + [ + 0.16680638149973573, + 0.9859896708845206, + 0, + -374.28004343506353 + ], + [ + 0, + 0, + 1, + 577.3291257179811 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.986475897403721, + -0.16390638743357125, + 0, + -25.78338689734143 + ], + [ + 0.16390638743357164, + 0.9864758974037213, + 0, + -315.6885389998515 + ], + [ + 0, + 0, + 1, + 577.5130166196902 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9866982765418331, + -0.16256232979807117, + 0, + -30.174969224451427 + ], + [ + 0.1625623297980714, + 0.9866982765418335, + 0, + -287.56482777368416 + ], + [ + 0, + 0, + 1, + 577.4953339759405 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9855673830884218, + -0.16928358866777007, + 0, + -6.229255550986623 + ], + [ + 0.16928358866777, + 0.9855673830884222, + 0, + -428.55573420894586 + ], + [ + 0, + 0, + 1, + 577.2154615593286 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9867547830475863, + -0.16221898203573248, + 0, + -29.971178394145745 + ], + [ + 0.16221898203573226, + 0.986754783047587, + 0, + -281.5426094237025 + ], + [ + 0, + 0, + 1, + 577.2569675551619 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9859205738774717, + -0.16721429964305656, + 0, + -13.158526470192948 + ], + [ + 0.16721429964305667, + 0.9859205738774711, + 0, + -385.66126961504006 + ], + [ + 0, + 0, + 1, + 577.2775308585723 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9865050752550115, + -0.16373068281816594, + 0, + -24.817765650108022 + ], + [ + 0.1637306828181656, + 0.986505075255012, + 0, + -312.4831422794003 + ], + [ + 0, + 0, + 1, + 577.7473165139924 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9870273818015541, + -0.16055200893782312, + 0, + -34.99487598363016 + ], + [ + 0.16055200893782337, + 0.9870273818015532, + 0, + -246.2347852844213 + ], + [ + 0, + 0, + 1, + 577.91470877669 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9854996596990534, + -0.1696774019516189, + 0, + -3.3368697335275526 + ], + [ + 0.16967740195161893, + 0.9854996596990527, + 0, + -435.9389912727183 + ], + [ + 0, + 0, + 1, + 577.8888623614705 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.985828834373858, + -0.16775431236507998, + 0, + -9.731191065586854 + ], + [ + 0.16775431236508015, + 0.985828834373858, + 0, + -396.21675317582185 + ], + [ + 0, + 0, + 1, + 577.8635880922781 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9859937247190143, + -0.1667824175826852, + 0, + -13.311612057482176 + ], + [ + 0.16678241758268525, + 0.9859937247190144, + 0, + -375.89847874792457 + ], + [ + 0, + 0, + 1, + 578.0649084873531 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9870977176975387, + -0.16011900485674452, + 0, + -35.804431796351345 + ], + [ + 0.16011900485674474, + 0.9870977176975372, + 0, + -237.78517319830164 + ], + [ + 0, + 0, + 1, + 578.2345073147621 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.986897989167513, + -0.16134546469337777, + 0, + -31.43791838321752 + ], + [ + 0.161345464693378, + 0.986897989167512, + 0, + -262.8201674298129 + ], + [ + 0, + 0, + 1, + 577.993559989513 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + ], + "inf_weekly": [ + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + ], + "inf_one_interval": [ + [ + [ + 0.9822948009118504, + -0.18734173080642613, + 0, + 60.45505321883426 + ], + [ + 0.18734173080642616, + 0.9822948009118508, + 0, + -804.0052430449001 + ], + [ + 0, + 0, + 1, + 577.4055827661293 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + ] +} \ No newline at end of file diff --git a/etc/adjusted/CMO_expected.json b/etc/adjusted/CMO_expected.json new file mode 100644 index 0000000000000000000000000000000000000000..4b53c32650c9a31e7e10969db32890a2b64d7c34 --- /dev/null +++ b/etc/adjusted/CMO_expected.json @@ -0,0 +1,3390 @@ +{ + "short_causal": [ + [ + [ + 0.9520106076060315, + -0.3060650306807265, + 0, + 99.65088924353279 + ], + [ + 0.3060650306807261, + 0.9520106076060315, + 0, + 257.97535719664864 + ], + [ + 0, + 0, + 1, + -53.21366487278599 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9552016612734489, + -0.29595571678959665, + 0, + 60.26698791955369 + ], + [ + 0.29595571678959687, + 0.9552016612734487, + 0, + 383.44028395677265 + ], + [ + 0, + 0, + 1, + -52.87006023415485 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9511585942240501, + -0.3087026540762643, + 0, + 110.129690010723 + ], + [ + 0.30870265407626424, + 0.9511585942240496, + 0, + 226.10380069007348 + ], + [ + 0, + 0, + 1, + -52.67229878128798 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9515742808541782, + -0.3074189129133971, + 0, + 104.67647657144059 + ], + [ + 0.3074189129133969, + 0.9515742808541776, + 0, + 242.10601732468 + ], + [ + 0, + 0, + 1, + -52.43740175872636 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.948621387123097, + -0.31641343823018786, + 0, + 141.25969063215254 + ], + [ + 0.3164134382301883, + 0.948621387123098, + 0, + 130.54895106153558 + ], + [ + 0, + 0, + 1, + -52.63979785302694 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9486213871230978, + -0.3164134382301881, + 0, + 141.2596906321415 + ], + [ + 0.3164134382301883, + 0.948621387123098, + 0, + 130.54895106153558 + ], + [ + 0, + 0, + 1, + -52.63979785302694 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9486213871230977, + -0.3164134382301883, + 0, + 141.25969063214285 + ], + [ + 0.31641343823018836, + 0.9486213871230981, + 0, + 130.5489510615348 + ], + [ + 0, + 0, + 1, + -52.63979785302694 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9488775268856051, + -0.31564448192777933, + 0, + 138.5736682077212 + ], + [ + 0.3156444819277799, + 0.9488775268856054, + 0, + 140.4491045887207 + ], + [ + 0, + 0, + 1, + -52.69650104874719 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9472688339429288, + -0.3204399417057814, + 0, + 158.3824370711891 + ], + [ + 0.3204399417057814, + 0.9472688339429285, + 0, + 80.74101015130873 + ], + [ + 0, + 0, + 1, + -52.89012982270929 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9479982729343638, + -0.31827546954401664, + 0, + 149.15611760297898 + ], + [ + 0.3182754695440166, + 0.9479982729343636, + 0, + 107.61021805698002 + ], + [ + 0, + 0, + 1, + -53.27139701240504 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.947998272934362, + -0.31827546954401625, + 0, + 149.15611760300106 + ], + [ + 0.3182754695440165, + 0.9479982729343631, + 0, + 107.61021805698138 + ], + [ + 0, + 0, + 1, + -53.27139701240504 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9492837362131247, + -0.31442071840330643, + 0, + 132.79138861861873 + ], + [ + 0.3144207184033068, + 0.9492837362131243, + 0, + 154.88628731934745 + ], + [ + 0, + 0, + 1, + -53.192828164424775 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9501482731754195, + -0.3117984268429974, + 0, + 122.3123260735645 + ], + [ + 0.31179842684299697, + 0.9501482731754196, + 0, + 186.83919994577647 + ], + [ + 0, + 0, + 1, + -53.49385810165857 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9501482731754192, + -0.3117984268429975, + 0, + 122.3123260735684 + ], + [ + 0.3117984268429973, + 0.9501482731754196, + 0, + 186.83919994577232 + ], + [ + 0, + 0, + 1, + -53.49385810165857 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9501482731754188, + -0.31179842684299736, + 0, + 122.31232607357265 + ], + [ + 0.3117984268429972, + 0.9501482731754195, + 0, + 186.8391999457737 + ], + [ + 0, + 0, + 1, + -53.49385810165857 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.950148273175419, + -0.31179842684299747, + 0, + 122.31232607357146 + ], + [ + 0.3117984268429971, + 0.9501482731754194, + 0, + 186.83919994577505 + ], + [ + 0, + 0, + 1, + -53.49385810165857 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9510420200470575, + -0.30906160567888885, + 0, + 110.78631608336227 + ], + [ + 0.309061605678889, + 0.9510420200470572, + 0, + 220.1228506549593 + ], + [ + 0, + 0, + 1, + -53.95450488765145 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9510420200470578, + -0.3090616056788887, + 0, + 110.78631608335698 + ], + [ + 0.30906160567888896, + 0.9510420200470572, + 0, + 220.12285065495976 + ], + [ + 0, + 0, + 1, + -53.95450488765145 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9511834337542936, + -0.3086261093157717, + 0, + 109.63477209051936 + ], + [ + 0.30862610931577167, + 0.9511834337542937, + 0, + 225.47903259111393 + ], + [ + 0, + 0, + 1, + -53.93337886228512 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9522437645884091, + -0.3053388491536775, + 0, + 96.44732813933707 + ], + [ + 0.30533884915367704, + 0.9522437645884101, + 0, + 266.16861124419376 + ], + [ + 0, + 0, + 1, + -53.83314985198863 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9522437645884108, + -0.30533884915367737, + 0, + 96.44732813931674 + ], + [ + 0.3053388491536774, + 0.9522437645884105, + 0, + 266.16861124418904 + ], + [ + 0, + 0, + 1, + -53.83314985198863 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9522474112520676, + -0.3053274762672623, + 0, + 96.20104651341549 + ], + [ + 0.3053274762672624, + 0.9522474112520676, + 0, + 266.29550094623545 + ], + [ + 0, + 0, + 1, + -53.6427500942824 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9492667038170053, + -0.3144721371193278, + 0, + 132.19674477317423 + ], + [ + 0.31447213711932726, + 0.9492667038170053, + 0, + 153.0047793960055 + ], + [ + 0, + 0, + 1, + -53.91983054901134 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9492667038170048, + -0.31447213711932775, + 0, + 132.5637586266592 + ], + [ + 0.3144721371193272, + 0.9492667038170051, + 0, + 152.8317764409154 + ], + [ + 0, + 0, + 1, + -54.11103438825747 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9492667038170055, + -0.31447213711932775, + 0, + 132.56375862665092 + ], + [ + 0.31447213711932753, + 0.9492667038170054, + 0, + 152.83177644091128 + ], + [ + 0, + 0, + 1, + -54.11103438825747 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9492667038170054, + -0.31447213711932787, + 0, + 132.563758626652 + ], + [ + 0.31447213711932775, + 0.9492667038170055, + 0, + 152.83177644090856 + ], + [ + 0, + 0, + 1, + -54.11103438825747 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9506383093674282, + -0.3103011517268955, + 0, + 115.50464948380066 + ], + [ + 0.3103011517268955, + 0.9506383093674282, + 0, + 206.01153307196805 + ], + [ + 0, + 0, + 1, + -54.400000000001455 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9505955192448176, + -0.3104322128769406, + 0, + 115.82708505308234 + ], + [ + 0.3104322128769401, + 0.9505955192448181, + 0, + 203.62370891387909 + ], + [ + 0, + 0, + 1, + -54.340851617960595 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9513703014531312, + -0.3080495893731687, + 0, + 106.4440517325126 + ], + [ + 0.30804958937316823, + 0.9513703014531312, + 0, + 232.5937707310631 + ], + [ + 0, + 0, + 1, + -54.53673610924302 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9513703014531311, + -0.30804958937316834, + 0, + 106.44405173251397 + ], + [ + 0.30804958937316806, + 0.9513703014531314, + 0, + 232.593770731065 + ], + [ + 0, + 0, + 1, + -54.53673610924302 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9516721259049419, + -0.3071158816726485, + 0, + 102.66812181019742 + ], + [ + 0.3071158816726488, + 0.9516721259049421, + 0, + 243.73830390207047 + ], + [ + 0, + 0, + 1, + -54.724043387716776 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9516926525939859, + -0.30705226753538545, + 0, + 102.3414222822089 + ], + [ + 0.3070522675353849, + 0.9516926525939859, + 0, + 244.52300143937606 + ], + [ + 0, + 0, + 1, + -54.793475102073316 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9524913061011091, + -0.304565775821586, + 0, + 92.42316654793483 + ], + [ + 0.30456577582158645, + 0.9524913061011087, + 0, + 275.13536834049125 + ], + [ + 0, + 0, + 1, + -54.62052782371143 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9524913061011095, + -0.3045657758215856, + 0, + 92.42316654792937 + ], + [ + 0.3045657758215862, + 0.952491306101109, + 0, + 275.13536834049404 + ], + [ + 0, + 0, + 1, + -54.62052782371143 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.950296018730672, + -0.3113478388950792, + 0, + 119.12979753766503 + ], + [ + 0.31134783889507983, + 0.9502960187306717, + 0, + 191.44318333401347 + ], + [ + 0, + 0, + 1, + -54.74202678734332 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9500606048354571, + -0.3120654532941546, + 0, + 122.06007435929664 + ], + [ + 0.31206545329415464, + 0.950060604835457, + 0, + 182.27466723645855 + ], + [ + 0, + 0, + 1, + -54.532197240180246 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9500606048354564, + -0.3120654532941546, + 0, + 122.06007435930393 + ], + [ + 0.3120654532941544, + 0.9500606048354565, + 0, + 182.27466723646128 + ], + [ + 0, + 0, + 1, + -54.532197240180246 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9502846023268284, + -0.3113826818892501, + 0, + 119.35310099476098 + ], + [ + 0.31138268188925017, + 0.9502846023268285, + 0, + 191.37261392106782 + ], + [ + 0, + 0, + 1, + -54.82128108711125 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9495509561919879, + -0.3136127892718685, + 0, + 128.4839608908748 + ], + [ + 0.3136127892718684, + 0.9495509561919879, + 0, + 163.37843197593557 + ], + [ + 0, + 0, + 1, + -54.51315932421658 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9491116933242957, + -0.31493966659518874, + 0, + 133.59958201705274 + ], + [ + 0.3149396665951888, + 0.949111693324296, + 0, + 146.82017888401936 + ], + [ + 0, + 0, + 1, + -54.40109499783231 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9493328378160123, + -0.31427243443260483, + 0, + 131.16521523308737 + ], + [ + 0.3142724344326044, + 0.9493328378160117, + 0, + 155.21045447759514 + ], + [ + 0, + 0, + 1, + -54.242767938657366 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9491380180978055, + -0.3148603223675052, + 0, + 133.39158086952148 + ], + [ + 0.3148603223675051, + 0.9491380180978056, + 0, + 148.03594551276845 + ], + [ + 0, + 0, + 1, + -54.07640075887624 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9491380180978056, + -0.3148603223675052, + 0, + 133.39158086952054 + ], + [ + 0.314860322367505, + 0.9491380180978055, + 0, + 148.0359455127698 + ], + [ + 0, + 0, + 1, + -54.07640075887624 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + ], + "short_acausal": [ + [ + [ + 0.9497629660016135, + -0.31297013980860444, + 0, + 127.27992032316135 + ], + [ + 0.3129701398086044, + 0.9497629660016137, + 0, + 172.83563567435738 + ], + [ + 0, + 0, + 1, + -52.882802662550375 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9499176319312758, + -0.31250038807348235, + 0, + 125.28544865161753 + ], + [ + 0.3125003880734824, + 0.949917631931276, + 0, + 178.90990418300998 + ], + [ + 0, + 0, + 1, + -52.726459632828885 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9496474055263497, + -0.3133206108398736, + 0, + 128.63794898508766 + ], + [ + 0.3133206108398734, + 0.9496474055263502, + 0, + 169.1933417150359 + ], + [ + 0, + 0, + 1, + -52.518411509905384 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9500870332841831, + -0.31198498230725696, + 0, + 123.16429136202113 + ], + [ + 0.3119849823072568, + 0.950087033284183, + 0, + 185.6097808970522 + ], + [ + 0, + 0, + 1, + -52.56719725446565 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9500647521207781, + -0.3120528269022471, + 0, + 123.63151782636125 + ], + [ + 0.31205282690224684, + 0.9500647521207782, + 0, + 184.54766316289917 + ], + [ + 0, + 0, + 1, + -52.622621598143816 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9528204722536545, + -0.30353442581414564, + 0, + 89.93318566268395 + ], + [ + 0.30353442581414564, + 0.9528204722536542, + 0, + 289.95768722051054 + ], + [ + 0, + 0, + 1, + -52.64799745147313 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9527951273959943, + -0.3036139740039157, + 0, + 90.1011261470215 + ], + [ + 0.30361397400391577, + 0.9527951273959945, + 0, + 288.85233800116447 + ], + [ + 0, + 0, + 1, + -52.85032638671682 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9532026083831409, + -0.3023322466618072, + 0, + 85.09232061099232 + ], + [ + 0.3023322466618072, + 0.9532026083831411, + 0, + 304.65412663810844 + ], + [ + 0, + 0, + 1, + -52.95642710646523 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9532721889835791, + -0.3021127830967359, + 0, + 83.88586536629727 + ], + [ + 0.3021127830967357, + 0.9532721889835792, + 0, + 307.2380541573799 + ], + [ + 0, + 0, + 1, + -53.175193691696414 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9531583362986883, + -0.30247179363424626, + 0, + 85.44875313066764 + ], + [ + 0.3024717936342463, + 0.9531583362986878, + 0, + 302.7268324079667 + ], + [ + 0, + 0, + 1, + -53.21155022037145 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9531663161501063, + -0.3024466461325625, + 0, + 85.34474935065883 + ], + [ + 0.30244664613256256, + 0.9531663161501065, + 0, + 302.84365993328186 + ], + [ + 0, + 0, + 1, + -53.214027264168934 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9531696080468409, + -0.3024362714619902, + 0, + 85.29249999539867 + ], + [ + 0.3024362714619901, + 0.9531696080468413, + 0, + 302.7606526077114 + ], + [ + 0, + 0, + 1, + -53.27758035674312 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9531379171339579, + -0.3025361315967738, + 0, + 85.57110836246713 + ], + [ + 0.3025361315967738, + 0.9531379171339578, + 0, + 301.41590904344093 + ], + [ + 0, + 0, + 1, + -53.47187569504326 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530126019638929, + -0.30293065295214333, + 0, + 87.10557261117486 + ], + [ + 0.3029306529521439, + 0.9530126019638925, + 0, + 296.49443603148313 + ], + [ + 0, + 0, + 1, + -53.47821307034197 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9529475879803535, + -0.30313510941893185, + 0, + 87.65727234441096 + ], + [ + 0.30313510941893257, + 0.9529475879803533, + 0, + 293.6550546636824 + ], + [ + 0, + 0, + 1, + -53.8206144977806 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.952918435331757, + -0.30322673959411245, + 0, + 87.52228582077669 + ], + [ + 0.3032267395941123, + 0.9529184353317572, + 0, + 292.4136580267427 + ], + [ + 0, + 0, + 1, + -53.988184917863634 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9528366760357795, + -0.3034835560637305, + 0, + 88.73296005428116 + ], + [ + 0.30348355606373056, + 0.9528366760357795, + 0, + 289.2267871858141 + ], + [ + 0, + 0, + 1, + -53.96823178920712 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9527754800340136, + -0.30367562405625353, + 0, + 89.82948181771125 + ], + [ + 0.30367562405625353, + 0.9527754800340135, + 0, + 286.7824725119135 + ], + [ + 0, + 0, + 1, + -53.86009415809723 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9526741446925282, + -0.3039933782738695, + 0, + 91.18199715875774 + ], + [ + 0.3039933782738696, + 0.9526741446925282, + 0, + 282.8336636698898 + ], + [ + 0, + 0, + 1, + -53.82540698150436 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.952527717919876, + -0.3044518789469904, + 0, + 92.89729892370873 + ], + [ + 0.3044518789469901, + 0.9525277179198758, + 0, + 277.1337664286167 + ], + [ + 0, + 0, + 1, + -53.81150289619046 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9524014055392418, + -0.30484678565941703, + 0, + 94.40373288138663 + ], + [ + 0.3048467856594178, + 0.9524014055392415, + 0, + 272.4361692360764 + ], + [ + 0, + 0, + 1, + -53.63332212936763 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.952277987263523, + -0.3052321001686008, + 0, + 95.69882730442794 + ], + [ + 0.30523210016860114, + 0.9522779872635226, + 0, + 267.24406234759635 + ], + [ + 0, + 0, + 1, + -53.8369047322245 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9519239491141167, + -0.30633444974894, + 0, + 99.89443042843077 + ], + [ + 0.3063344497489405, + 0.9519239491141163, + 0, + 253.46354175240361 + ], + [ + 0, + 0, + 1, + -54.005861928134614 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9519614228223408, + -0.30621797703280734, + 0, + 99.56570113241516 + ], + [ + 0.30621797703280756, + 0.9519614228223406, + 0, + 255.07744901650892 + ], + [ + 0, + 0, + 1, + -54.14545553629375 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9517214848895255, + -0.30696288896164325, + 0, + 102.42360942886475 + ], + [ + 0.3069628889616435, + 0.9517214848895251, + 0, + 246.32383929907027 + ], + [ + 0, + 0, + 1, + -54.34041606617888 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9512203900522184, + -0.30851218703465266, + 0, + 108.31761306477459 + ], + [ + 0.3085121870346525, + 0.9512203900522185, + 0, + 227.6719209251085 + ], + [ + 0, + 0, + 1, + -54.34347214750673 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9512692367507553, + -0.3083615397737448, + 0, + 107.66000912738522 + ], + [ + 0.30836153977374414, + 0.9512692367507553, + 0, + 228.94612723171636 + ], + [ + 0, + 0, + 1, + -54.47392013977816 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9513776206666531, + -0.30802698403655976, + 0, + 106.33599241151092 + ], + [ + 0.30802698403656, + 0.9513776206666528, + 0, + 233.32878147858722 + ], + [ + 0, + 0, + 1, + -54.56242455331851 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9513087306466879, + -0.30823967784078043, + 0, + 107.0744493752633 + ], + [ + 0.3082396778407808, + 0.9513087306466878, + 0, + 230.320266886755 + ], + [ + 0, + 0, + 1, + -54.603179194884305 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9506105786417777, + -0.3103860946858668, + 0, + 115.57435498862584 + ], + [ + 0.31038609468586664, + 0.950610578641778, + 0, + 203.19598999479484 + ], + [ + 0, + 0, + 1, + -54.71562075578477 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9503143484018964, + -0.3112918874970556, + 0, + 119.07786030309022 + ], + [ + 0.31129188749705583, + 0.9503143484018963, + 0, + 191.94577906153995 + ], + [ + 0, + 0, + 1, + -54.71183454741115 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9500356637477465, + -0.31214137439208334, + 0, + 122.45181958079965 + ], + [ + 0.3121413743920831, + 0.9500356637477464, + 0, + 181.38251130176764 + ], + [ + 0, + 0, + 1, + -54.74134747486604 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9501348095023199, + -0.3118394519171522, + 0, + 121.22777483788327 + ], + [ + 0.311839451917152, + 0.9501348095023201, + 0, + 185.14893825365792 + ], + [ + 0, + 0, + 1, + -54.7224753059539 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9500994880574112, + -0.3119470512651868, + 0, + 121.76005294606801 + ], + [ + 0.3119470512651864, + 0.9500994880574112, + 0, + 183.91376679160405 + ], + [ + 0, + 0, + 1, + -54.77036961035862 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9502415293653819, + -0.31151410219978953, + 0, + 119.91439869816548 + ], + [ + 0.3115141021997899, + 0.9502415293653819, + 0, + 189.37589568272057 + ], + [ + 0, + 0, + 1, + -54.69794169418869 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9502174875676661, + -0.31158742965753733, + 0, + 120.29658734348071 + ], + [ + 0.3115874296575364, + 0.9502174875676661, + 0, + 188.5647908438023 + ], + [ + 0, + 0, + 1, + -54.66880722665258 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9496120358838342, + -0.31342779280810334, + 0, + 127.73969844873508 + ], + [ + 0.31342779280810346, + 0.9496120358838344, + 0, + 165.7270142032457 + ], + [ + 0, + 0, + 1, + -54.65389059373349 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9492310322384901, + -0.3145797950209939, + 0, + 132.48373825101822 + ], + [ + 0.314579795020994, + 0.9492310322384906, + 0, + 151.4686278906733 + ], + [ + 0, + 0, + 1, + -54.6139591944754 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.949700845852346, + -0.3131585914314641, + 0, + 126.58766294189229 + ], + [ + 0.31315859143146396, + 0.9497008458523459, + 0, + 168.9858523891425 + ], + [ + 0, + 0, + 1, + -54.43790057295874 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9496672039772364, + -0.3132605970913976, + 0, + 126.91271474785472 + ], + [ + 0.313260597091398, + 0.9496672039772364, + 0, + 167.6538314057921 + ], + [ + 0, + 0, + 1, + -54.339337779171494 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9497520015380477, + -0.3130034114422281, + 0, + 126.00038000915222 + ], + [ + 0.3130034114422282, + 0.9497520015380476, + 0, + 170.98464429976246 + ], + [ + 0, + 0, + 1, + -54.217873286785725 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9496915924226942, + -0.31318665246406663, + 0, + 126.60352150474561 + ], + [ + 0.3131866524640669, + 0.9496915924226942, + 0, + 169.01884096286346 + ], + [ + 0, + 0, + 1, + -53.833458756227905 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9497605517985698, + -0.3129774660375366, + 0, + 126.06566315391913 + ], + [ + 0.31297746603753684, + 0.9497605517985694, + 0, + 171.77221482185792 + ], + [ + 0, + 0, + 1, + -53.42655859217465 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + ], + "inf_weekly": [ + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + ], + "inf_one_interval": [ + [ + [ + 0.9530416740423117, + -0.30283917768120405, + 0, + 86.51389127230165 + ], + [ + 0.3028391776812039, + 0.9530416740423118, + 0, + 297.5848898679969 + ], + [ + 0, + 0, + 1, + -53.71111111111132 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + ] +} \ No newline at end of file diff --git a/etc/adjusted/adjbou_state_.json b/etc/adjusted/adjbou_state_.json index 365af0fa1f48d98474ce7642cc270227bc4c92c1..7492efd366c258ee9392814efb53c9ed2451f204 100644 --- a/etc/adjusted/adjbou_state_.json +++ b/etc/adjusted/adjbou_state_.json @@ -1 +1,24 @@ -{"M32": -0.011809351484171948, "M41": -0.0, "M44": 1.0, "PC": -22, "M24": -0.84581925813504188, "M43": 0.0, "M34": 905.38008857968441, "M33": 0.99618690124939757, "M21": 0.16680172992706568, "M22": 0.98791620101212796, "M23": -0.0049868332295851525, "M11": 0.98342757670906167, "M42": -0.0, "M13": 0.027384986324932026, "M12": -0.15473074200902157, "M14": -1276.1646811919759, "M31": -0.006725053082782385} \ No newline at end of file +{ + "matrix":[ + [ + 0.9834275767090617, + -0.15473074200902157, + 0.027384986324932026, + -1276.164681191976 + ], + [ + 0.16680172992706568, + 0.987916201012128, + -0.0049868332295851525, + -0.8458192581350419 + ], + [ + -0.006725053082782385, + -0.011809351484171948, + 0.9961869012493976, + 905.3800885796844 + ], + [0, 0, 0, 1] + ], + "pier_correction": -22 +} \ No newline at end of file diff --git a/etc/adjusted/adjbou_state_HE_.json b/etc/adjusted/adjbou_state_HE_.json index 91ed3625c0bbb0ecd40e770c3ac7202797783df8..4aa26dd352c076490f79b6941af7676c6d132e9b 100644 --- a/etc/adjusted/adjbou_state_HE_.json +++ b/etc/adjusted/adjbou_state_HE_.json @@ -1 +1,12 @@ -{"PC": -22.0, "M11": -1.0, "M12": -0.0, "M13": -0.0, "M21": -0.0, "M22": -1.0, "M23": -0.0, "M31": 0.0, "M32": 0.0, "M33": 1.0} \ No newline at end of file +{ + "PC": -22, + "M11": -1, + "M12": 0, + "M13": 0, + "M21": 0, + "M22": -1, + "M23": 0, + "M31": 0, + "M32": 0, + "M33": 1 +} \ No newline at end of file diff --git a/etc/adjusted/synthetic.json b/etc/adjusted/synthetic.json new file mode 100644 index 0000000000000000000000000000000000000000..29f466f5c21eac383ac25d2567effa0def98e6d9 --- /dev/null +++ b/etc/adjusted/synthetic.json @@ -0,0 +1,398 @@ +{ + "variables": { + "h_ord": [ + 1.79289322, + 1.83983806, + 1.91024109, + 2.51489726, + 1.78952971, + 2.81157403, + 2.36417497, + 2.43864844, + 3.25364394, + 2.27479228, + 3.47800226, + 2.95694665, + 3.03482926, + 3.84541634, + 2.92632734, + 4.01635411, + 3.59456862, + 3.69312428, + 4.27043672, + 3.82250402 + ], + "e_ord": [ + 4.46592583, + 3.99414122, + 4.86042918, + 4.08398446, + 4.06789278, + 4.89701546, + 3.29221074, + 4.91313376, + 3.76430406, + 3.77979418, + 4.76896396, + 2.98259167, + 4.69899703, + 3.54330923, + 3.56461527, + 4.43474537, + 2.93993354, + 4.24945532, + 3.43005774, + 3.46078181 + ], + "z_ord": [ + 2.24118095, + 2.17339265, + 2.88988358, + 1.74783897, + 3.18842428, + 2.36825251, + 2.39116112, + 3.56453372, + 1.85923781, + 3.7224498, + 2.71235632, + 2.74095046, + 3.96752743, + 2.26503334, + 4.0367477, + 3.13361064, + 3.18331114, + 4.12716967, + 3.00224898, + 4.03024472 + ], + "x_abs": [ + -1, + -0.899979996, + -0.799959992, + -0.699939988, + -0.599919984, + -0.49989998, + -0.399879976, + -0.299859972, + -0.199839968, + -0.099819964, + 0.000200040008, + 0.100220044, + 0.200240048, + 0.300260052, + 0.400280056, + 0.50030006, + 0.600320064, + 0.700340068, + 0.800360072, + 0.900380076 + ], + "y_abs": [ + 0, + -0.35248241, + 0.18456572, + 0.22223661, + -0.64867776, + 0.86607699, + -0.7390734, + 0.29000341, + 0.30840017, + -0.80892729, + 0.99997154, + -0.80006479, + 0.29431378, + 0.30363751, + -0.74702288, + 0.8657967, + -0.64130022, + 0.211858, + 0.19298425, + -0.35563497 + ], + "z_abs": [ + 0, + -0.25649982, + 0.57096366, + -0.67874509, + 0.46830885, + 0.0032657, + -0.54209456, + 0.90883553, + -0.93002867, + 0.57937261, + 0.00754126, + -0.59148311, + 0.93449629, + -0.904239, + 0.53078497, + 0.00979431, + -0.47785966, + 0.68164505, + -0.56760976, + 0.25067805 + ] + }, + "results": { + "LeastSq": [ + [ + 0.5364919027495736, + -0.20707814501736121, + 0.33725290893445425, + -1.7929218549826373 + ], + [ + 0.5325045930544003, + 0.847092478025647, + -0.32696549213267934, + -4.004987214867749 + ], + [ + -0.25884768127170443, + 0.4215661361680674, + 0.6706148784369698, + -2.921566136168065 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "ZRotationShear": [ + [ + 0.5587910280835219, + -0.1593988978820583, + 0, + -0.9594702768899068 + ], + [ + 0.5108856705906101, + 0.8008676180137191, + 0, + -4.813015546664324 + ], + [ + 0, + 0, + 0.7036074465845426, + -2.273788534881388 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "ZRotationHscale": [ + [ + 0.6395521012137755, + -0.3193786953272828, + 0, + -0.6145604575448345 + ], + [ + 0.3193786953272828, + 0.6395521012137755, + 0, + -3.550295624100921 + ], + [ + 0, + 0, + 0.7036074465845424, + -2.2737885348813887 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "ZRotationHscaleZbaseline": [ + [ + 0.053926953353156136, + 0.03582167951143748, + 0, + 0 + ], + [ + -0.03582167951143748, + 0.053926953353156136, + 0, + 0 + ], + [ + 0, + 0, + 1, + -3.2332029498017487 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "SVD": [ + [ + 0.8100811050998874, + -0.3030939677547838, + 0.5018990435045756, + -2.8734904692082317 + ], + [ + 0.4979757275740692, + 0.8075341522117618, + -0.3160834822617412, + -3.7722406528522203 + ], + [ + -0.3094976218119015, + 0.5059867979723319, + 0.8051015975456041, + -3.511977037527143 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "Rescale3D": [ + [ + 0.10754195315849033, + 0, + 0, + 0 + ], + [ + 0, + 0.012787625579547376, + 0, + 0 + ], + [ + 0, + 0, + 0.032798154583522594, + 0 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "TranslateOrigins": [ + [ + 1, + 0, + 0, + -3.0518410718922895 + ], + [ + 0, + 1, + 0, + -3.8667699341149695 + ], + [ + 0, + 0, + 1, + -3.2332029498017487 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "ShearYZ": [ + [ + 1, + 0, + 0, + 0 + ], + [ + -0.6692050417082805, + 1, + 0, + 0 + ], + [ + 0.35315324212931615, + -0.06394207042397665, + 1, + 0 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "RotationTranslationXY": [ + [ + 0.8946494196067308, + -0.44676886193795917, + 0, + -0.9794537295011263 + ], + [ + 0.4467688619379593, + 0.894649419606731, + 0, + -4.958977628364134 + ], + [ + 0, + 0, + 1, + -3.233202949801749 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "QRFactorization": [ + [ + 0.7380347209075413, + -0.2626732024800775, + 0, + -1.1628964087154696 + ], + [ + 0.6747627366229761, + 1.1147956698369132, + 0, + -6.5703958608516055 + ], + [ + 0, + 0, + 1, + -3.233202949801749 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + } +} \ No newline at end of file diff --git a/etc/filter/BOU20200831vday.day b/etc/filter/BOU20200831vday.day new file mode 100644 index 0000000000000000000000000000000000000000..94858b73c0a6ade10b0f89955ec2f3fa866efc70 --- /dev/null +++ b/etc/filter/BOU20200831vday.day @@ -0,0 +1,26 @@ + Format IAGA-2002 | + Source of Data United States Geological Survey (USGS) | + Station Name Boulder | + IAGA CODE BOU | + Geodetic Latitude 40.137 | + Geodetic Longitude 254.763 | + Elevation 1682 | + Reported HEZF | + Sensor Orientation HDZF | + Digital Sampling 0.01 second | + Data Interval Type filtered 1-minute (00:15-01:45) | + Data Type variation | + # DECBAS 5527 (Baseline declination value in | + # tenths of minutes East (0-216,000)). | + # Vector 1-minute values are computed from 1-second values using | + # the INTERMAGNET gaussian filter centered on the minute. Scalar | + # 1-minute values are computed from 1-second values using the | + # INTERMAGNET gaussian filter centered on the minute. | + # CONDITIONS OF USE: The Conditions of Use for data provided | + # through INTERMAGNET and acknowledgement templates can be found at | + # www.intermagnet.org | +DATE TIME DOY BOUH BOUE BOUZ BOUF | +2020-08-27 11:59:30.000 240 20817.44 -110.62 46800.86 51739.31 +2020-08-28 11:59:30.000 241 20817.73 -111.55 46799.29 51738.09 +2020-08-29 11:59:30.000 242 20804.21 -109.16 46800.15 51733.70 +2020-08-30 11:59:30.000 243 20808.16 -109.66 46803.17 51738.29 diff --git a/etc/filter/BOU20200831vhor.hor b/etc/filter/BOU20200831vhor.hor new file mode 100644 index 0000000000000000000000000000000000000000..c721fe096ba82490efbb294325da3986d9163b63 --- /dev/null +++ b/etc/filter/BOU20200831vhor.hor @@ -0,0 +1,26 @@ + Format IAGA-2002 | + Source of Data United States Geological Survey (USGS) | + Station Name Boulder | + IAGA CODE BOU | + Geodetic Latitude 40.137 | + Geodetic Longitude 254.763 | + Elevation 1682 | + Reported HEZF | + Sensor Orientation HDZF | + Digital Sampling 0.01 second | + Data Interval Type filtered 1-minute (00:15-01:45) | + Data Type variation | + # DECBAS 5527 (Baseline declination value in | + # tenths of minutes East (0-216,000)). | + # Vector 1-minute values are computed from 1-second values using | + # the INTERMAGNET gaussian filter centered on the minute. Scalar | + # 1-minute values are computed from 1-second values using the | + # INTERMAGNET gaussian filter centered on the minute. | + # CONDITIONS OF USE: The Conditions of Use for data provided | + # through INTERMAGNET and acknowledgement templates can be found at | + # www.intermagnet.org | +DATE TIME DOY BOUH BOUE BOUZ BOUF | +2020-08-31 00:29:30.000 244 20778.61 -99.10 46814.71 51737.42 +2020-08-31 01:29:30.000 244 20777.91 -102.77 46814.63 51737.11 +2020-08-31 02:29:30.000 244 20789.41 -96.59 46814.35 51741.37 +2020-08-31 03:29:30.000 244 20813.68 -73.96 46808.12 51745.12 diff --git a/etc/filter/LLO20200106vhor.hor b/etc/filter/LLO20200106vhor.hor deleted file mode 100644 index bb6a60d077cd341939eff9e729a2c40585b85e76..0000000000000000000000000000000000000000 --- a/etc/filter/LLO20200106vhor.hor +++ /dev/null @@ -1,9 +0,0 @@ - Format IAGA-2002 | - IAGA CODE LLO | - Reported UVWNUL | -DATE TIME DOY LLOU LLOV LLOW LLONUL | -2020-01-06 00:00:00.000 006 8330.00 -18971.07 39294.07 99999.00 -2020-01-06 01:00:00.000 006 8341.78 -18959.85 39294.98 99999.00 -2020-01-06 02:00:00.000 006 8363.88 -18962.53 39294.24 99999.00 -2020-01-06 03:00:00.000 006 8305.76 -18932.89 39305.85 99999.00 -2020-01-06 04:00:00.000 006 8320.99 -18990.56 39275.92 99999.00 diff --git a/etc/filter/coeffs.json b/etc/filter/coeffs.json index 32708e153259678d2223b2fac9552964f2fbd4bb..e5c7b364cf101f29c4527535ac33441d55ac0a28 100644 --- a/etc/filter/coeffs.json +++ b/etc/filter/coeffs.json @@ -123,5 +123,6 @@ -0.000004277196717501353, -0.0000012056376791654907, 7.2383512291811e-20 - ] + ], + "type": "firfilter" } \ No newline at end of file diff --git a/etc/filter/day_filter_min.mseed b/etc/filter/day_filter_min.mseed new file mode 100644 index 0000000000000000000000000000000000000000..412c009d4a756661707eee3533de147cff41957b Binary files /dev/null and b/etc/filter/day_filter_min.mseed differ diff --git a/etc/filter/hor_filter_min.mseed b/etc/filter/hor_filter_min.mseed new file mode 100644 index 0000000000000000000000000000000000000000..1917ade005d7bc1518a8f073c38ee86acf450fc0 Binary files /dev/null and b/etc/filter/hor_filter_min.mseed differ diff --git a/etc/residual/BOU20190702.json b/etc/residual/BOU20190702.json new file mode 100644 index 0000000000000000000000000000000000000000..aeb0ecf2afa7693ffd450990eef07fff32785bb1 --- /dev/null +++ b/etc/residual/BOU20190702.json @@ -0,0 +1 @@ +[{"absolutes": [{"element": "D", "absolute": 8.4358, "baseline": 8.5850478, "starttime": "2019-07-02T16:20:46", "endtime": "2019-07-02T16:24:25", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20739.5579824, "baseline": -76.9417676, "starttime": "2019-07-02T16:27:13", "endtime": "2019-07-02T16:31:40", "shift": 0.0, "valid": false}, {"element": "Z", "absolute": 47504.8688095, "baseline": 574.6068095, "starttime": "2019-07-02T16:27:13", "endtime": "2019-07-02T16:31:40", "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 190.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 10.386666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 10.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 190.38666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 269.73833333333, "residual": 0.0, "time": "2019-07-02T16:20:46", "h": 20819.185, "e": -53.891, "z": 46931.842, "f": 51859.3}, {"measurement_type": "EastDown", "angle": 89.601666666667, "residual": 0.0, "time": "2019-07-02T16:22:00", "h": 20818.702, "e": -54.311, "z": 46931.738, "f": 51859.0}, {"measurement_type": "WestUp", "angle": 89.63, "residual": 0.0, "time": "2019-07-02T16:22:49", "h": 20818.452, "e": -54.129, "z": 46931.399, "f": 51858.61}, {"measurement_type": "EastUp", "angle": 269.76333333333, "residual": 0.0, "time": "2019-07-02T16:24:25", "h": 20817.584, "e": -53.764, "z": 46931.192, "f": 51858.07}, {"measurement_type": "SouthDown", "angle": 246.38333333333, "residual": 0.0, "time": "2019-07-02T16:27:13", "h": 20817.561, "e": -53.482, "z": 46930.707, "f": 51857.58}, {"measurement_type": "NorthUp", "angle": 66.383333333333, "residual": 0.0, "time": "2019-07-02T16:27:56", "h": 20817.389, "e": -53.323, "z": 46930.486, "f": 51857.32}, {"measurement_type": "SouthUp", "angle": 113.55333333333, "residual": 0.0, "time": "2019-07-02T16:30:40", "h": 20815.75, "e": -53.589, "z": 46930.115, "f": 51856.33}, {"measurement_type": "NorthDown", "angle": 293.55333333333, "residual": 0.0, "time": "2019-07-02T16:31:40", "h": 20815.299, "e": -53.24, "z": 46929.74, "f": 51855.79}], "metadata": {"time": "2019-07-02T16:20:46Z", "reviewed": true, "electronics": "0110", "theodolite": "614379", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Bennett Emmons", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.4328833, "baseline": 8.5799061, "starttime": "2019-07-02T16:34:49", "endtime": "2019-07-02T16:38:20", "shift": 0.0, "valid": false}, {"element": "H", "absolute": 20738.2526264, "baseline": -75.7596236, "starttime": "2019-07-02T16:41:27", "endtime": "2019-07-02T16:45:14", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47501.8788342, "baseline": 574.1223342, "starttime": "2019-07-02T16:41:27", "endtime": "2019-07-02T16:45:14", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 10.386666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 190.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 190.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 10.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 269.735, "residual": 0.0, "time": "2019-07-02T16:34:49", "h": 20814.756, "e": -53.24, "z": 46929.196, "f": 51855.09}, {"measurement_type": "EastDown", "angle": 89.586666666667, "residual": 0.0, "time": "2019-07-02T16:35:41", "h": 20814.408, "e": -52.934, "z": 46928.999, "f": 51854.78}, {"measurement_type": "WestUp", "angle": 89.633333333333, "residual": 0.0, "time": "2019-07-02T16:37:04", "h": 20814.638, "e": -53.267, "z": 46928.75, "f": 51854.67}, {"measurement_type": "EastUp", "angle": 269.765, "residual": 0.0, "time": "2019-07-02T16:38:20", "h": 20814.82, "e": -53.419, "z": 46928.536, "f": 51854.49}, {"measurement_type": "SouthDown", "angle": 246.38333333333, "residual": 0.0, "time": "2019-07-02T16:41:27", "h": 20813.878, "e": -52.728, "z": 46928.011, "f": 51853.7}, {"measurement_type": "NorthUp", "angle": 66.383333333333, "residual": 0.0, "time": "2019-07-02T16:42:36", "h": 20814.146, "e": -52.795, "z": 46927.824, "f": 51853.62}, {"measurement_type": "SouthUp", "angle": 113.55333333333, "residual": 0.0, "time": "2019-07-02T16:44:00", "h": 20814.106, "e": -52.939, "z": 46927.665, "f": 51853.42}, {"measurement_type": "NorthDown", "angle": 293.55333333333, "residual": 0.0, "time": "2019-07-02T16:45:14", "h": 20813.919, "e": -52.993, "z": 46927.526, "f": 51853.23}], "metadata": {"time": "2019-07-02T16:20:46Z", "reviewed": true, "electronics": "0110", "theodolite": "614379", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Bennett Emmons", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.4358, "baseline": 8.5862435, "starttime": "2019-07-02T16:47:19", "endtime": "2019-07-02T16:50:14", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20737.7244824, "baseline": -75.5807676, "starttime": "2019-07-02T16:51:54", "endtime": "2019-07-02T16:54:25", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47500.6690971, "baseline": 574.0740971, "starttime": "2019-07-02T16:51:54", "endtime": "2019-07-02T16:54:25", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 190.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 190.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 10.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 10.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 269.73166666667, "residual": 0.0, "time": "2019-07-02T16:47:19", "h": 20814.146, "e": -53.594, "z": 46927.201, "f": 51853.04}, {"measurement_type": "EastDown", "angle": 89.6, "residual": 0.0, "time": "2019-07-02T16:48:18", "h": 20813.935, "e": -54.139, "z": 46927.227, "f": 51852.98}, {"measurement_type": "WestUp", "angle": 89.638333333333, "residual": 0.0, "time": "2019-07-02T16:48:58", "h": 20813.988, "e": -54.762, "z": 46927.164, "f": 51852.91}, {"measurement_type": "EastUp", "angle": 269.76, "residual": 0.0, "time": "2019-07-02T16:50:14", "h": 20813.834, "e": -55.312, "z": 46926.943, "f": 51852.66}, {"measurement_type": "SouthDown", "angle": 246.385, "residual": 0.0, "time": "2019-07-02T16:51:54", "h": 20813.621, "e": -56.089, "z": 46926.728, "f": 51852.42}, {"measurement_type": "NorthUp", "angle": 66.385, "residual": 0.0, "time": "2019-07-02T16:52:41", "h": 20813.433, "e": -56.561, "z": 46926.667, "f": 51852.27}, {"measurement_type": "SouthUp", "angle": 113.55333333333, "residual": 0.0, "time": "2019-07-02T16:53:41", "h": 20813.181, "e": -56.834, "z": 46926.494, "f": 51852.06}, {"measurement_type": "NorthDown", "angle": 293.55666666667, "residual": 0.0, "time": "2019-07-02T16:54:25", "h": 20812.986, "e": -57.102, "z": 46926.491, "f": 51851.94}], "metadata": {"time": "2019-07-02T16:20:46Z", "reviewed": true, "electronics": "0110", "theodolite": "614379", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Bennett Emmons", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.41205, "baseline": 8.5852865, "starttime": "2019-07-02T16:59:37", "endtime": "2019-07-02T17:03:13", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20739.1282195, "baseline": -75.6935305, "starttime": "2019-07-02T17:05:30", "endtime": "2019-07-02T17:08:27", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47500.1162461, "baseline": 574.1532461, "starttime": "2019-07-02T17:05:30", "endtime": "2019-07-02T17:08:27", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 190.38666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 190.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 10.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 10.385, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 269.70166666667, "residual": 0.0, "time": "2019-07-02T16:59:37", "h": 20813.215, "e": -61.617, "z": 46926.215, "f": 51851.8}, {"measurement_type": "EastDown", "angle": 89.583333333333, "residual": 0.0, "time": "2019-07-02T17:00:52", "h": 20813.607, "e": -62.62, "z": 46926.101, "f": 51851.87}, {"measurement_type": "WestUp", "angle": 89.616666666667, "residual": 0.0, "time": "2019-07-02T17:02:19", "h": 20813.554, "e": -63.094, "z": 46926.042, "f": 51851.77}, {"measurement_type": "EastUp", "angle": 269.735, "residual": 0.0, "time": "2019-07-02T17:03:13", "h": 20813.67, "e": -63.492, "z": 46926.022, "f": 51851.8}, {"measurement_type": "SouthDown", "angle": 246.385, "residual": 0.0, "time": "2019-07-02T17:05:30", "h": 20814.256, "e": -64.44, "z": 46926.03, "f": 51852.05}, {"measurement_type": "NorthUp", "angle": 66.385, "residual": 0.0, "time": "2019-07-02T17:06:19", "h": 20814.45, "e": -64.926, "z": 46926.012, "f": 51852.11}, {"measurement_type": "SouthUp", "angle": 113.55833333333, "residual": 0.0, "time": "2019-07-02T17:07:27", "h": 20815.241, "e": -65.458, "z": 46925.899, "f": 51852.36}, {"measurement_type": "NorthDown", "angle": 293.55833333333, "residual": 0.0, "time": "2019-07-02T17:08:27", "h": 20815.34, "e": -65.821, "z": 46925.911, "f": 51852.39}], "metadata": {"time": "2019-07-02T16:20:46Z", "reviewed": true, "electronics": "0110", "theodolite": "614379", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Bennett Emmons", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}] \ No newline at end of file diff --git a/etc/residual/BOU20191001.json b/etc/residual/BOU20191001.json new file mode 100644 index 0000000000000000000000000000000000000000..6db1a5298bdd3177e4501a684068d47c2498b9d9 --- /dev/null +++ b/etc/residual/BOU20191001.json @@ -0,0 +1 @@ +[{"absolutes": [{"element": "D", "absolute": 8.3894111, "baseline": 8.5879326, "starttime": "2019-10-02T13:44:55", "endtime": "2019-10-02T13:49:50", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20751.3862752, "baseline": -76.8952248, "starttime": "2019-10-02T13:57:00", "endtime": "2019-10-02T14:00:37", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47483.7701945, "baseline": 575.8616945, "starttime": "2019-10-02T13:57:00", "endtime": "2019-10-02T14:00:37", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.45111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.448888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.448333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.44888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60972222222, "residual": 0.0, "time": "2019-10-02T13:44:55", "h": 20831.253, "e": -73.216, "z": 46908.583, "f": 51843.98}, {"measurement_type": "EastDown", "angle": 90.553611111111, "residual": 0.0, "time": "2019-10-02T13:47:09", "h": 20830.425, "e": -71.349, "z": 46908.471, "f": 51843.51}, {"measurement_type": "WestUp", "angle": 90.784444444444, "residual": 0.0, "time": "2019-10-02T13:48:39", "h": 20830.387, "e": -71.155, "z": 46908.635, "f": 51843.68}, {"measurement_type": "EastUp", "angle": 270.85388888889, "residual": 0.0, "time": "2019-10-02T13:49:50", "h": 20830.563, "e": -71.882, "z": 46908.38, "f": 51843.54}, {"measurement_type": "SouthDown", "angle": 246.37972222222, "residual": 0.0, "time": "2019-10-02T13:57:00", "h": 20829.257, "e": -68.815, "z": 46907.921, "f": 51842.56}, {"measurement_type": "NorthUp", "angle": 66.378333333333, "residual": 0.0, "time": "2019-10-02T13:58:16", "h": 20828.642, "e": -67.645, "z": 46907.81, "f": 51842.23}, {"measurement_type": "SouthUp", "angle": 113.58944444444, "residual": 0.0, "time": "2019-10-02T13:59:36", "h": 20827.673, "e": -66.901, "z": 46908.178, "f": 51842.15}, {"measurement_type": "NorthDown", "angle": 293.59388888889, "residual": 0.0, "time": "2019-10-02T14:00:37", "h": 20827.554, "e": -66.317, "z": 46907.725, "f": 51841.68}], "metadata": {"time": "2019-10-02T13:44:55Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.4167722, "baseline": 8.5941292, "starttime": "2019-10-02T14:03:39", "endtime": "2019-10-02T14:07:38", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20743.2614094, "baseline": -76.6220906, "starttime": "2019-10-02T14:13:10", "endtime": "2019-10-02T14:16:32", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47482.2730636, "baseline": 575.7863136, "starttime": "2019-10-02T14:13:10", "endtime": "2019-10-02T14:16:32", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.455833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.45638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.45694444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.455555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.63444444444, "residual": 0.0, "time": "2019-10-02T14:03:39", "h": 20826.4, "e": -65.107, "z": 46907.395, "f": 51840.96}, {"measurement_type": "EastDown", "angle": 90.583333333333, "residual": 0.0, "time": "2019-10-02T14:05:05", "h": 20825.874, "e": -64.628, "z": 46907.42, "f": 51840.77}, {"measurement_type": "WestUp", "angle": 90.821111111111, "residual": 0.0, "time": "2019-10-02T14:06:18", "h": 20825.555, "e": -63.96, "z": 46907.332, "f": 51840.54}, {"measurement_type": "EastUp", "angle": 270.89972222222, "residual": 0.0, "time": "2019-10-02T14:07:38", "h": 20825.141, "e": -63.145, "z": 46907.101, "f": 51840.19}, {"measurement_type": "SouthDown", "angle": 246.38805555556, "residual": 0.0, "time": "2019-10-02T14:13:10", "h": 20821.423, "e": -60.643, "z": 46906.892, "f": 51838.53}, {"measurement_type": "NorthUp", "angle": 66.388055555556, "residual": 0.0, "time": "2019-10-02T14:14:19", "h": 20820.778, "e": -58.811, "z": 46906.403, "f": 51837.8}, {"measurement_type": "SouthUp", "angle": 113.58416666667, "residual": 0.0, "time": "2019-10-02T14:15:40", "h": 20819.476, "e": -58.524, "z": 46906.62, "f": 51837.5}, {"measurement_type": "NorthDown", "angle": 293.58694444444, "residual": 0.0, "time": "2019-10-02T14:16:32", "h": 20817.857, "e": -56.256, "z": 46906.032, "f": 51836.29}], "metadata": {"time": "2019-10-02T13:44:55Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.4487861, "baseline": 8.5913819, "starttime": "2019-10-02T14:20:39", "endtime": "2019-10-02T14:24:47", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20736.9902439, "baseline": -76.6657561, "starttime": "2019-10-02T14:30:56", "endtime": "2019-10-02T14:34:48", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47481.0947872, "baseline": 575.7935372, "starttime": "2019-10-02T14:30:56", "endtime": "2019-10-02T14:34:48", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.46194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.45916666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.458055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.457777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.67861111111, "residual": 0.0, "time": "2019-10-02T14:20:39", "h": 20815.36, "e": -52.474, "z": 46906.024, "f": 51835.25}, {"measurement_type": "EastDown", "angle": 90.625, "residual": 0.0, "time": "2019-10-02T14:22:06", "h": 20816.348, "e": -52.41, "z": 46906.269, "f": 51835.91}, {"measurement_type": "WestUp", "angle": 90.855, "residual": 0.0, "time": "2019-10-02T14:23:33", "h": 20815.075, "e": -51.59, "z": 46905.943, "f": 51835.09}, {"measurement_type": "EastUp", "angle": 270.92027777778, "residual": 0.0, "time": "2019-10-02T14:24:47", "h": 20814.634, "e": -49.964, "z": 46906.03, "f": 51835.01}, {"measurement_type": "SouthDown", "angle": 246.39833333333, "residual": 0.0, "time": "2019-10-02T14:30:56", "h": 20813.737, "e": -48.346, "z": 46905.353, "f": 51834.03}, {"measurement_type": "NorthUp", "angle": 66.395277777778, "residual": 0.0, "time": "2019-10-02T14:31:57", "h": 20813.721, "e": -48.162, "z": 46905.457, "f": 51834.1}, {"measurement_type": "SouthUp", "angle": 113.58083333333, "residual": 0.0, "time": "2019-10-02T14:33:49", "h": 20813.682, "e": -47.971, "z": 46905.315, "f": 51833.96}, {"measurement_type": "NorthDown", "angle": 293.58444444444, "residual": 0.0, "time": "2019-10-02T14:34:48", "h": 20813.484, "e": -47.151, "z": 46905.08, "f": 51833.67}], "metadata": {"time": "2019-10-02T13:44:55Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.4578833, "baseline": 8.5892011, "starttime": "2019-10-02T14:37:51", "endtime": "2019-10-02T14:41:42", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20734.3017642, "baseline": -76.5572358, "starttime": "2019-10-02T14:47:00", "endtime": "2019-10-02T14:50:10", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47479.8027803, "baseline": 575.7587803, "starttime": "2019-10-02T14:47:00", "endtime": "2019-10-02T14:50:10", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.45972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.46055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.459722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.458333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.68416666667, "residual": 0.0, "time": "2019-10-02T14:37:51", "h": 20813.338, "e": -46.948, "z": 46904.991, "f": 51833.51}, {"measurement_type": "EastDown", "angle": 90.638888888889, "residual": 0.0, "time": "2019-10-02T14:39:17", "h": 20813.396, "e": -47.387, "z": 46904.981, "f": 51833.54}, {"measurement_type": "WestUp", "angle": 90.873333333333, "residual": 0.0, "time": "2019-10-02T14:40:40", "h": 20812.921, "e": -47.883, "z": 46904.588, "f": 51833.02}, {"measurement_type": "EastUp", "angle": 270.92027777778, "residual": 0.0, "time": "2019-10-02T14:41:42", "h": 20813.02, "e": -47.868, "z": 46904.647, "f": 51833.08}, {"measurement_type": "SouthDown", "angle": 246.40083333333, "residual": 0.0, "time": "2019-10-02T14:47:00", "h": 20811.157, "e": -48.319, "z": 46904.336, "f": 51832.08}, {"measurement_type": "NorthUp", "angle": 66.399722222222, "residual": 0.0, "time": "2019-10-02T14:47:55", "h": 20811.171, "e": -48.633, "z": 46904.052, "f": 51831.82}, {"measurement_type": "SouthUp", "angle": 113.58083333333, "residual": 0.0, "time": "2019-10-02T14:49:09", "h": 20811.125, "e": -48.806, "z": 46904.014, "f": 51831.77}, {"measurement_type": "NorthDown", "angle": 293.58277777778, "residual": 0.0, "time": "2019-10-02T14:50:10", "h": 20809.983, "e": -48.326, "z": 46903.774, "f": 51831.05}], "metadata": {"time": "2019-10-02T13:44:55Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3246889, "baseline": 8.5901692, "starttime": "2019-10-04T19:30:11", "endtime": "2019-10-04T19:37:01", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20735.1086052, "baseline": -72.2263948, "starttime": "2019-10-04T19:45:13", "endtime": "2019-10-04T19:54:59", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47477.2571023, "baseline": 574.2851023, "starttime": "2019-10-04T19:45:13", "endtime": "2019-10-04T19:54:59", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.46277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.461111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.460833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.46277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.54083333333, "residual": 0.0, "time": "2019-10-04T19:30:11", "h": 20802.326, "e": -95.471, "z": 46901.282, "f": 51826.22}, {"measurement_type": "EastDown", "angle": 90.518333333333, "residual": 0.0, "time": "2019-10-04T19:32:27", "h": 20803.85, "e": -95.732, "z": 46901.24, "f": 51826.73}, {"measurement_type": "WestUp", "angle": 90.738055555556, "residual": 0.0, "time": "2019-10-04T19:35:36", "h": 20804.058, "e": -96.697, "z": 46901.805, "f": 51827.38}, {"measurement_type": "EastUp", "angle": 270.79583333333, "residual": 0.0, "time": "2019-10-04T19:37:01", "h": 20804.117, "e": -96.405, "z": 46901.946, "f": 51827.46}, {"measurement_type": "SouthDown", "angle": 246.40472222222, "residual": 0.0, "time": "2019-10-04T19:45:13", "h": 20805.618, "e": -95.886, "z": 46902.777, "f": 51828.84}, {"measurement_type": "NorthUp", "angle": 66.4025, "residual": 0.0, "time": "2019-10-04T19:47:22", "h": 20807.061, "e": -95.976, "z": 46902.692, "f": 51829.32}, {"measurement_type": "SouthUp", "angle": 113.57861111111, "residual": 0.0, "time": "2019-10-04T19:53:16", "h": 20808.224, "e": -96.385, "z": 46903.346, "f": 51830.35}, {"measurement_type": "NorthDown", "angle": 293.59944444444, "residual": 0.0, "time": "2019-10-04T19:54:59", "h": 20808.437, "e": -95.957, "z": 46903.073, "f": 51830.17}], "metadata": {"time": "2019-10-04T19:30:11Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3274667, "baseline": 8.5903328, "starttime": "2019-10-04T20:04:50", "endtime": "2019-10-04T20:12:52", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20734.6872736, "baseline": -76.6519764, "starttime": "2019-10-04T20:19:37", "endtime": "2019-10-04T20:26:34", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47477.5475041, "baseline": 576.2175041, "starttime": "2019-10-04T20:19:37", "endtime": "2019-10-04T20:26:34", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.460833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.46222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.46166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.460833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.53444444444, "residual": 0.0, "time": "2019-10-04T20:04:50", "h": 20808.143, "e": -94.898, "z": 46902.771, "f": 51829.75}, {"measurement_type": "EastDown", "angle": 90.526944444444, "residual": 0.0, "time": "2019-10-04T20:06:23", "h": 20807.046, "e": -94.667, "z": 46902.597, "f": 51829.27}, {"measurement_type": "WestUp", "angle": 90.749166666667, "residual": 0.0, "time": "2019-10-04T20:11:04", "h": 20808.479, "e": -95.286, "z": 46902.449, "f": 51829.62}, {"measurement_type": "EastUp", "angle": 270.79166666667, "residual": 0.0, "time": "2019-10-04T20:12:52", "h": 20808.892, "e": -95.662, "z": 46902.333, "f": 51829.76}, {"measurement_type": "SouthDown", "angle": 246.40277777778, "residual": 0.0, "time": "2019-10-04T20:19:37", "h": 20810.499, "e": -95.627, "z": 46901.719, "f": 51829.79}, {"measurement_type": "NorthUp", "angle": 66.398888888889, "residual": 0.0, "time": "2019-10-04T20:21:38", "h": 20812.409, "e": -95.871, "z": 46901.29, "f": 51830.16}, {"measurement_type": "SouthUp", "angle": 113.58277777778, "residual": 0.0, "time": "2019-10-04T20:24:12", "h": 20811.93, "e": -95.035, "z": 46900.997, "f": 51829.69}, {"measurement_type": "NorthDown", "angle": 293.5875, "residual": 0.0, "time": "2019-10-04T20:26:34", "h": 20810.519, "e": -95.805, "z": 46901.314, "f": 51829.43}], "metadata": {"time": "2019-10-04T19:30:11Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3277444, "baseline": 8.5900511, "starttime": "2019-10-04T20:35:42", "endtime": "2019-10-04T20:42:05", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20731.4563025, "baseline": -79.4454475, "starttime": "2019-10-04T20:47:30", "endtime": "2019-10-04T20:55:31", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47478.4646665, "baseline": 577.4706665, "starttime": "2019-10-04T20:47:30", "endtime": "2019-10-04T20:55:31", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.46194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.46138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.460555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.460555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.53361111111, "residual": 0.0, "time": "2019-10-04T20:35:42", "h": 20811.814, "e": -95.195, "z": 46900.913, "f": 51829.56}, {"measurement_type": "EastDown", "angle": 90.526388888889, "residual": 0.0, "time": "2019-10-04T20:37:09", "h": 20811.572, "e": -94.866, "z": 46900.925, "f": 51829.48}, {"measurement_type": "WestUp", "angle": 90.746944444444, "residual": 0.0, "time": "2019-10-04T20:39:30", "h": 20812.559, "e": -94.937, "z": 46901.004, "f": 51829.94}, {"measurement_type": "EastUp", "angle": 270.79527777778, "residual": 0.0, "time": "2019-10-04T20:42:05", "h": 20811.239, "e": -94.646, "z": 46901.053, "f": 51829.47}, {"measurement_type": "SouthDown", "angle": 246.41583333333, "residual": 0.0, "time": "2019-10-04T20:47:30", "h": 20813.159, "e": -95.598, "z": 46901.119, "f": 51830.29}, {"measurement_type": "NorthUp", "angle": 66.399722222222, "residual": 0.0, "time": "2019-10-04T20:50:13", "h": 20810.906, "e": -94.982, "z": 46900.88, "f": 51829.22}, {"measurement_type": "SouthUp", "angle": 113.58305555556, "residual": 0.0, "time": "2019-10-04T20:53:42", "h": 20810.432, "e": -95.137, "z": 46901.054, "f": 51829.19}, {"measurement_type": "NorthDown", "angle": 293.58638888889, "residual": 0.0, "time": "2019-10-04T20:55:31", "h": 20809.11, "e": -94.408, "z": 46900.923, "f": 51828.56}], "metadata": {"time": "2019-10-04T19:30:11Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3310083, "baseline": 8.5901235, "starttime": "2019-10-04T21:02:48", "endtime": "2019-10-04T21:08:33", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20738.4896155, "baseline": -76.5153845, "starttime": "2019-10-04T21:16:52", "endtime": "2019-10-04T21:26:41", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47477.9382718, "baseline": 576.1670218, "starttime": "2019-10-04T21:16:52", "endtime": "2019-10-04T21:26:41", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.46111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.46138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.460277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.460555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.53777777778, "residual": 0.0, "time": "2019-10-04T21:02:48", "h": 20810.695, "e": -93.494, "z": 46901.145, "f": 51829.32}, {"measurement_type": "EastDown", "angle": 90.531666666667, "residual": 0.0, "time": "2019-10-04T21:04:22", "h": 20812.666, "e": -93.768, "z": 46901.037, "f": 51830.01}, {"measurement_type": "WestUp", "angle": 90.748055555556, "residual": 0.0, "time": "2019-10-04T21:06:31", "h": 20813.105, "e": -94.003, "z": 46901.086, "f": 51830.23}, {"measurement_type": "EastUp", "angle": 270.79666666667, "residual": 0.0, "time": "2019-10-04T21:08:33", "h": 20812.713, "e": -93.887, "z": 46901.247, "f": 51830.26}, {"measurement_type": "SouthDown", "angle": 246.40444444444, "residual": 0.0, "time": "2019-10-04T21:16:52", "h": 20813.452, "e": -93.342, "z": 46901.677, "f": 51830.95}, {"measurement_type": "NorthUp", "angle": 66.395, "residual": 0.0, "time": "2019-10-04T21:19:35", "h": 20813.587, "e": -93.173, "z": 46901.5, "f": 51830.86}, {"measurement_type": "SouthUp", "angle": 113.59, "residual": 0.0, "time": "2019-10-04T21:24:12", "h": 20816.159, "e": -94.024, "z": 46901.911, "f": 51832.2}, {"measurement_type": "NorthDown", "angle": 293.59277777778, "residual": 0.0, "time": "2019-10-04T21:26:41", "h": 20816.822, "e": -93.703, "z": 46901.997, "f": 51832.58}], "metadata": {"time": "2019-10-04T19:30:11Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3731611, "baseline": 8.5852353, "starttime": "2019-10-11T16:46:16", "endtime": "2019-10-11T16:48:56", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20744.2872963, "baseline": -75.2902037, "starttime": "2019-10-11T16:51:05", "endtime": "2019-10-11T16:53:46", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47481.4838557, "baseline": 576.9338557, "starttime": "2019-10-11T16:51:05", "endtime": "2019-10-11T16:53:46", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.46527777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.462777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.463055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.46444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.62, "residual": 0.0, "time": "2019-10-11T16:46:16", "h": 20818.689, "e": -75.878, "z": 46904.995, "f": 51837.21}, {"measurement_type": "EastDown", "angle": 90.54, "residual": 0.0, "time": "2019-10-11T16:47:11", "h": 20819.19, "e": -76.652, "z": 46904.908, "f": 51837.32}, {"measurement_type": "WestUp", "angle": 90.773888888889, "residual": 0.0, "time": "2019-10-11T16:48:00", "h": 20818.528, "e": -77.141, "z": 46905.167, "f": 51837.35}, {"measurement_type": "EastUp", "angle": 270.86111111111, "residual": 0.0, "time": "2019-10-11T16:48:56", "h": 20819.76, "e": -77.46, "z": 46904.837, "f": 51837.51}, {"measurement_type": "SouthDown", "angle": 246.38111111111, "residual": 0.0, "time": "2019-10-11T16:51:05", "h": 20819.576, "e": -77.21, "z": 46904.604, "f": 51837.26}, {"measurement_type": "NorthUp", "angle": 66.381111111111, "residual": 0.0, "time": "2019-10-11T16:51:42", "h": 20819.185, "e": -76.732, "z": 46904.471, "f": 51836.97}, {"measurement_type": "SouthUp", "angle": 113.57972222222, "residual": 0.0, "time": "2019-10-11T16:53:02", "h": 20819.949, "e": -77.151, "z": 46904.557, "f": 51837.37}, {"measurement_type": "NorthDown", "angle": 293.58305555556, "residual": 0.0, "time": "2019-10-11T16:53:46", "h": 20819.6, "e": -77.368, "z": 46904.568, "f": 51837.27}], "metadata": {"time": "2019-10-11T16:46:16Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3667722, "baseline": 8.5863637, "starttime": "2019-10-11T16:57:46", "endtime": "2019-10-11T17:01:17", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20747.1051882, "baseline": -75.1305618, "starttime": "2019-10-11T17:03:31", "endtime": "2019-10-11T17:06:33", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47480.5609335, "baseline": 576.7859335, "starttime": "2019-10-11T17:03:31", "endtime": "2019-10-11T17:06:33", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.463888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.46388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.46388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.462222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60305555556, "residual": 0.0, "time": "2019-10-11T16:57:46", "h": 20820.923, "e": -81.343, "z": 46904.466, "f": 51837.69}, {"measurement_type": "EastDown", "angle": 90.531944444444, "residual": 0.0, "time": "2019-10-11T16:58:53", "h": 20821.835, "e": -80.242, "z": 46903.632, "f": 51837.31}, {"measurement_type": "WestUp", "angle": 90.771944444444, "residual": 0.0, "time": "2019-10-11T17:00:12", "h": 20821.857, "e": -79.375, "z": 46903.704, "f": 51837.36}, {"measurement_type": "EastUp", "angle": 270.86083333333, "residual": 0.0, "time": "2019-10-11T17:01:17", "h": 20822.39, "e": -77.101, "z": 46903.442, "f": 51837.31}, {"measurement_type": "SouthDown", "angle": 246.38083333333, "residual": 0.0, "time": "2019-10-11T17:03:31", "h": 20822.178, "e": -73.245, "z": 46903.912, "f": 51837.6}, {"measurement_type": "NorthUp", "angle": 66.379166666667, "residual": 0.0, "time": "2019-10-11T17:04:32", "h": 20822.147, "e": -72.471, "z": 46903.755, "f": 51837.46}, {"measurement_type": "SouthUp", "angle": 113.58416666667, "residual": 0.0, "time": "2019-10-11T17:05:48", "h": 20822.378, "e": -71.973, "z": 46903.818, "f": 51837.58}, {"measurement_type": "NorthDown", "angle": 293.58944444444, "residual": 0.0, "time": "2019-10-11T17:06:33", "h": 20822.24, "e": -71.182, "z": 46903.615, "f": 51837.36}], "metadata": {"time": "2019-10-11T16:46:16Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3960083, "baseline": 8.5870779, "starttime": "2019-10-11T17:08:25", "endtime": "2019-10-11T17:11:44", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20748.4407386, "baseline": -75.2522614, "starttime": "2019-10-11T17:13:51", "endtime": "2019-10-11T17:15:53", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47479.3825631, "baseline": 576.8603131, "starttime": "2019-10-11T17:13:51", "endtime": "2019-10-11T17:15:53", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.46555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.46388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.461944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.461388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.64138888889, "residual": 0.0, "time": "2019-10-11T17:08:25", "h": 20821.23, "e": -67.525, "z": 46903.441, "f": 51836.76}, {"measurement_type": "EastDown", "angle": 90.575, "residual": 0.0, "time": "2019-10-11T17:09:41", "h": 20820.12, "e": -66.131, "z": 46903.548, "f": 51836.46}, {"measurement_type": "WestUp", "angle": 90.798333333333, "residual": 0.0, "time": "2019-10-11T17:10:56", "h": 20820.504, "e": -70.044, "z": 46904.338, "f": 51837.36}, {"measurement_type": "EastUp", "angle": 270.86888888889, "residual": 0.0, "time": "2019-10-11T17:11:44", "h": 20822.238, "e": -73.067, "z": 46904.127, "f": 51837.85}, {"measurement_type": "SouthDown", "angle": 246.37888888889, "residual": 0.0, "time": "2019-10-11T17:13:51", "h": 20824.378, "e": -77.557, "z": 46903.034, "f": 51837.7}, {"measurement_type": "NorthUp", "angle": 66.3775, "residual": 0.0, "time": "2019-10-11T17:14:31", "h": 20824.171, "e": -76.306, "z": 46902.477, "f": 51837.09}, {"measurement_type": "SouthUp", "angle": 113.58722222222, "residual": 0.0, "time": "2019-10-11T17:15:20", "h": 20823.259, "e": -74.055, "z": 46902.207, "f": 51836.49}, {"measurement_type": "NorthDown", "angle": 293.59027777778, "residual": 0.0, "time": "2019-10-11T17:15:53", "h": 20822.964, "e": -73.55, "z": 46902.371, "f": 51836.54}], "metadata": {"time": "2019-10-11T16:46:16Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3681611, "baseline": 8.5858421, "starttime": "2019-10-11T17:18:40", "endtime": "2019-10-11T17:21:44", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20749.3559169, "baseline": -75.3033331, "starttime": "2019-10-11T17:24:00", "endtime": "2019-10-11T17:26:50", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47478.496981, "baseline": 576.879731, "starttime": "2019-10-11T17:24:00", "endtime": "2019-10-11T17:26:50", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.46277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.46361111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.463055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.462222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60805555556, "residual": 0.0, "time": "2019-10-11T17:18:40", "h": 20823.217, "e": -78.445, "z": 46902.881, "f": 51837.1}, {"measurement_type": "EastDown", "angle": 90.537222222222, "residual": 0.0, "time": "2019-10-11T17:19:39", "h": 20823.637, "e": -79.326, "z": 46902.393, "f": 51836.84}, {"measurement_type": "WestUp", "angle": 90.773333333333, "residual": 0.0, "time": "2019-10-11T17:20:48", "h": 20822.866, "e": -78.723, "z": 46901.856, "f": 51836.01}, {"measurement_type": "EastUp", "angle": 270.8525, "residual": 0.0, "time": "2019-10-11T17:21:44", "h": 20823.339, "e": -78.834, "z": 46901.846, "f": 51836.21}, {"measurement_type": "SouthDown", "angle": 246.37833333333, "residual": 0.0, "time": "2019-10-11T17:24:00", "h": 20823.931, "e": -80.465, "z": 46901.582, "f": 51836.19}, {"measurement_type": "NorthUp", "angle": 66.377222222222, "residual": 0.0, "time": "2019-10-11T17:24:37", "h": 20824.263, "e": -79.658, "z": 46901.146, "f": 51835.92}, {"measurement_type": "SouthUp", "angle": 113.58888888889, "residual": 0.0, "time": "2019-10-11T17:26:03", "h": 20825.816, "e": -83.095, "z": 46901.769, "f": 51837.07}, {"measurement_type": "NorthDown", "angle": 293.59305555556, "residual": 0.0, "time": "2019-10-11T17:26:50", "h": 20824.627, "e": -85.058, "z": 46901.972, "f": 51836.86}], "metadata": {"time": "2019-10-11T16:46:16Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3760083, "baseline": 8.5925544, "starttime": "2019-10-18T16:51:45", "endtime": "2019-10-18T16:55:24", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20742.7469274, "baseline": -73.8700726, "starttime": "2019-10-18T17:01:28", "endtime": "2019-10-18T17:07:14", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47468.2347677, "baseline": 576.8222677, "starttime": "2019-10-18T17:01:28", "endtime": "2019-10-18T17:07:14", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.51972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.5175, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.516944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.51888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.6625, "residual": 0.0, "time": "2019-10-18T16:51:45", "h": 20816.23, "e": -78.113, "z": 46892.287, "f": 51825.13}, {"measurement_type": "EastDown", "angle": 90.607777777778, "residual": 0.0, "time": "2019-10-18T16:53:04", "h": 20816.508, "e": -78.309, "z": 46892.169, "f": 51825.13}, {"measurement_type": "WestUp", "angle": 90.839722222222, "residual": 0.0, "time": "2019-10-18T16:54:15", "h": 20816.432, "e": -78.554, "z": 46892.182, "f": 51825.12}, {"measurement_type": "EastUp", "angle": 270.91388888889, "residual": 0.0, "time": "2019-10-18T16:55:24", "h": 20816.531, "e": -78.608, "z": 46892.161, "f": 51825.1}, {"measurement_type": "SouthDown", "angle": 246.3825, "residual": 0.0, "time": "2019-10-18T17:01:28", "h": 20816.859, "e": -79.701, "z": 46891.637, "f": 51824.76}, {"measurement_type": "NorthUp", "angle": 66.3825, "residual": 0.0, "time": "2019-10-18T17:03:10", "h": 20816.882, "e": -79.907, "z": 46891.513, "f": 51824.66}, {"measurement_type": "SouthUp", "angle": 113.58972222222, "residual": 0.0, "time": "2019-10-18T17:05:46", "h": 20816.491, "e": -81.171, "z": 46891.367, "f": 51824.39}, {"measurement_type": "NorthDown", "angle": 293.59305555556, "residual": 0.0, "time": "2019-10-18T17:07:14", "h": 20816.236, "e": -81.047, "z": 46891.133, "f": 51824.03}], "metadata": {"time": "2019-10-18T16:51:45Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3574667, "baseline": 8.5887854, "starttime": "2019-10-18T17:11:17", "endtime": "2019-10-18T17:15:48", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20743.079613, "baseline": -73.999637, "starttime": "2019-10-18T17:20:45", "endtime": "2019-10-18T17:24:23", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47465.5466226, "baseline": 576.8813726, "starttime": "2019-10-18T17:20:45", "endtime": "2019-10-18T17:24:23", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.514722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.51666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.515555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.63777777778, "residual": 0.0, "time": "2019-10-18T17:11:17", "h": 20816.288, "e": -82.968, "z": 46890.548, "f": 51823.53}, {"measurement_type": "EastDown", "angle": 90.585, "residual": 0.0, "time": "2019-10-18T17:12:33", "h": 20816.785, "e": -83.562, "z": 46890.368, "f": 51823.55}, {"measurement_type": "WestUp", "angle": 90.831666666667, "residual": 0.0, "time": "2019-10-18T17:14:12", "h": 20816.272, "e": -84.055, "z": 46890.078, "f": 51823.08}, {"measurement_type": "EastUp", "angle": 270.88527777778, "residual": 0.0, "time": "2019-10-18T17:15:48", "h": 20816.552, "e": -84.397, "z": 46889.797, "f": 51822.97}, {"measurement_type": "SouthDown", "angle": 246.38388888889, "residual": 0.0, "time": "2019-10-18T17:20:45", "h": 20816.913, "e": -84.667, "z": 46888.947, "f": 51822.31}, {"measurement_type": "NorthUp", "angle": 66.380833333333, "residual": 0.0, "time": "2019-10-18T17:21:46", "h": 20817.184, "e": -85.536, "z": 46888.812, "f": 51822.31}, {"measurement_type": "SouthUp", "angle": 113.59277777778, "residual": 0.0, "time": "2019-10-18T17:23:22", "h": 20817.289, "e": -86.473, "z": 46888.534, "f": 51822.09}, {"measurement_type": "NorthDown", "angle": 293.59583333333, "residual": 0.0, "time": "2019-10-18T17:24:23", "h": 20816.931, "e": -86.941, "z": 46888.368, "f": 51821.81}], "metadata": {"time": "2019-10-18T16:51:45Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3438556, "baseline": 8.585491, "starttime": "2019-10-18T17:28:01", "endtime": "2019-10-18T17:31:26", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20740.17909, "baseline": -74.55591, "starttime": "2019-10-18T17:35:44", "endtime": "2019-10-18T17:39:07", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47464.0831149, "baseline": 577.1428649, "starttime": "2019-10-18T17:35:44", "endtime": "2019-10-18T17:39:07", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.51416666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51305555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.511666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.511111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.61777777778, "residual": 0.0, "time": "2019-10-18T17:28:01", "h": 20816.859, "e": -87.136, "z": 46887.738, "f": 51821.19}, {"measurement_type": "EastDown", "angle": 90.573055555556, "residual": 0.0, "time": "2019-10-18T17:29:21", "h": 20815.965, "e": -87.166, "z": 46887.559, "f": 51820.69}, {"measurement_type": "WestUp", "angle": 90.8125, "residual": 0.0, "time": "2019-10-18T17:30:29", "h": 20816.169, "e": -87.222, "z": 46887.481, "f": 51820.68}, {"measurement_type": "EastUp", "angle": 270.86888888889, "residual": 0.0, "time": "2019-10-18T17:31:26", "h": 20816.579, "e": -88.349, "z": 46887.56, "f": 51820.91}, {"measurement_type": "SouthDown", "angle": 246.38555555556, "residual": 0.0, "time": "2019-10-18T17:35:44", "h": 20814.616, "e": -90.056, "z": 46887.05, "f": 51819.68}, {"measurement_type": "NorthUp", "angle": 66.385, "residual": 0.0, "time": "2019-10-18T17:36:46", "h": 20814.76, "e": -90.464, "z": 46886.996, "f": 51819.66}, {"measurement_type": "SouthUp", "angle": 113.59111111111, "residual": 0.0, "time": "2019-10-18T17:37:54", "h": 20815.229, "e": -91.177, "z": 46886.917, "f": 51819.81}, {"measurement_type": "NorthDown", "angle": 293.59416666667, "residual": 0.0, "time": "2019-10-18T17:39:07", "h": 20814.335, "e": -91.087, "z": 46886.798, "f": 51819.36}], "metadata": {"time": "2019-10-18T16:51:45Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3303139, "baseline": 8.5870591, "starttime": "2019-10-18T17:42:33", "endtime": "2019-10-18T17:45:11", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20739.3921268, "baseline": -75.0066232, "starttime": "2019-10-18T17:50:22", "endtime": "2019-10-18T17:53:50", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47464.0068348, "baseline": 577.3738348, "starttime": "2019-10-18T17:50:22", "endtime": "2019-10-18T17:53:50", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.51194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51027777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.509166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.508333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60916666667, "residual": 0.0, "time": "2019-10-18T17:42:33", "h": 20814.181, "e": -92.317, "z": 46886.679, "f": 51819.22}, {"measurement_type": "EastDown", "angle": 90.551944444444, "residual": 0.0, "time": "2019-10-18T17:43:44", "h": 20814.339, "e": -93.027, "z": 46886.81, "f": 51819.34}, {"measurement_type": "WestUp", "angle": 90.797777777778, "residual": 0.0, "time": "2019-10-18T17:45:02", "h": 20814.335, "e": -93.108, "z": 46886.695, "f": 51819.23}, {"measurement_type": "EastUp", "angle": 270.84888888889, "residual": 0.0, "time": "2019-10-18T17:45:11", "h": 20814.214, "e": -93.285, "z": 46886.724, "f": 51819.26}, {"measurement_type": "SouthDown", "angle": 246.38666666667, "residual": 0.0, "time": "2019-10-18T17:50:22", "h": 20814.415, "e": -94.988, "z": 46886.657, "f": 51819.31}, {"measurement_type": "NorthUp", "angle": 66.385833333333, "residual": 0.0, "time": "2019-10-18T17:51:18", "h": 20814.175, "e": -94.941, "z": 46886.676, "f": 51819.17}, {"measurement_type": "SouthUp", "angle": 113.58916666667, "residual": 0.0, "time": "2019-10-18T17:52:57", "h": 20814.407, "e": -95.315, "z": 46886.616, "f": 51819.23}, {"measurement_type": "NorthDown", "angle": 293.595, "residual": 0.0, "time": "2019-10-18T17:53:50", "h": 20814.598, "e": -95.417, "z": 46886.583, "f": 51819.26}], "metadata": {"time": "2019-10-18T16:51:45Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3235778, "baseline": 8.5829313, "starttime": "2019-10-23T21:59:28", "endtime": "2019-10-23T22:06:25", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20755.2186351, "baseline": -70.6576149, "starttime": "2019-10-23T22:11:04", "endtime": "2019-10-23T22:16:02", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47473.0940449, "baseline": 575.9522949, "starttime": "2019-10-23T22:11:04", "endtime": "2019-10-23T22:16:02", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.50638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.503611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.503888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.50666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60222222222, "residual": 0.0, "time": "2019-10-23T21:59:28", "h": 20824.692, "e": -94.65, "z": 46897.053, "f": 51833.3}, {"measurement_type": "EastDown", "angle": 90.535833333333, "residual": 0.0, "time": "2019-10-23T22:01:47", "h": 20824.364, "e": -94.161, "z": 46897.1, "f": 51833.29}, {"measurement_type": "WestUp", "angle": 90.769444444444, "residual": 0.0, "time": "2019-10-23T22:03:45", "h": 20824.893, "e": -93.79, "z": 46897.028, "f": 51833.42}, {"measurement_type": "EastUp", "angle": 270.85416666667, "residual": 0.0, "time": "2019-10-23T22:06:25", "h": 20825.205, "e": -93.199, "z": 46897.151, "f": 51833.65}, {"measurement_type": "SouthDown", "angle": 246.37111111111, "residual": 0.0, "time": "2019-10-23T22:11:04", "h": 20825.372, "e": -92.843, "z": 46897.159, "f": 51833.73}, {"measurement_type": "NorthUp", "angle": 66.368888888889, "residual": 0.0, "time": "2019-10-23T22:12:31", "h": 20825.98, "e": -92.648, "z": 46897.144, "f": 51833.96}, {"measurement_type": "SouthUp", "angle": 113.59777777778, "residual": 0.0, "time": "2019-10-23T22:14:42", "h": 20825.939, "e": -92.392, "z": 46897.153, "f": 51833.93}, {"measurement_type": "NorthDown", "angle": 293.60194444444, "residual": 0.0, "time": "2019-10-23T22:16:02", "h": 20826.214, "e": -92.187, "z": 46897.111, "f": 51834.01}], "metadata": {"time": "2019-10-23T21:59:28Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3294806, "baseline": 8.5810655, "starttime": "2019-10-23T22:19:56", "endtime": "2019-10-23T22:25:40", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20755.9291311, "baseline": -71.1853689, "starttime": "2019-10-23T22:30:33", "endtime": "2019-10-23T22:35:17", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47472.8379801, "baseline": 576.1724801, "starttime": "2019-10-23T22:30:33", "endtime": "2019-10-23T22:35:17", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.503611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.50666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.50555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.504444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60472222222, "residual": 0.0, "time": "2019-10-23T22:19:56", "h": 20825.845, "e": -91.474, "z": 46897.087, "f": 51833.84}, {"measurement_type": "EastDown", "angle": 90.541111111111, "residual": 0.0, "time": "2019-10-23T22:22:07", "h": 20826.005, "e": -91.329, "z": 46896.99, "f": 51833.8}, {"measurement_type": "WestUp", "angle": 90.781666666667, "residual": 0.0, "time": "2019-10-23T22:23:48", "h": 20826.033, "e": -90.988, "z": 46896.942, "f": 51833.78}, {"measurement_type": "EastUp", "angle": 270.8575, "residual": 0.0, "time": "2019-10-23T22:25:40", "h": 20826.314, "e": -90.765, "z": 46896.918, "f": 51833.85}, {"measurement_type": "SouthDown", "angle": 246.37055555556, "residual": 0.0, "time": "2019-10-23T22:30:33", "h": 20826.877, "e": -90.067, "z": 46896.727, "f": 51833.92}, {"measurement_type": "NorthUp", "angle": 66.368888888889, "residual": 0.0, "time": "2019-10-23T22:32:03", "h": 20827.067, "e": -89.803, "z": 46896.677, "f": 51833.93}, {"measurement_type": "SouthUp", "angle": 113.59944444444, "residual": 0.0, "time": "2019-10-23T22:34:03", "h": 20827.164, "e": -89.458, "z": 46896.605, "f": 51833.97}, {"measurement_type": "NorthDown", "angle": 293.60305555556, "residual": 0.0, "time": "2019-10-23T22:35:17", "h": 20827.35, "e": -89.297, "z": 46896.653, "f": 51834.01}], "metadata": {"time": "2019-10-23T21:59:28Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3365639, "baseline": 8.5804667, "starttime": "2019-10-23T22:40:42", "endtime": "2019-10-23T22:46:40", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20756.6163859, "baseline": -70.9046141, "starttime": "2019-10-23T22:50:49", "endtime": "2019-10-23T22:55:39", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47471.9017483, "baseline": 576.0267483, "starttime": "2019-10-23T22:50:49", "endtime": "2019-10-23T22:55:39", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.50611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.50611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.504444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.504166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.61305555556, "residual": 0.0, "time": "2019-10-23T22:40:42", "h": 20827.287, "e": -88.671, "z": 46896.46, "f": 51833.82}, {"measurement_type": "EastDown", "angle": 90.549166666667, "residual": 0.0, "time": "2019-10-23T22:42:37", "h": 20827.51, "e": -88.465, "z": 46896.402, "f": 51833.85}, {"measurement_type": "WestUp", "angle": 90.790555555556, "residual": 0.0, "time": "2019-10-23T22:44:54", "h": 20827.504, "e": -88.195, "z": 46896.26, "f": 51833.75}, {"measurement_type": "EastUp", "angle": 270.86111111111, "residual": 0.0, "time": "2019-10-23T22:46:40", "h": 20827.558, "e": -88.105, "z": 46896.209, "f": 51833.69}, {"measurement_type": "SouthDown", "angle": 246.37055555556, "residual": 0.0, "time": "2019-10-23T22:50:49", "h": 20827.227, "e": -87.364, "z": 46895.995, "f": 51833.39}, {"measurement_type": "NorthUp", "angle": 66.368888888889, "residual": 0.0, "time": "2019-10-23T22:52:36", "h": 20827.35, "e": -87.158, "z": 46895.914, "f": 51833.33}, {"measurement_type": "SouthUp", "angle": 113.60138888889, "residual": 0.0, "time": "2019-10-23T22:54:23", "h": 20827.65, "e": -87.029, "z": 46895.821, "f": 51833.37}, {"measurement_type": "NorthDown", "angle": 293.60555555556, "residual": 0.0, "time": "2019-10-23T22:55:39", "h": 20827.857, "e": -86.953, "z": 46895.77, "f": 51833.41}], "metadata": {"time": "2019-10-23T21:59:28Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3433694, "baseline": 8.5814107, "starttime": "2019-10-23T22:58:51", "endtime": "2019-10-23T23:04:07", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20757.6320253, "baseline": -70.9414747, "starttime": "2019-10-23T23:08:35", "endtime": "2019-10-23T23:12:59", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47471.4030861, "baseline": 576.0230861, "starttime": "2019-10-23T23:08:35", "endtime": "2019-10-23T23:12:59", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.50638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.50527777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.504166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.504444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.61833333333, "residual": 0.0, "time": "2019-10-23T22:58:51", "h": 20828.0, "e": -86.474, "z": 46895.641, "f": 51833.37}, {"measurement_type": "EastDown", "angle": 90.555833333333, "residual": 0.0, "time": "2019-10-23T23:00:37", "h": 20828.009, "e": -86.282, "z": 46895.633, "f": 51833.34}, {"measurement_type": "WestUp", "angle": 90.797222222222, "residual": 0.0, "time": "2019-10-23T23:02:17", "h": 20827.987, "e": -86.137, "z": 46895.551, "f": 51833.3}, {"measurement_type": "EastUp", "angle": 270.86916666667, "residual": 0.0, "time": "2019-10-23T23:04:07", "h": 20828.012, "e": -86.066, "z": 46895.524, "f": 51833.24}, {"measurement_type": "SouthDown", "angle": 246.36944444444, "residual": 0.0, "time": "2019-10-23T23:08:35", "h": 20828.354, "e": -85.887, "z": 46895.407, "f": 51833.27}, {"measurement_type": "NorthUp", "angle": 66.368333333333, "residual": 0.0, "time": "2019-10-23T23:09:50", "h": 20828.511, "e": -85.859, "z": 46895.379, "f": 51833.3}, {"measurement_type": "SouthUp", "angle": 113.60305555556, "residual": 0.0, "time": "2019-10-23T23:11:52", "h": 20828.696, "e": -85.905, "z": 46895.378, "f": 51833.36}, {"measurement_type": "NorthDown", "angle": 293.60722222222, "residual": 0.0, "time": "2019-10-23T23:12:59", "h": 20828.733, "e": -85.917, "z": 46895.356, "f": 51833.37}], "metadata": {"time": "2019-10-23T21:59:28Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.410175, "baseline": 8.5868188, "starttime": "2019-10-31T14:53:11", "endtime": "2019-10-31T14:56:50", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20731.4889745, "baseline": -73.7262755, "starttime": "2019-10-31T15:01:58", "endtime": "2019-10-31T15:05:34", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47480.7364038, "baseline": 578.0609038, "starttime": "2019-10-31T15:01:58", "endtime": "2019-10-31T15:05:34", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.51222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.510555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.510277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.51222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.70166666667, "residual": 0.0, "time": "2019-10-31T14:53:11", "h": 20806.732, "e": -64.033, "z": 46902.953, "f": 51832.27}, {"measurement_type": "EastDown", "angle": 90.616944444444, "residual": 0.0, "time": "2019-10-31T14:54:28", "h": 20806.941, "e": -64.069, "z": 46903.015, "f": 51832.37}, {"measurement_type": "WestUp", "angle": 90.853055555556, "residual": 0.0, "time": "2019-10-31T14:55:49", "h": 20805.92, "e": -63.812, "z": 46903.084, "f": 51832.03}, {"measurement_type": "EastUp", "angle": 270.96111111111, "residual": 0.0, "time": "2019-10-31T14:56:50", "h": 20806.143, "e": -63.748, "z": 46902.947, "f": 51831.97}, {"measurement_type": "SouthDown", "angle": 246.39277777778, "residual": 0.0, "time": "2019-10-31T15:01:58", "h": 20805.798, "e": -63.105, "z": 46902.706, "f": 51831.68}, {"measurement_type": "NorthUp", "angle": 66.391666666667, "residual": 0.0, "time": "2019-10-31T15:03:06", "h": 20805.446, "e": -61.752, "z": 46902.482, "f": 51831.32}, {"measurement_type": "SouthUp", "angle": 113.565, "residual": 0.0, "time": "2019-10-31T15:04:43", "h": 20804.798, "e": -61.109, "z": 46902.625, "f": 51831.21}, {"measurement_type": "NorthDown", "angle": 293.56944444444, "residual": 0.0, "time": "2019-10-31T15:05:34", "h": 20804.819, "e": -61.308, "z": 46902.889, "f": 51831.43}], "metadata": {"time": "2019-10-31T14:53:11Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.4106611, "baseline": 8.585027, "starttime": "2019-10-31T15:09:06", "endtime": "2019-10-31T15:12:17", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20730.0795662, "baseline": -73.7494338, "starttime": "2019-10-31T15:17:18", "endtime": "2019-10-31T15:20:58", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47479.8623408, "baseline": 578.0578408, "starttime": "2019-10-31T15:17:18", "endtime": "2019-10-31T15:20:58", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.510833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.51361111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.511944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.70583333333, "residual": 0.0, "time": "2019-10-31T15:09:06", "h": 20804.721, "e": -62.161, "z": 46902.479, "f": 51830.98}, {"measurement_type": "EastDown", "angle": 90.625277777778, "residual": 0.0, "time": "2019-10-31T15:10:10", "h": 20804.938, "e": -62.913, "z": 46902.686, "f": 51831.32}, {"measurement_type": "WestUp", "angle": 90.853611111111, "residual": 0.0, "time": "2019-10-31T15:11:24", "h": 20805.137, "e": -63.708, "z": 46902.802, "f": 51831.47}, {"measurement_type": "EastUp", "angle": 270.95388888889, "residual": 0.0, "time": "2019-10-31T15:12:17", "h": 20804.927, "e": -63.566, "z": 46902.551, "f": 51831.14}, {"measurement_type": "SouthDown", "angle": 246.39666666667, "residual": 0.0, "time": "2019-10-31T15:17:18", "h": 20803.901, "e": -64.406, "z": 46901.972, "f": 51830.22}, {"measurement_type": "NorthUp", "angle": 66.392777777778, "residual": 0.0, "time": "2019-10-31T15:18:25", "h": 20803.835, "e": -65.518, "z": 46902.102, "f": 51830.31}, {"measurement_type": "SouthUp", "angle": 113.56583333333, "residual": 0.0, "time": "2019-10-31T15:20:04", "h": 20803.698, "e": -64.681, "z": 46901.754, "f": 51829.96}, {"measurement_type": "NorthDown", "angle": 293.56944444444, "residual": 0.0, "time": "2019-10-31T15:20:58", "h": 20803.882, "e": -64.766, "z": 46901.39, "f": 51829.69}], "metadata": {"time": "2019-10-31T14:53:11Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.407675, "baseline": 8.5869352, "starttime": "2019-10-31T15:23:16", "endtime": "2019-10-31T15:26:54", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20727.7415164, "baseline": -74.8297336, "starttime": "2019-10-31T15:31:18", "endtime": "2019-10-31T15:34:29", "shift": 0.0, "valid": false}, {"element": "Z", "absolute": 47479.0581545, "baseline": 578.5144045, "starttime": "2019-10-31T15:31:18", "endtime": "2019-10-31T15:34:29", "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.51194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.5125, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.511944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.5125, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.69888888889, "residual": 0.0, "time": "2019-10-31T15:23:16", "h": 20803.484, "e": -64.525, "z": 46901.343, "f": 51829.49}, {"measurement_type": "EastDown", "angle": 90.620277777778, "residual": 0.0, "time": "2019-10-31T15:24:26", "h": 20803.329, "e": -64.884, "z": 46901.249, "f": 51829.36}, {"measurement_type": "WestUp", "angle": 90.859444444444, "residual": 0.0, "time": "2019-10-31T15:25:46", "h": 20803.659, "e": -64.623, "z": 46901.095, "f": 51829.36}, {"measurement_type": "EastUp", "angle": 270.94777777778, "residual": 0.0, "time": "2019-10-31T15:26:54", "h": 20803.727, "e": -65.37, "z": 46901.182, "f": 51829.45}, {"measurement_type": "SouthDown", "angle": 246.39666666667, "residual": 0.0, "time": "2019-10-31T15:31:18", "h": 20802.764, "e": -64.087, "z": 46900.635, "f": 51828.53}, {"measurement_type": "NorthUp", "angle": 66.395, "residual": 0.0, "time": "2019-10-31T15:32:11", "h": 20802.64, "e": -64.587, "z": 46900.692, "f": 51828.54}, {"measurement_type": "SouthUp", "angle": 113.56194444444, "residual": 0.0, "time": "2019-10-31T15:33:30", "h": 20802.467, "e": -64.581, "z": 46900.428, "f": 51828.23}, {"measurement_type": "NorthDown", "angle": 293.5675, "residual": 0.0, "time": "2019-10-31T15:34:29", "h": 20802.414, "e": -64.673, "z": 46900.42, "f": 51828.19}], "metadata": {"time": "2019-10-31T14:53:11Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.4271889, "baseline": 8.6022191, "starttime": "2019-10-31T15:37:10", "endtime": "2019-10-31T15:41:31", "shift": 0.0, "valid": false}, {"element": "H", "absolute": 20726.5219722, "baseline": -73.9187778, "starttime": "2019-10-31T15:46:38", "endtime": "2019-10-31T15:50:49", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47477.5201223, "baseline": 578.1318723, "starttime": "2019-10-31T15:46:38", "endtime": "2019-10-31T15:50:49", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.5125, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.509166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.510277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.7, "residual": 0.0, "time": "2019-10-31T15:37:10", "h": 20801.847, "e": -63.192, "z": 46900.063, "f": 51827.61}, {"measurement_type": "EastDown", "angle": 90.625277777778, "residual": 0.0, "time": "2019-10-31T15:38:29", "h": 20801.658, "e": -63.486, "z": 46900.143, "f": 51827.62}, {"measurement_type": "WestUp", "angle": 90.884166666667, "residual": 0.0, "time": "2019-10-31T15:40:13", "h": 20801.397, "e": -63.063, "z": 46899.995, "f": 51827.42}, {"measurement_type": "EastUp", "angle": 270.99027777778, "residual": 0.0, "time": "2019-10-31T15:41:31", "h": 20801.407, "e": -63.525, "z": 46899.827, "f": 51827.28}, {"measurement_type": "SouthDown", "angle": 246.39944444444, "residual": 0.0, "time": "2019-10-31T15:46:38", "h": 20800.399, "e": -63.51, "z": 46899.624, "f": 51826.67}, {"measurement_type": "NorthUp", "angle": 66.399444444444, "residual": 0.0, "time": "2019-10-31T15:47:48", "h": 20800.611, "e": -64.382, "z": 46899.419, "f": 51826.56}, {"measurement_type": "SouthUp", "angle": 113.565, "residual": 0.0, "time": "2019-10-31T15:49:50", "h": 20800.36, "e": -64.61, "z": 46899.343, "f": 51826.39}, {"measurement_type": "NorthDown", "angle": 293.56944444444, "residual": 0.0, "time": "2019-10-31T15:50:49", "h": 20800.393, "e": -65.307, "z": 46899.167, "f": 51826.28}], "metadata": {"time": "2019-10-31T14:53:11Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3889944, "baseline": 8.5848531, "starttime": "2019-11-05T14:25:32", "endtime": "2019-11-05T14:36:20", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20740.6853153, "baseline": -76.9266847, "starttime": "2019-11-05T14:46:11", "endtime": "2019-11-05T14:54:45", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47477.63115, "baseline": 578.84015, "starttime": "2019-11-05T14:46:11", "endtime": "2019-11-05T14:54:45", "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.51305555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.510833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.510555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.51305555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.68583333333, "residual": 0.0, "time": "2019-11-05T14:25:32", "h": 20821.691, "e": -70.003, "z": 46898.226, "f": 51833.3}, {"measurement_type": "EastDown", "angle": 90.605, "residual": 0.0, "time": "2019-11-05T14:29:40", "h": 20820.954, "e": -68.543, "z": 46898.588, "f": 51833.41}, {"measurement_type": "WestUp", "angle": 90.833333333333, "residual": 0.0, "time": "2019-11-05T14:34:05", "h": 20821.403, "e": -72.735, "z": 46898.774, "f": 51833.74}, {"measurement_type": "EastUp", "angle": 270.92611111111, "residual": 0.0, "time": "2019-11-05T14:36:20", "h": 20821.719, "e": -72.317, "z": 46898.472, "f": 51833.62}, {"measurement_type": "SouthDown", "angle": 246.37861111111, "residual": 0.0, "time": "2019-11-05T14:46:11", "h": 20818.243, "e": -71.09, "z": 46898.878, "f": 51832.55}, {"measurement_type": "NorthUp", "angle": 66.378611111111, "residual": 0.0, "time": "2019-11-05T14:49:58", "h": 20818.128, "e": -69.954, "z": 46898.571, "f": 51832.23}, {"measurement_type": "SouthUp", "angle": 113.56555555556, "residual": 0.0, "time": "2019-11-05T14:52:51", "h": 20817.154, "e": -69.272, "z": 46898.957, "f": 51832.22}, {"measurement_type": "NorthDown", "angle": 293.58444444444, "residual": 0.0, "time": "2019-11-05T14:54:45", "h": 20816.923, "e": -67.183, "z": 46898.758, "f": 51831.98}], "metadata": {"time": "2019-11-05T14:25:32Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.4164944, "baseline": 8.5964637, "starttime": "2019-11-05T15:06:51", "endtime": "2019-11-05T15:13:04", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20739.0420225, "baseline": -72.9559775, "starttime": "2019-11-05T15:20:46", "endtime": "2019-11-05T15:29:18", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47474.4968913, "baseline": 577.1096413, "starttime": "2019-11-05T15:20:46", "endtime": "2019-11-05T15:29:18", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.51, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.51277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.509444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.68888888889, "residual": 0.0, "time": "2019-11-05T15:06:51", "h": 20815.601, "e": -65.707, "z": 46899.369, "f": 51831.95}, {"measurement_type": "EastDown", "angle": 90.641111111111, "residual": 0.0, "time": "2019-11-05T15:08:51", "h": 20815.678, "e": -65.259, "z": 46898.976, "f": 51831.6}, {"measurement_type": "WestUp", "angle": 90.858888888889, "residual": 0.0, "time": "2019-11-05T15:11:26", "h": 20814.672, "e": -64.172, "z": 46899.033, "f": 51831.28}, {"measurement_type": "EastUp", "angle": 270.96888888889, "residual": 0.0, "time": "2019-11-05T15:13:04", "h": 20814.941, "e": -65.432, "z": 46898.878, "f": 51831.24}, {"measurement_type": "SouthDown", "angle": 246.38472222222, "residual": 0.0, "time": "2019-11-05T15:20:46", "h": 20813.615, "e": -65.087, "z": 46897.484, "f": 51829.49}, {"measurement_type": "NorthUp", "angle": 66.383333333333, "residual": 0.0, "time": "2019-11-05T15:22:46", "h": 20813.299, "e": -63.884, "z": 46897.476, "f": 51829.29}, {"measurement_type": "SouthUp", "angle": 113.57916666667, "residual": 0.0, "time": "2019-11-05T15:27:03", "h": 20811.43, "e": -62.145, "z": 46897.411, "f": 51828.47}, {"measurement_type": "NorthDown", "angle": 293.58055555556, "residual": 0.0, "time": "2019-11-05T15:29:18", "h": 20809.648, "e": -61.334, "z": 46897.178, "f": 51827.61}], "metadata": {"time": "2019-11-05T14:25:32Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.426425, "baseline": 8.5965166, "starttime": "2019-11-05T15:40:34", "endtime": "2019-11-05T15:49:02", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20731.2488351, "baseline": -71.9544149, "starttime": "2019-11-05T15:56:52", "endtime": "2019-11-05T16:07:16", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47470.4586427, "baseline": 576.7401427, "starttime": "2019-11-05T15:56:52", "endtime": "2019-11-05T16:07:16", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.51277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.510555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.51, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.6975, "residual": 0.0, "time": "2019-11-05T15:40:34", "h": 20809.207, "e": -61.147, "z": 46896.049, "f": 51826.22}, {"measurement_type": "EastDown", "angle": 90.658888888889, "residual": 0.0, "time": "2019-11-05T15:42:28", "h": 20807.861, "e": -60.49, "z": 46895.991, "f": 51825.8}, {"measurement_type": "WestUp", "angle": 90.866944444444, "residual": 0.0, "time": "2019-11-05T15:47:25", "h": 20807.232, "e": -62.562, "z": 46895.756, "f": 51825.31}, {"measurement_type": "EastUp", "angle": 270.97416666667, "residual": 0.0, "time": "2019-11-05T15:49:02", "h": 20806.594, "e": -61.977, "z": 46895.146, "f": 51824.49}, {"measurement_type": "SouthDown", "angle": 246.39055555556, "residual": 0.0, "time": "2019-11-05T15:56:52", "h": 20803.112, "e": -64.89, "z": 46894.909, "f": 51822.97}, {"measurement_type": "NorthUp", "angle": 66.391111111111, "residual": 0.0, "time": "2019-11-05T16:01:14", "h": 20803.393, "e": -68.448, "z": 46893.655, "f": 51821.9}, {"measurement_type": "SouthUp", "angle": 113.57277777778, "residual": 0.0, "time": "2019-11-05T16:05:14", "h": 20803.274, "e": -70.968, "z": 46893.264, "f": 51821.5}, {"measurement_type": "NorthDown", "angle": 293.57611111111, "residual": 0.0, "time": "2019-11-05T16:07:16", "h": 20803.034, "e": -72.348, "z": 46893.046, "f": 51821.21}], "metadata": {"time": "2019-11-05T14:25:32Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3929528, "baseline": 8.6006377, "starttime": "2019-11-05T16:17:47", "endtime": "2019-11-05T16:25:56", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.1901886, "baseline": -73.2228114, "starttime": "2019-11-05T16:33:02", "endtime": "2019-11-05T16:43:49", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47465.9646503, "baseline": 577.2966503, "starttime": "2019-11-05T16:33:02", "endtime": "2019-11-05T16:43:49", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.51194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.508888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.51, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.66305555556, "residual": 0.0, "time": "2019-11-05T16:17:47", "h": 20802.761, "e": -74.232, "z": 46891.372, "f": 51819.62}, {"measurement_type": "EastDown", "angle": 90.629444444444, "residual": 0.0, "time": "2019-11-05T16:19:40", "h": 20803.363, "e": -74.872, "z": 46891.035, "f": 51819.48}, {"measurement_type": "WestUp", "angle": 90.831111111111, "residual": 0.0, "time": "2019-11-05T16:23:01", "h": 20802.513, "e": -76.095, "z": 46890.519, "f": 51818.63}, {"measurement_type": "EastUp", "angle": 270.9375, "residual": 0.0, "time": "2019-11-05T16:25:56", "h": 20802.356, "e": -75.342, "z": 46890.226, "f": 51818.42}, {"measurement_type": "SouthDown", "angle": 246.39333333333, "residual": 0.0, "time": "2019-11-05T16:33:02", "h": 20801.996, "e": -77.523, "z": 46889.526, "f": 51817.65}, {"measurement_type": "NorthUp", "angle": 66.394166666667, "residual": 0.0, "time": "2019-11-05T16:36:19", "h": 20801.434, "e": -76.952, "z": 46889.061, "f": 51816.94}, {"measurement_type": "SouthUp", "angle": 113.57333333333, "residual": 0.0, "time": "2019-11-05T16:40:20", "h": 20801.35, "e": -76.116, "z": 46888.167, "f": 51816.07}, {"measurement_type": "NorthDown", "angle": 293.57694444444, "residual": 0.0, "time": "2019-11-05T16:43:49", "h": 20800.872, "e": -75.743, "z": 46887.918, "f": 51815.55}], "metadata": {"time": "2019-11-05T14:25:32Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2960778, "baseline": 8.5833573, "starttime": "2019-11-15T19:39:38", "endtime": "2019-11-15T19:43:50", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20737.0429141, "baseline": -73.6035859, "starttime": "2019-11-15T19:49:07", "endtime": "2019-11-15T19:53:57", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47461.6093694, "baseline": 577.5318694, "starttime": "2019-11-15T19:49:07", "endtime": "2019-11-15T19:53:57", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.52027777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.519444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.52, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.51944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.58527777778, "residual": 0.0, "time": "2019-11-15T19:39:38", "h": 20806.456, "e": -104.704, "z": 46883.26, "f": 51813.73}, {"measurement_type": "EastDown", "angle": 90.518888888889, "residual": 0.0, "time": "2019-11-15T19:40:53", "h": 20805.187, "e": -103.83, "z": 46883.373, "f": 51813.3}, {"measurement_type": "WestUp", "angle": 90.766944444444, "residual": 0.0, "time": "2019-11-15T19:42:10", "h": 20807.352, "e": -103.663, "z": 46883.187, "f": 51813.95}, {"measurement_type": "EastUp", "angle": 270.83916666667, "residual": 0.0, "time": "2019-11-15T19:43:50", "h": 20807.983, "e": -103.703, "z": 46883.366, "f": 51814.38}, {"measurement_type": "SouthDown", "angle": 246.38472222222, "residual": 0.0, "time": "2019-11-15T19:49:07", "h": 20810.235, "e": -105.622, "z": 46883.984, "f": 51815.84}, {"measurement_type": "NorthUp", "angle": 66.384166666667, "residual": 0.0, "time": "2019-11-15T19:50:17", "h": 20811.065, "e": -107.47, "z": 46884.192, "f": 51816.35}, {"measurement_type": "SouthUp", "angle": 113.58527777778, "residual": 0.0, "time": "2019-11-15T19:52:27", "h": 20811.186, "e": -109.175, "z": 46884.11, "f": 51816.39}, {"measurement_type": "NorthDown", "angle": 293.59, "residual": 0.0, "time": "2019-11-15T19:53:57", "h": 20810.1, "e": -108.8, "z": 46884.024, "f": 51815.84}], "metadata": {"time": "2019-11-15T19:39:38Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Payton Cain", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2865639, "baseline": 8.5837509, "starttime": "2019-11-15T19:57:48", "endtime": "2019-11-15T20:02:24", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20734.9635056, "baseline": -73.2137444, "starttime": "2019-11-15T20:09:34", "endtime": "2019-11-15T20:13:12", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47462.6515351, "baseline": 577.3725351, "starttime": "2019-11-15T20:09:34", "endtime": "2019-11-15T20:13:12", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.519722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.52055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.519444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.57472222222, "residual": 0.0, "time": "2019-11-15T19:57:48", "h": 20808.884, "e": -107.558, "z": 46884.186, "f": 51815.51}, {"measurement_type": "EastDown", "angle": 90.513055555556, "residual": 0.0, "time": "2019-11-15T19:59:29", "h": 20808.951, "e": -107.437, "z": 46884.266, "f": 51815.57}, {"measurement_type": "WestUp", "angle": 90.758333333333, "residual": 0.0, "time": "2019-11-15T20:01:04", "h": 20809.883, "e": -107.837, "z": 46884.434, "f": 51816.11}, {"measurement_type": "EastUp", "angle": 270.82722222222, "residual": 0.0, "time": "2019-11-15T20:02:24", "h": 20809.098, "e": -107.368, "z": 46884.541, "f": 51815.85}, {"measurement_type": "SouthDown", "angle": 246.38472222222, "residual": 0.0, "time": "2019-11-15T20:09:34", "h": 20809.298, "e": -107.028, "z": 46885.128, "f": 51816.51}, {"measurement_type": "NorthUp", "angle": 66.387222222222, "residual": 0.0, "time": "2019-11-15T20:10:37", "h": 20808.402, "e": -106.587, "z": 46885.21, "f": 51816.29}, {"measurement_type": "SouthUp", "angle": 113.58305555556, "residual": 0.0, "time": "2019-11-15T20:12:13", "h": 20808.162, "e": -106.147, "z": 46885.338, "f": 51816.27}, {"measurement_type": "NorthDown", "angle": 293.585, "residual": 0.0, "time": "2019-11-15T20:13:12", "h": 20806.847, "e": -105.639, "z": 46885.44, "f": 51815.84}], "metadata": {"time": "2019-11-15T19:39:38Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Payton Cain", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2893417, "baseline": 8.5826696, "starttime": "2019-11-15T20:16:28", "endtime": "2019-11-15T20:20:23", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20737.0723552, "baseline": -73.7093948, "starttime": "2019-11-15T20:25:16", "endtime": "2019-11-15T20:29:00", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47464.185567, "baseline": 577.534317, "starttime": "2019-11-15T20:25:16", "endtime": "2019-11-15T20:29:00", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.52055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.52, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.520555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.57333333333, "residual": 0.0, "time": "2019-11-15T20:16:28", "h": 20810.078, "e": -106.284, "z": 46885.544, "f": 51817.19}, {"measurement_type": "EastDown", "angle": 90.515833333333, "residual": 0.0, "time": "2019-11-15T20:17:54", "h": 20810.236, "e": -106.2, "z": 46885.71, "f": 51817.41}, {"measurement_type": "WestUp", "angle": 90.764722222222, "residual": 0.0, "time": "2019-11-15T20:19:17", "h": 20810.498, "e": -106.231, "z": 46885.886, "f": 51817.71}, {"measurement_type": "EastUp", "angle": 270.83305555556, "residual": 0.0, "time": "2019-11-15T20:20:23", "h": 20810.203, "e": -105.942, "z": 46886.054, "f": 51817.73}, {"measurement_type": "SouthDown", "angle": 246.38944444444, "residual": 0.0, "time": "2019-11-15T20:25:16", "h": 20809.446, "e": -104.517, "z": 46886.476, "f": 51817.8}, {"measurement_type": "NorthUp", "angle": 66.385833333333, "residual": 0.0, "time": "2019-11-15T20:26:26", "h": 20810.551, "e": -104.743, "z": 46886.566, "f": 51818.31}, {"measurement_type": "SouthUp", "angle": 113.58611111111, "residual": 0.0, "time": "2019-11-15T20:27:55", "h": 20811.329, "e": -105.012, "z": 46886.737, "f": 51818.78}, {"measurement_type": "NorthDown", "angle": 293.59111111111, "residual": 0.0, "time": "2019-11-15T20:29:00", "h": 20811.801, "e": -105.063, "z": 46886.826, "f": 51819.02}], "metadata": {"time": "2019-11-15T19:39:38Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Payton Cain", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2952444, "baseline": 8.5843074, "starttime": "2019-11-15T20:31:39", "endtime": "2019-11-15T20:36:29", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20739.8756046, "baseline": -72.8118954, "starttime": "2019-11-15T20:40:09", "endtime": "2019-11-15T20:43:44", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47465.1133182, "baseline": 577.1285682, "starttime": "2019-11-15T20:40:09", "endtime": "2019-11-15T20:43:44", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.52083333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.520277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.519444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.57361111111, "residual": 0.0, "time": "2019-11-15T20:31:39", "h": 20812.886, "e": -104.853, "z": 46887.057, "f": 51819.69}, {"measurement_type": "EastDown", "angle": 90.523333333333, "residual": 0.0, "time": "2019-11-15T20:33:47", "h": 20812.022, "e": -104.438, "z": 46887.379, "f": 51819.6}, {"measurement_type": "WestUp", "angle": 90.772777777778, "residual": 0.0, "time": "2019-11-15T20:35:10", "h": 20812.366, "e": -104.637, "z": 46887.419, "f": 51819.82}, {"measurement_type": "EastUp", "angle": 270.84, "residual": 0.0, "time": "2019-11-15T20:36:29", "h": 20812.438, "e": -104.611, "z": 46887.572, "f": 51819.94}, {"measurement_type": "SouthDown", "angle": 246.38361111111, "residual": 0.0, "time": "2019-11-15T20:40:09", "h": 20812.297, "e": -104.645, "z": 46887.898, "f": 51820.21}, {"measurement_type": "NorthUp", "angle": 66.384444444444, "residual": 0.0, "time": "2019-11-15T20:41:06", "h": 20812.52, "e": -104.751, "z": 46887.938, "f": 51820.32}, {"measurement_type": "SouthUp", "angle": 113.5875, "residual": 0.0, "time": "2019-11-15T20:42:44", "h": 20812.801, "e": -104.801, "z": 46887.987, "f": 51820.51}, {"measurement_type": "NorthDown", "angle": 293.59222222222, "residual": 0.0, "time": "2019-11-15T20:43:44", "h": 20813.132, "e": -105.018, "z": 46888.116, "f": 51820.76}], "metadata": {"time": "2019-11-15T19:39:38Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Payton Cain", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.35205, "baseline": 8.5857466, "starttime": "2019-11-22T17:13:22", "endtime": "2019-11-22T17:17:16", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20718.9083402, "baseline": -73.5929098, "starttime": "2019-11-22T17:21:47", "endtime": "2019-11-22T17:24:31", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47453.1780302, "baseline": 577.6827802, "starttime": "2019-11-22T17:21:47", "endtime": "2019-11-22T17:24:31", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.51972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.519166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.521388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.52138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.655, "residual": 0.0, "time": "2019-11-22T17:13:22", "h": 20796.898, "e": -84.122, "z": 46875.848, "f": 51803.24}, {"measurement_type": "EastDown", "angle": 90.571111111111, "residual": 0.0, "time": "2019-11-22T17:14:33", "h": 20796.441, "e": -84.685, "z": 46875.904, "f": 51803.06}, {"measurement_type": "WestUp", "angle": 90.8075, "residual": 0.0, "time": "2019-11-22T17:16:09", "h": 20796.1, "e": -84.484, "z": 46875.673, "f": 51802.75}, {"measurement_type": "EastUp", "angle": 270.90305555556, "residual": 0.0, "time": "2019-11-22T17:17:16", "h": 20794.962, "e": -84.74, "z": 46876.055, "f": 51802.6}, {"measurement_type": "SouthDown", "angle": 246.39777777778, "residual": 0.0, "time": "2019-11-22T17:21:47", "h": 20791.752, "e": -81.773, "z": 46875.469, "f": 51800.77}, {"measurement_type": "NorthUp", "angle": 66.395, "residual": 0.0, "time": "2019-11-22T17:22:37", "h": 20792.181, "e": -82.128, "z": 46875.293, "f": 51800.81}, {"measurement_type": "SouthUp", "angle": 113.56833333333, "residual": 0.0, "time": "2019-11-22T17:23:35", "h": 20793.064, "e": -83.104, "z": 46875.471, "f": 51801.3}, {"measurement_type": "NorthDown", "angle": 293.57222222222, "residual": 0.0, "time": "2019-11-22T17:24:31", "h": 20793.008, "e": -84.814, "z": 46875.748, "f": 51801.6}], "metadata": {"time": "2019-11-22T17:13:22Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3474667, "baseline": 8.5872032, "starttime": "2019-11-22T17:27:13", "endtime": "2019-11-22T17:30:22", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20714.5712125, "baseline": -73.3217875, "starttime": "2019-11-22T17:35:27", "endtime": "2019-11-22T17:38:33", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47453.2820217, "baseline": 577.6402717, "starttime": "2019-11-22T17:35:27", "endtime": "2019-11-22T17:38:33", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.518888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.52083333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52083333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.518888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.63361111111, "residual": 0.0, "time": "2019-11-22T17:27:13", "h": 20792.001, "e": -87.958, "z": 46875.503, "f": 51801.03}, {"measurement_type": "EastDown", "angle": 90.573333333333, "residual": 0.0, "time": "2019-11-22T17:28:20", "h": 20790.904, "e": -86.412, "z": 46874.847, "f": 51799.85}, {"measurement_type": "WestUp", "angle": 90.815, "residual": 0.0, "time": "2019-11-22T17:29:19", "h": 20791.025, "e": -85.718, "z": 46875.127, "f": 51800.14}, {"measurement_type": "EastUp", "angle": 270.89416666667, "residual": 0.0, "time": "2019-11-22T17:30:22", "h": 20790.057, "e": -86.607, "z": 46875.636, "f": 51800.34}, {"measurement_type": "SouthDown", "angle": 246.40222222222, "residual": 0.0, "time": "2019-11-22T17:35:27", "h": 20787.759, "e": -87.189, "z": 46875.354, "f": 51799.16}, {"measurement_type": "NorthUp", "angle": 66.402222222222, "residual": 0.0, "time": "2019-11-22T17:36:24", "h": 20787.96, "e": -88.018, "z": 46875.743, "f": 51799.5}, {"measurement_type": "SouthUp", "angle": 113.56555555556, "residual": 0.0, "time": "2019-11-22T17:37:41", "h": 20788.189, "e": -88.735, "z": 46875.796, "f": 51799.73}, {"measurement_type": "NorthDown", "angle": 293.56888888889, "residual": 0.0, "time": "2019-11-22T17:38:33", "h": 20787.664, "e": -89.078, "z": 46875.674, "f": 51799.53}], "metadata": {"time": "2019-11-22T17:13:22Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3267028, "baseline": 8.5859662, "starttime": "2019-11-22T17:53:18", "endtime": "2019-11-22T17:56:28", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20716.6286845, "baseline": -73.0848155, "starttime": "2019-11-22T17:58:58", "endtime": "2019-11-22T18:01:44", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47455.7990968, "baseline": 577.5205968, "starttime": "2019-11-22T17:58:58", "endtime": "2019-11-22T18:01:44", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.52027777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.519166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.519166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.61333333333, "residual": 0.0, "time": "2019-11-22T17:53:18", "h": 20789.154, "e": -94.018, "z": 46876.945, "f": 51801.11}, {"measurement_type": "EastDown", "angle": 90.548611111111, "residual": 0.0, "time": "2019-11-22T17:54:11", "h": 20789.604, "e": -94.534, "z": 46877.218, "f": 51801.5}, {"measurement_type": "WestUp", "angle": 90.797777777778, "residual": 0.0, "time": "2019-11-22T17:55:33", "h": 20788.948, "e": -94.02, "z": 46877.609, "f": 51801.65}, {"measurement_type": "EastUp", "angle": 270.87361111111, "residual": 0.0, "time": "2019-11-22T17:56:28", "h": 20788.505, "e": -92.399, "z": 46877.339, "f": 51801.24}, {"measurement_type": "SouthDown", "angle": 246.40333333333, "residual": 0.0, "time": "2019-11-22T17:58:58", "h": 20788.869, "e": -93.865, "z": 46878.187, "f": 51802.19}, {"measurement_type": "NorthUp", "angle": 66.403333333333, "residual": 0.0, "time": "2019-11-22T17:59:51", "h": 20789.436, "e": -94.785, "z": 46878.315, "f": 51802.52}, {"measurement_type": "SouthUp", "angle": 113.56888888889, "residual": 0.0, "time": "2019-11-22T18:00:54", "h": 20790.583, "e": -95.484, "z": 46878.256, "f": 51802.9}, {"measurement_type": "NorthDown", "angle": 293.57166666667, "residual": 0.0, "time": "2019-11-22T18:01:44", "h": 20789.966, "e": -95.421, "z": 46878.356, "f": 51802.83}], "metadata": {"time": "2019-11-22T17:13:22Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3280222, "baseline": 8.5861663, "starttime": "2019-11-22T18:04:12", "endtime": "2019-11-22T18:07:25", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20715.3447228, "baseline": -73.1795272, "starttime": "2019-11-22T18:10:55", "endtime": "2019-11-22T18:14:24", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47457.0933619, "baseline": 577.4976119, "starttime": "2019-11-22T18:10:55", "endtime": "2019-11-22T18:14:24", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.52138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.52, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.518888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60833333333, "residual": 0.0, "time": "2019-11-22T18:04:12", "h": 20787.878, "e": -95.265, "z": 46878.763, "f": 51802.34}, {"measurement_type": "EastDown", "angle": 90.552777777778, "residual": 0.0, "time": "2019-11-22T18:05:28", "h": 20787.416, "e": -94.553, "z": 46878.916, "f": 51802.29}, {"measurement_type": "WestUp", "angle": 90.801666666667, "residual": 0.0, "time": "2019-11-22T18:06:27", "h": 20786.902, "e": -92.239, "z": 46878.262, "f": 51801.54}, {"measurement_type": "EastUp", "angle": 270.87833333333, "residual": 0.0, "time": "2019-11-22T18:07:25", "h": 20786.379, "e": -91.272, "z": 46878.754, "f": 51801.6}, {"measurement_type": "SouthDown", "angle": 246.40694444444, "residual": 0.0, "time": "2019-11-22T18:10:55", "h": 20788.021, "e": -90.434, "z": 46879.019, "f": 51802.49}, {"measurement_type": "NorthUp", "angle": 66.404444444444, "residual": 0.0, "time": "2019-11-22T18:11:48", "h": 20788.711, "e": -91.252, "z": 46879.656, "f": 51803.34}, {"measurement_type": "SouthUp", "angle": 113.56722222222, "residual": 0.0, "time": "2019-11-22T18:13:10", "h": 20788.763, "e": -91.109, "z": 46879.836, "f": 51803.73}, {"measurement_type": "NorthDown", "angle": 293.57055555556, "residual": 0.0, "time": "2019-11-22T18:14:24", "h": 20788.602, "e": -91.34, "z": 46879.872, "f": 51803.57}], "metadata": {"time": "2019-11-22T17:13:22Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3262167, "baseline": 8.5792861, "starttime": "2019-12-04T20:52:40", "endtime": "2019-12-04T20:58:32", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20752.4183859, "baseline": -71.5118641, "starttime": "2019-12-04T21:05:45", "endtime": "2019-12-04T21:10:55", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47461.0468932, "baseline": 576.8293932, "starttime": "2019-12-04T21:05:45", "endtime": "2019-12-04T21:10:55", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.52277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.520833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.521111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.52444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60583333333, "residual": 0.0, "time": "2019-12-04T20:52:40", "h": 20824.494, "e": -91.579, "z": 46884.444, "f": 51822.22}, {"measurement_type": "EastDown", "angle": 90.560555555556, "residual": 0.0, "time": "2019-12-04T20:54:51", "h": 20824.796, "e": -91.6, "z": 46884.462, "f": 51822.26}, {"measurement_type": "WestUp", "angle": 90.813888888889, "residual": 0.0, "time": "2019-12-04T20:56:37", "h": 20824.533, "e": -91.675, "z": 46884.414, "f": 51822.15}, {"measurement_type": "EastUp", "angle": 270.86055555556, "residual": 0.0, "time": "2019-12-04T20:58:32", "h": 20824.541, "e": -91.791, "z": 46884.392, "f": 51822.15}, {"measurement_type": "SouthDown", "angle": 246.37444444444, "residual": 0.0, "time": "2019-12-04T21:05:45", "h": 20824.072, "e": -91.585, "z": 46884.253, "f": 51821.83}, {"measurement_type": "NorthUp", "angle": 66.373888888889, "residual": 0.0, "time": "2019-12-04T21:07:28", "h": 20823.82, "e": -91.403, "z": 46884.223, "f": 51821.72}, {"measurement_type": "SouthUp", "angle": 113.60777777778, "residual": 0.0, "time": "2019-12-04T21:09:41", "h": 20823.928, "e": -91.347, "z": 46884.189, "f": 51821.72}, {"measurement_type": "NorthDown", "angle": 293.61027777778, "residual": 0.0, "time": "2019-12-04T21:10:55", "h": 20823.901, "e": -91.34, "z": 46884.205, "f": 51821.72}], "metadata": {"time": "2019-12-04T20:52:40Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3278833, "baseline": 8.579313, "starttime": "2019-12-04T21:14:35", "endtime": "2019-12-04T21:20:25", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20752.1702599, "baseline": -72.0722401, "starttime": "2019-12-04T21:25:32", "endtime": "2019-12-04T21:30:28", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47461.1062723, "baseline": 577.0365223, "starttime": "2019-12-04T21:25:32", "endtime": "2019-12-04T21:30:28", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.520833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.52277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.521388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60166666667, "residual": 0.0, "time": "2019-12-04T21:14:35", "h": 20823.908, "e": -91.189, "z": 46884.243, "f": 51821.73}, {"measurement_type": "EastDown", "angle": 90.563333333333, "residual": 0.0, "time": "2019-12-04T21:16:47", "h": 20824.05, "e": -91.065, "z": 46884.204, "f": 51821.75}, {"measurement_type": "WestUp", "angle": 90.819444444444, "residual": 0.0, "time": "2019-12-04T21:18:40", "h": 20824.101, "e": -91.024, "z": 46884.202, "f": 51821.79}, {"measurement_type": "EastUp", "angle": 270.86222222222, "residual": 0.0, "time": "2019-12-04T21:20:25", "h": 20824.189, "e": -90.987, "z": 46884.196, "f": 51821.78}, {"measurement_type": "SouthDown", "angle": 246.37527777778, "residual": 0.0, "time": "2019-12-04T21:25:32", "h": 20824.275, "e": -90.972, "z": 46884.144, "f": 51821.76}, {"measurement_type": "NorthUp", "angle": 66.374166666667, "residual": 0.0, "time": "2019-12-04T21:27:30", "h": 20824.207, "e": -91.029, "z": 46884.104, "f": 51821.72}, {"measurement_type": "SouthUp", "angle": 113.60777777778, "residual": 0.0, "time": "2019-12-04T21:29:18", "h": 20824.254, "e": -91.042, "z": 46884.019, "f": 51821.67}, {"measurement_type": "NorthDown", "angle": 293.61027777778, "residual": 0.0, "time": "2019-12-04T21:30:28", "h": 20824.234, "e": -91.028, "z": 46884.012, "f": 51821.66}], "metadata": {"time": "2019-12-04T20:52:40Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3297583, "baseline": 8.5806196, "starttime": "2019-12-04T21:41:30", "endtime": "2019-12-04T21:49:26", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20752.8864576, "baseline": -71.7300424, "starttime": "2019-12-04T21:54:13", "endtime": "2019-12-04T21:59:20", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47460.5502702, "baseline": 576.8780202, "starttime": "2019-12-04T21:54:13", "endtime": "2019-12-04T21:59:20", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.52416666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.520277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.522222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.59805555556, "residual": 0.0, "time": "2019-12-04T21:41:30", "h": 20824.408, "e": -90.965, "z": 46883.846, "f": 51821.54}, {"measurement_type": "EastDown", "angle": 90.571111111111, "residual": 0.0, "time": "2019-12-04T21:44:15", "h": 20824.264, "e": -90.928, "z": 46883.809, "f": 51821.47}, {"measurement_type": "WestUp", "angle": 90.831666666667, "residual": 0.0, "time": "2019-12-04T21:46:39", "h": 20824.597, "e": -90.803, "z": 46883.756, "f": 51821.54}, {"measurement_type": "EastUp", "angle": 270.85611111111, "residual": 0.0, "time": "2019-12-04T21:49:26", "h": 20824.791, "e": -90.758, "z": 46883.729, "f": 51821.63}, {"measurement_type": "SouthDown", "angle": 246.37722222222, "residual": 0.0, "time": "2019-12-04T21:54:13", "h": 20824.781, "e": -91.012, "z": 46883.663, "f": 51821.55}, {"measurement_type": "NorthUp", "angle": 66.377777777778, "residual": 0.0, "time": "2019-12-04T21:55:42", "h": 20824.478, "e": -90.861, "z": 46883.663, "f": 51821.42}, {"measurement_type": "SouthUp", "angle": 113.6125, "residual": 0.0, "time": "2019-12-04T21:57:56", "h": 20824.579, "e": -90.738, "z": 46883.678, "f": 51821.45}, {"measurement_type": "NorthDown", "angle": 293.615, "residual": 0.0, "time": "2019-12-04T21:59:20", "h": 20824.628, "e": -90.744, "z": 46883.685, "f": 51821.5}], "metadata": {"time": "2019-12-04T20:52:40Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3274667, "baseline": 8.5785839, "starttime": "2019-12-04T22:06:53", "endtime": "2019-12-04T22:14:09", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20752.9481431, "baseline": -71.6453569, "starttime": "2019-12-04T22:18:24", "endtime": "2019-12-04T22:24:52", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47459.7511148, "baseline": 576.8583648, "starttime": "2019-12-04T22:18:24", "endtime": "2019-12-04T22:24:52", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.52444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.52388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.522222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.521388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.59055555556, "residual": 0.0, "time": "2019-12-04T22:06:53", "h": 20824.253, "e": -90.993, "z": 46883.491, "f": 51821.18}, {"measurement_type": "EastDown", "angle": 90.5725, "residual": 0.0, "time": "2019-12-04T22:09:46", "h": 20824.423, "e": -91.003, "z": 46883.368, "f": 51821.14}, {"measurement_type": "WestUp", "angle": 90.830277777778, "residual": 0.0, "time": "2019-12-04T22:11:39", "h": 20824.428, "e": -90.941, "z": 46883.284, "f": 51821.04}, {"measurement_type": "EastUp", "angle": 270.85527777778, "residual": 0.0, "time": "2019-12-04T22:14:09", "h": 20824.503, "e": -90.889, "z": 46883.18, "f": 51821.0}, {"measurement_type": "SouthDown", "angle": 246.37805555556, "residual": 0.0, "time": "2019-12-04T22:18:24", "h": 20824.439, "e": -90.925, "z": 46882.983, "f": 51820.81}, {"measurement_type": "NorthUp", "angle": 66.376944444444, "residual": 0.0, "time": "2019-12-04T22:22:18", "h": 20824.656, "e": -90.8, "z": 46882.872, "f": 51820.77}, {"measurement_type": "SouthUp", "angle": 113.61333333333, "residual": 0.0, "time": "2019-12-04T22:22:52", "h": 20824.627, "e": -90.817, "z": 46882.896, "f": 51820.78}, {"measurement_type": "NorthDown", "angle": 293.61583333333, "residual": 0.0, "time": "2019-12-04T22:24:52", "h": 20824.652, "e": -90.911, "z": 46882.82, "f": 51820.73}], "metadata": {"time": "2019-12-04T20:52:40Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3324667, "baseline": 8.5819642, "starttime": "2019-12-04T23:02:30", "endtime": "2019-12-04T23:07:25", "shift": 0.0, "valid": false}, {"element": "H", "absolute": 20747.5913775, "baseline": -75.9928725, "starttime": "2019-12-04T23:12:19", "endtime": "2019-12-04T23:17:10", "shift": 0.0, "valid": false}, {"element": "Z", "absolute": 47462.0740337, "baseline": 578.7445337, "starttime": "2019-12-04T23:12:19", "endtime": "2019-12-04T23:17:10", "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.52166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.523333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.525, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.52166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.91666666667, "residual": 0.0, "time": "2019-12-04T23:02:30", "h": 20823.416, "e": -90.469, "z": 46882.939, "f": 51820.35}, {"measurement_type": "EastDown", "angle": 89.77, "residual": 0.0, "time": "2019-12-04T23:03:50", "h": 20823.41, "e": -90.347, "z": 46882.994, "f": 51820.41}, {"measurement_type": "WestUp", "angle": 90.485, "residual": 0.0, "time": "2019-12-04T23:05:59", "h": 20823.612, "e": -90.251, "z": 46883.118, "f": 51820.59}, {"measurement_type": "EastUp", "angle": 271.69666666667, "residual": 0.0, "time": "2019-12-04T23:07:25", "h": 20823.444, "e": -90.319, "z": 46883.154, "f": 51820.56}, {"measurement_type": "SouthDown", "angle": 246.15, "residual": 0.0, "time": "2019-12-04T23:12:19", "h": 20823.339, "e": -90.376, "z": 46883.295, "f": 51820.63}, {"measurement_type": "NorthUp", "angle": 66.12, "residual": 0.0, "time": "2019-12-04T23:13:36", "h": 20823.43, "e": -90.259, "z": 46883.27, "f": 51820.64}, {"measurement_type": "SouthUp", "angle": 113.35333333333, "residual": 0.0, "time": "2019-12-04T23:15:09", "h": 20823.686, "e": -90.04, "z": 46883.33, "f": 51820.78}, {"measurement_type": "NorthDown", "angle": 293.365, "residual": 0.0, "time": "2019-12-04T23:17:10", "h": 20823.882, "e": -89.794, "z": 46883.423, "f": 51820.97}], "metadata": {"time": "2019-12-04T23:02:30Z", "reviewed": true, "electronics": "0110", "theodolite": "312713", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": -70.8617, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}, {"element": "H", "absolute": null, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}, {"element": "Z", "absolute": null, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "WestDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "NorthDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "EastUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SouthDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "NorthUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SouthUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "EastDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}], "metadata": {"time": "2019-12-04T23:02:30Z", "reviewed": true, "electronics": "0110", "theodolite": "312713", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": -70.8617, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}, {"element": "H", "absolute": null, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}, {"element": "Z", "absolute": null, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "WestUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SouthDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "NorthUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SouthUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "EastDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "NorthDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "EastUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}], "metadata": {"time": "2019-12-04T23:02:30Z", "reviewed": true, "electronics": "0110", "theodolite": "312713", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": -70.8617, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}, {"element": "H", "absolute": null, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}, {"element": "Z", "absolute": null, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SouthUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "EastDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "NorthDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "EastUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SouthDown", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "NorthUp", "angle": 0.0, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}], "metadata": {"time": "2019-12-04T23:02:30Z", "reviewed": true, "electronics": "0110", "theodolite": "312713", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3087167, "baseline": 8.5799865, "starttime": "2019-12-13T20:50:02", "endtime": "2019-12-13T20:54:10", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20748.689698, "baseline": -72.477052, "starttime": "2019-12-13T21:05:09", "endtime": "2019-12-13T21:09:08", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47461.1382457, "baseline": 577.5072457, "starttime": "2019-12-13T21:05:09", "endtime": "2019-12-13T21:09:08", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.51361111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.510555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.510833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.51333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.56722222222, "residual": 0.0, "time": "2019-12-13T20:50:02", "h": 20818.563, "e": -98.36, "z": 46883.64, "f": 51819.3}, {"measurement_type": "EastDown", "angle": 90.536111111111, "residual": 0.0, "time": "2019-12-13T20:51:13", "h": 20818.889, "e": -98.352, "z": 46883.7, "f": 51819.48}, {"measurement_type": "WestUp", "angle": 90.793611111111, "residual": 0.0, "time": "2019-12-13T20:52:21", "h": 20818.73, "e": -98.159, "z": 46883.667, "f": 51819.37}, {"measurement_type": "EastUp", "angle": 270.83305555556, "residual": 0.0, "time": "2019-12-13T20:54:10", "h": 20819.215, "e": -98.072, "z": 46883.614, "f": 51819.55}, {"measurement_type": "SouthDown", "angle": 246.38166666667, "residual": 0.0, "time": "2019-12-13T21:05:09", "h": 20820.854, "e": -97.355, "z": 46883.745, "f": 51820.34}, {"measurement_type": "NorthUp", "angle": 66.380833333333, "residual": 0.0, "time": "2019-12-13T21:06:15", "h": 20820.928, "e": -97.385, "z": 46883.709, "f": 51820.31}, {"measurement_type": "SouthUp", "angle": 113.60666666667, "residual": 0.0, "time": "2019-12-13T21:08:11", "h": 20821.116, "e": -97.148, "z": 46883.525, "f": 51820.21}, {"measurement_type": "NorthDown", "angle": 293.61027777778, "residual": 0.0, "time": "2019-12-13T21:09:08", "h": 20821.769, "e": -97.324, "z": 46883.545, "f": 51820.49}], "metadata": {"time": "2019-12-13T20:50:02Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3187167, "baseline": 8.5839578, "starttime": "2019-12-13T21:12:15", "endtime": "2019-12-13T21:16:42", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20751.3752412, "baseline": -72.6157588, "starttime": "2019-12-13T21:21:50", "endtime": "2019-12-13T21:25:37", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47461.0118633, "baseline": 577.5306133, "starttime": "2019-12-13T21:21:50", "endtime": "2019-12-13T21:25:37", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.5075, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.50944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.50944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.506666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.56583333333, "residual": 0.0, "time": "2019-12-13T21:12:15", "h": 20821.563, "e": -96.558, "z": 46883.669, "f": 51820.51}, {"measurement_type": "EastDown", "angle": 90.544722222222, "residual": 0.0, "time": "2019-12-13T21:14:10", "h": 20821.776, "e": -96.419, "z": 46883.644, "f": 51820.58}, {"measurement_type": "WestUp", "angle": 90.806944444444, "residual": 0.0, "time": "2019-12-13T21:15:33", "h": 20821.387, "e": -95.979, "z": 46883.56, "f": 51820.35}, {"measurement_type": "EastUp", "angle": 270.83722222222, "residual": 0.0, "time": "2019-12-13T21:16:42", "h": 20821.012, "e": -95.304, "z": 46883.513, "f": 51820.11}, {"measurement_type": "SouthDown", "angle": 246.37777777778, "residual": 0.0, "time": "2019-12-13T21:21:50", "h": 20824.354, "e": -95.765, "z": 46883.499, "f": 51821.43}, {"measurement_type": "NorthUp", "angle": 66.376944444444, "residual": 0.0, "time": "2019-12-13T21:23:14", "h": 20823.629, "e": -95.548, "z": 46883.573, "f": 51821.22}, {"measurement_type": "SouthUp", "angle": 113.60861111111, "residual": 0.0, "time": "2019-12-13T21:24:34", "h": 20823.598, "e": -95.305, "z": 46883.434, "f": 51821.1}, {"measurement_type": "NorthDown", "angle": 293.61166666667, "residual": 0.0, "time": "2019-12-13T21:25:37", "h": 20824.383, "e": -95.758, "z": 46883.419, "f": 51821.44}], "metadata": {"time": "2019-12-13T20:50:02Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.322675, "baseline": 8.5840712, "starttime": "2019-12-13T21:28:53", "endtime": "2019-12-13T21:33:16", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20753.6405621, "baseline": -71.9284379, "starttime": "2019-12-13T21:38:36", "endtime": "2019-12-13T21:41:52", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47459.7675752, "baseline": 577.1785752, "starttime": "2019-12-13T21:38:36", "endtime": "2019-12-13T21:41:52", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.50722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.505555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.507777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.57194444444, "residual": 0.0, "time": "2019-12-13T21:28:53", "h": 20825.591, "e": -94.792, "z": 46883.145, "f": 51821.65}, {"measurement_type": "EastDown", "angle": 90.545, "residual": 0.0, "time": "2019-12-13T21:29:50", "h": 20825.22, "e": -94.397, "z": 46883.262, "f": 51821.58}, {"measurement_type": "WestUp", "angle": 90.810555555556, "residual": 0.0, "time": "2019-12-13T21:31:50", "h": 20824.943, "e": -94.364, "z": 46883.302, "f": 51821.49}, {"measurement_type": "EastUp", "angle": 270.84111111111, "residual": 0.0, "time": "2019-12-13T21:33:16", "h": 20824.982, "e": -95.178, "z": 46883.277, "f": 51821.53}, {"measurement_type": "SouthDown", "angle": 246.375, "residual": 0.0, "time": "2019-12-13T21:38:36", "h": 20825.531, "e": -95.192, "z": 46882.645, "f": 51821.11}, {"measurement_type": "NorthUp", "angle": 66.374722222222, "residual": 0.0, "time": "2019-12-13T21:39:26", "h": 20825.489, "e": -94.914, "z": 46882.639, "f": 51821.09}, {"measurement_type": "SouthUp", "angle": 113.61277777778, "residual": 0.0, "time": "2019-12-13T21:40:40", "h": 20825.625, "e": -94.383, "z": 46882.535, "f": 51821.07}, {"measurement_type": "NorthDown", "angle": 293.61388888889, "residual": 0.0, "time": "2019-12-13T21:41:52", "h": 20825.631, "e": -93.995, "z": 46882.537, "f": 51820.99}], "metadata": {"time": "2019-12-13T20:50:02Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3246889, "baseline": 8.5829084, "starttime": "2019-12-13T21:45:34", "endtime": "2019-12-13T21:49:58", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20753.4472453, "baseline": -72.6787547, "starttime": "2019-12-13T21:55:58", "endtime": "2019-12-13T22:00:38", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47459.3254951, "baseline": 577.5279951, "starttime": "2019-12-13T21:55:58", "endtime": "2019-12-13T22:00:38", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.51, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.51055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.506111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.507222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.57916666667, "residual": 0.0, "time": "2019-12-13T21:45:34", "h": 20825.514, "e": -93.732, "z": 46882.384, "f": 51820.88}, {"measurement_type": "EastDown", "angle": 90.554166666667, "residual": 0.0, "time": "2019-12-13T21:46:55", "h": 20825.482, "e": -93.627, "z": 46882.367, "f": 51820.87}, {"measurement_type": "WestUp", "angle": 90.806111111111, "residual": 0.0, "time": "2019-12-13T21:48:21", "h": 20825.351, "e": -93.428, "z": 46882.16, "f": 51820.65}, {"measurement_type": "EastUp", "angle": 270.84, "residual": 0.0, "time": "2019-12-13T21:49:58", "h": 20825.453, "e": -93.338, "z": 46882.098, "f": 51820.62}, {"measurement_type": "SouthDown", "angle": 246.37416666667, "residual": 0.0, "time": "2019-12-13T21:55:58", "h": 20826.365, "e": -92.899, "z": 46881.933, "f": 51820.81}, {"measurement_type": "NorthUp", "angle": 66.374444444444, "residual": 0.0, "time": "2019-12-13T21:57:44", "h": 20826.032, "e": -92.561, "z": 46881.845, "f": 51820.57}, {"measurement_type": "SouthUp", "angle": 113.61111111111, "residual": 0.0, "time": "2019-12-13T21:59:41", "h": 20826.119, "e": -92.379, "z": 46881.717, "f": 51820.5}, {"measurement_type": "NorthDown", "angle": 293.61444444444, "residual": 0.0, "time": "2019-12-13T22:00:38", "h": 20825.988, "e": -92.146, "z": 46881.695, "f": 51820.45}], "metadata": {"time": "2019-12-13T20:50:02Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3273278, "baseline": 8.5820503, "starttime": "2019-12-23T18:46:30", "endtime": "2019-12-23T18:51:38", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20726.7745651, "baseline": -71.5896849, "starttime": "2019-12-23T18:58:07", "endtime": "2019-12-23T19:03:01", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47452.3725989, "baseline": 577.8995989, "starttime": "2019-12-23T18:58:07", "endtime": "2019-12-23T19:03:01", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.58638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.585555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.585555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.58722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.66666666667, "residual": 0.0, "time": "2019-12-23T18:46:30", "h": 20797.7, "e": -91.76, "z": 46873.467, "f": 51802.35}, {"measurement_type": "EastDown", "angle": 90.628333333333, "residual": 0.0, "time": "2019-12-23T18:47:52", "h": 20797.586, "e": -92.164, "z": 46873.589, "f": 51802.42}, {"measurement_type": "WestUp", "angle": 90.878333333333, "residual": 0.0, "time": "2019-12-23T18:49:46", "h": 20797.568, "e": -92.225, "z": 46873.767, "f": 51802.58}, {"measurement_type": "EastUp", "angle": 270.9275, "residual": 0.0, "time": "2019-12-23T18:51:38", "h": 20797.405, "e": -92.435, "z": 46873.993, "f": 51802.68}, {"measurement_type": "SouthDown", "angle": 246.39722222222, "residual": 0.0, "time": "2019-12-23T18:58:07", "h": 20798.125, "e": -91.966, "z": 46874.352, "f": 51803.32}, {"measurement_type": "NorthUp", "angle": 66.396111111111, "residual": 0.0, "time": "2019-12-23T18:59:20", "h": 20798.246, "e": -91.975, "z": 46874.407, "f": 51803.43}, {"measurement_type": "SouthUp", "angle": 113.58527777778, "residual": 0.0, "time": "2019-12-23T19:01:43", "h": 20798.332, "e": -92.673, "z": 46874.557, "f": 51803.6}, {"measurement_type": "NorthDown", "angle": 293.58916666667, "residual": 0.0, "time": "2019-12-23T19:03:01", "h": 20798.754, "e": -93.292, "z": 46874.576, "f": 51803.77}], "metadata": {"time": "2019-12-23T18:46:30Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Bill Worthington", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3173972, "baseline": 8.5804845, "starttime": "2019-12-23T19:07:05", "endtime": "2019-12-23T19:12:18", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20727.27976, "baseline": -71.80124, "starttime": "2019-12-23T19:18:02", "endtime": "2019-12-23T19:21:28", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47452.5884254, "baseline": 577.9984254, "starttime": "2019-12-23T19:18:02", "endtime": "2019-12-23T19:21:28", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.584722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.58638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.584444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.65027777778, "residual": 0.0, "time": "2019-12-23T19:07:05", "h": 20798.71, "e": -94.515, "z": 46874.545, "f": 51803.75}, {"measurement_type": "EastDown", "angle": 90.619166666667, "residual": 0.0, "time": "2019-12-23T19:08:38", "h": 20798.879, "e": -95.306, "z": 46874.457, "f": 51803.76}, {"measurement_type": "WestUp", "angle": 90.875, "residual": 0.0, "time": "2019-12-23T19:10:23", "h": 20798.372, "e": -95.425, "z": 46874.367, "f": 51803.46}, {"measurement_type": "EastUp", "angle": 270.91388888889, "residual": 0.0, "time": "2019-12-23T19:12:18", "h": 20798.226, "e": -95.451, "z": 46874.324, "f": 51803.38}, {"measurement_type": "SouthDown", "angle": 246.3975, "residual": 0.0, "time": "2019-12-23T19:18:02", "h": 20799.092, "e": -95.359, "z": 46874.459, "f": 51803.83}, {"measurement_type": "NorthUp", "angle": 66.396111111111, "residual": 0.0, "time": "2019-12-23T19:19:20", "h": 20798.954, "e": -95.355, "z": 46874.588, "f": 51803.89}, {"measurement_type": "SouthUp", "angle": 113.58666666667, "residual": 0.0, "time": "2019-12-23T19:20:36", "h": 20799.007, "e": -95.266, "z": 46874.642, "f": 51803.93}, {"measurement_type": "NorthDown", "angle": 293.58972222222, "residual": 0.0, "time": "2019-12-23T19:21:28", "h": 20799.271, "e": -95.265, "z": 46874.671, "f": 51804.07}], "metadata": {"time": "2019-12-23T18:46:30Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Bill Worthington", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3193417, "baseline": 8.5817059, "starttime": "2019-12-23T19:25:13", "endtime": "2019-12-23T19:29:58", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20729.2561886, "baseline": -71.5305614, "starttime": "2019-12-23T19:35:17", "endtime": "2019-12-23T19:39:48", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47454.1340084, "baseline": 577.8755084, "starttime": "2019-12-23T19:35:17", "endtime": "2019-12-23T19:39:48", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.58583333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.583055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.583055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.64944444444, "residual": 0.0, "time": "2019-12-23T19:25:13", "h": 20799.022, "e": -95.185, "z": 46875.013, "f": 51804.28}, {"measurement_type": "EastDown", "angle": 90.620277777778, "residual": 0.0, "time": "2019-12-23T19:26:41", "h": 20799.048, "e": -95.076, "z": 46875.091, "f": 51804.4}, {"measurement_type": "WestUp", "angle": 90.875833333333, "residual": 0.0, "time": "2019-12-23T19:28:13", "h": 20799.014, "e": -94.9, "z": 46875.292, "f": 51804.53}, {"measurement_type": "EastUp", "angle": 270.91694444444, "residual": 0.0, "time": "2019-12-23T19:29:58", "h": 20799.356, "e": -94.526, "z": 46875.423, "f": 51804.81}, {"measurement_type": "SouthDown", "angle": 246.39527777778, "residual": 0.0, "time": "2019-12-23T19:35:17", "h": 20800.368, "e": -95.1, "z": 46876.081, "f": 51805.81}, {"measurement_type": "NorthUp", "angle": 66.393888888889, "residual": 0.0, "time": "2019-12-23T19:36:58", "h": 20800.886, "e": -95.035, "z": 46876.138, "f": 51806.06}, {"measurement_type": "SouthUp", "angle": 113.58694444444, "residual": 0.0, "time": "2019-12-23T19:38:32", "h": 20800.881, "e": -95.151, "z": 46876.336, "f": 51806.24}, {"measurement_type": "NorthDown", "angle": 293.59027777778, "residual": 0.0, "time": "2019-12-23T19:39:48", "h": 20801.012, "e": -95.46, "z": 46876.479, "f": 51806.44}], "metadata": {"time": "2019-12-23T18:46:30Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Bill Worthington", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3196194, "baseline": 8.5817625, "starttime": "2019-12-23T19:45:11", "endtime": "2019-12-23T19:49:38", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.0560047, "baseline": -73.4809953, "starttime": "2019-12-23T19:53:39", "endtime": "2019-12-23T19:57:56", "shift": 0.0, "valid": false}, {"element": "Z", "absolute": 47455.776776, "baseline": 578.733276, "starttime": "2019-12-23T19:53:39", "endtime": "2019-12-23T19:57:56", "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.58555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.584444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.583055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.64944444444, "residual": 0.0, "time": "2019-12-23T19:45:11", "h": 20800.271, "e": -95.226, "z": 46876.827, "f": 51806.41}, {"measurement_type": "EastDown", "angle": 90.621666666667, "residual": 0.0, "time": "2019-12-23T19:46:23", "h": 20800.156, "e": -95.097, "z": 46876.859, "f": 51806.44}, {"measurement_type": "WestUp", "angle": 90.876388888889, "residual": 0.0, "time": "2019-12-23T19:48:00", "h": 20800.105, "e": -94.73, "z": 46876.856, "f": 51806.39}, {"measurement_type": "EastUp", "angle": 270.91638888889, "residual": 0.0, "time": "2019-12-23T19:49:38", "h": 20800.228, "e": -94.292, "z": 46876.92, "f": 51806.51}, {"measurement_type": "SouthDown", "angle": 246.39694444444, "residual": 0.0, "time": "2019-12-23T19:53:39", "h": 20801.628, "e": -95.681, "z": 46877.133, "f": 51807.28}, {"measurement_type": "NorthUp", "angle": 66.395555555556, "residual": 0.0, "time": "2019-12-23T19:54:49", "h": 20801.472, "e": -95.863, "z": 46877.068, "f": 51807.17}, {"measurement_type": "SouthUp", "angle": 113.58583333333, "residual": 0.0, "time": "2019-12-23T19:56:46", "h": 20801.536, "e": -95.554, "z": 46876.984, "f": 51807.09}, {"measurement_type": "NorthDown", "angle": 293.58694444444, "residual": 0.0, "time": "2019-12-23T19:57:56", "h": 20801.512, "e": -95.403, "z": 46876.989, "f": 51807.11}], "metadata": {"time": "2019-12-23T18:46:30Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Bill Worthington", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3851056, "baseline": 8.58571, "starttime": "2020-01-03T17:12:47", "endtime": "2020-01-03T17:16:21", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.0650365, "baseline": -71.7177135, "starttime": "2020-01-03T17:20:48", "endtime": "2020-01-03T17:24:40", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.1529433, "baseline": 578.2041933, "starttime": "2020-01-03T17:20:48", "endtime": "2020-01-03T17:24:40", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.58305555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.583055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.581111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.58305555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.72055555556, "residual": 0.0, "time": "2020-01-03T17:12:47", "h": 20800.329, "e": -72.242, "z": 46871.49, "f": 51801.81}, {"measurement_type": "EastDown", "angle": 90.682222222222, "residual": 0.0, "time": "2020-01-03T17:14:13", "h": 20800.259, "e": -72.636, "z": 46871.641, "f": 51801.92}, {"measurement_type": "WestUp", "angle": 90.937222222222, "residual": 0.0, "time": "2020-01-03T17:15:22", "h": 20800.259, "e": -72.657, "z": 46871.521, "f": 51801.82}, {"measurement_type": "EastUp", "angle": 270.9775, "residual": 0.0, "time": "2020-01-03T17:16:21", "h": 20800.086, "e": -72.758, "z": 46871.707, "f": 51801.92}, {"measurement_type": "SouthDown", "angle": 246.39555555556, "residual": 0.0, "time": "2020-01-03T17:20:48", "h": 20799.796, "e": -72.898, "z": 46871.802, "f": 51801.89}, {"measurement_type": "NorthUp", "angle": 66.393055555556, "residual": 0.0, "time": "2020-01-03T17:21:46", "h": 20799.852, "e": -72.8, "z": 46871.919, "f": 51802.01}, {"measurement_type": "SouthUp", "angle": 113.58805555556, "residual": 0.0, "time": "2020-01-03T17:22:59", "h": 20799.668, "e": -72.775, "z": 46871.997, "f": 51802.01}, {"measurement_type": "NorthDown", "angle": 293.59083333333, "residual": 0.0, "time": "2020-01-03T17:24:40", "h": 20799.815, "e": -72.813, "z": 46872.077, "f": 51802.14}], "metadata": {"time": "2020-01-03T17:12:47Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3819111, "baseline": 8.5852195, "starttime": "2020-01-03T17:28:08", "endtime": "2020-01-03T17:32:18", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20727.7191551, "baseline": -71.6995949, "starttime": "2020-01-03T17:35:44", "endtime": "2020-01-03T17:39:04", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47451.2425073, "baseline": 578.2107573, "starttime": "2020-01-03T17:35:44", "endtime": "2020-01-03T17:39:04", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.580833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.58138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.580555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.7125, "residual": 0.0, "time": "2020-01-03T17:28:08", "h": 20799.734, "e": -72.917, "z": 46872.404, "f": 51802.4}, {"measurement_type": "EastDown", "angle": 90.680277777778, "residual": 0.0, "time": "2020-01-03T17:29:28", "h": 20799.839, "e": -73.319, "z": 46872.542, "f": 51802.54}, {"measurement_type": "WestUp", "angle": 90.935277777778, "residual": 0.0, "time": "2020-01-03T17:30:49", "h": 20800.006, "e": -73.791, "z": 46872.632, "f": 51802.71}, {"measurement_type": "EastUp", "angle": 270.97111111111, "residual": 0.0, "time": "2020-01-03T17:32:18", "h": 20799.867, "e": -74.174, "z": 46872.79, "f": 51802.79}, {"measurement_type": "SouthDown", "angle": 246.39611111111, "residual": 0.0, "time": "2020-01-03T17:35:44", "h": 20799.682, "e": -74.909, "z": 46872.939, "f": 51802.88}, {"measurement_type": "NorthUp", "angle": 66.396388888889, "residual": 0.0, "time": "2020-01-03T17:36:55", "h": 20799.458, "e": -74.806, "z": 46872.973, "f": 51802.84}, {"measurement_type": "SouthUp", "angle": 113.58916666667, "residual": 0.0, "time": "2020-01-03T17:38:15", "h": 20799.393, "e": -74.635, "z": 46873.083, "f": 51802.89}, {"measurement_type": "NorthDown", "angle": 293.59027777778, "residual": 0.0, "time": "2020-01-03T17:39:04", "h": 20799.142, "e": -74.597, "z": 46873.132, "f": 51802.88}], "metadata": {"time": "2020-01-03T17:12:47Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3787167, "baseline": 8.5851619, "starttime": "2020-01-03T17:42:11", "endtime": "2020-01-03T17:45:30", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20727.6357972, "baseline": -71.8987028, "starttime": "2020-01-03T17:47:36", "endtime": "2020-01-03T17:50:24", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47452.1491847, "baseline": 578.2884347, "starttime": "2020-01-03T17:47:36", "endtime": "2020-01-03T17:50:24", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.576944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.579444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.70388888889, "residual": 0.0, "time": "2020-01-03T17:42:11", "h": 20799.321, "e": -74.172, "z": 46873.369, "f": 51803.12}, {"measurement_type": "EastDown", "angle": 90.6775, "residual": 0.0, "time": "2020-01-03T17:43:20", "h": 20799.265, "e": -74.395, "z": 46873.502, "f": 51803.25}, {"measurement_type": "WestUp", "angle": 90.929444444444, "residual": 0.0, "time": "2020-01-03T17:44:20", "h": 20799.555, "e": -74.818, "z": 46873.588, "f": 51803.4}, {"measurement_type": "EastUp", "angle": 270.96722222222, "residual": 0.0, "time": "2020-01-03T17:45:30", "h": 20799.794, "e": -75.354, "z": 46873.613, "f": 51803.61}, {"measurement_type": "SouthDown", "angle": 246.39722222222, "residual": 0.0, "time": "2020-01-03T17:47:36", "h": 20799.599, "e": -75.677, "z": 46873.788, "f": 51803.62}, {"measurement_type": "NorthUp", "angle": 66.397222222222, "residual": 0.0, "time": "2020-01-03T17:48:29", "h": 20799.463, "e": -75.814, "z": 46873.847, "f": 51803.64}, {"measurement_type": "SouthUp", "angle": 113.58888888889, "residual": 0.0, "time": "2020-01-03T17:49:35", "h": 20799.572, "e": -76.029, "z": 46873.869, "f": 51803.7}, {"measurement_type": "NorthDown", "angle": 293.59055555556, "residual": 0.0, "time": "2020-01-03T17:50:24", "h": 20799.504, "e": -76.076, "z": 46873.939, "f": 51803.72}], "metadata": {"time": "2020-01-03T17:12:47Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3734389, "baseline": 8.5867341, "starttime": "2020-01-03T17:52:25", "endtime": "2020-01-03T17:55:04", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.0154327, "baseline": -71.2608173, "starttime": "2020-01-03T17:56:47", "endtime": "2020-01-03T17:59:05", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47452.2343397, "baseline": 578.0225897, "starttime": "2020-01-03T17:56:47", "endtime": "2020-01-03T17:59:05", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.58, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58027777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.578055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.5775, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.70166666667, "residual": 0.0, "time": "2020-01-03T17:52:25", "h": 20799.573, "e": -76.779, "z": 46874.079, "f": 51803.87}, {"measurement_type": "EastDown", "angle": 90.670833333333, "residual": 0.0, "time": "2020-01-03T17:53:22", "h": 20799.478, "e": -77.109, "z": 46874.081, "f": 51803.87}, {"measurement_type": "WestUp", "angle": 90.925277777778, "residual": 0.0, "time": "2020-01-03T17:54:09", "h": 20799.423, "e": -77.207, "z": 46874.099, "f": 51803.81}, {"measurement_type": "EastUp", "angle": 270.95861111111, "residual": 0.0, "time": "2020-01-03T17:55:04", "h": 20799.361, "e": -77.562, "z": 46874.168, "f": 51803.89}, {"measurement_type": "SouthDown", "angle": 246.39777777778, "residual": 0.0, "time": "2020-01-03T17:56:47", "h": 20799.248, "e": -77.903, "z": 46874.189, "f": 51803.87}, {"measurement_type": "NorthUp", "angle": 66.397222222222, "residual": 0.0, "time": "2020-01-03T17:57:23", "h": 20799.27, "e": -77.977, "z": 46874.177, "f": 51803.87}, {"measurement_type": "SouthUp", "angle": 113.58972222222, "residual": 0.0, "time": "2020-01-03T17:58:22", "h": 20799.315, "e": -78.154, "z": 46874.254, "f": 51803.93}, {"measurement_type": "NorthDown", "angle": 293.59166666667, "residual": 0.0, "time": "2020-01-03T17:59:05", "h": 20799.272, "e": -78.287, "z": 46874.227, "f": 51803.93}], "metadata": {"time": "2020-01-03T17:12:47Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.315175, "baseline": 8.5804386, "starttime": "2020-01-10T21:02:33", "endtime": "2020-01-10T21:07:51", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20736.9818715, "baseline": -69.4726285, "starttime": "2020-01-10T21:12:58", "endtime": "2020-01-10T21:24:22", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.1827973, "baseline": 577.3772973, "starttime": "2020-01-10T21:12:58", "endtime": "2020-01-10T21:24:22", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.57833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.573888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.574166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.64916666667, "residual": 0.0, "time": "2020-01-10T21:02:33", "h": 20806.986, "e": -96.226, "z": 46872.425, "f": 51805.46}, {"measurement_type": "EastDown", "angle": 90.601388888889, "residual": 0.0, "time": "2020-01-10T21:04:31", "h": 20806.491, "e": -95.51, "z": 46872.54, "f": 51805.34}, {"measurement_type": "WestUp", "angle": 90.857222222222, "residual": 0.0, "time": "2020-01-10T21:06:28", "h": 20805.724, "e": -95.767, "z": 46872.606, "f": 51805.12}, {"measurement_type": "EastUp", "angle": 270.9025, "residual": 0.0, "time": "2020-01-10T21:07:51", "h": 20805.655, "e": -96.523, "z": 46872.748, "f": 51805.23}, {"measurement_type": "SouthDown", "angle": 246.385, "residual": 0.0, "time": "2020-01-10T21:12:58", "h": 20806.285, "e": -95.897, "z": 46872.621, "f": 51805.37}, {"measurement_type": "NorthUp", "angle": 66.384722222222, "residual": 0.0, "time": "2020-01-10T21:14:18", "h": 20806.004, "e": -95.296, "z": 46872.652, "f": 51805.31}, {"measurement_type": "SouthUp", "angle": 113.59638888889, "residual": 0.0, "time": "2020-01-10T21:16:49", "h": 20806.466, "e": -95.092, "z": 46872.803, "f": 51805.63}, {"measurement_type": "NorthDown", "angle": 293.59972222222, "residual": 0.0, "time": "2020-01-10T21:24:22", "h": 20807.063, "e": -94.084, "z": 46873.146, "f": 51806.13}], "metadata": {"time": "2020-01-10T21:02:33Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3208694, "baseline": 8.5803502, "starttime": "2020-01-10T21:26:21", "endtime": "2020-01-10T21:32:43", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20737.971354, "baseline": -69.435396, "starttime": "2020-01-10T21:37:14", "endtime": "2020-01-10T21:43:30", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.5661274, "baseline": 577.3468774, "starttime": "2020-01-10T21:37:14", "endtime": "2020-01-10T21:43:30", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.573888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.574722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.6475, "residual": 0.0, "time": "2020-01-10T21:26:21", "h": 20806.756, "e": -93.799, "z": 46872.891, "f": 51805.79}, {"measurement_type": "EastDown", "angle": 90.610833333333, "residual": 0.0, "time": "2020-01-10T21:28:12", "h": 20806.435, "e": -94.385, "z": 46873.256, "f": 51806.01}, {"measurement_type": "WestUp", "angle": 90.865833333333, "residual": 0.0, "time": "2020-01-10T21:30:44", "h": 20806.354, "e": -93.866, "z": 46873.286, "f": 51805.97}, {"measurement_type": "EastUp", "angle": 270.90916666667, "residual": 0.0, "time": "2020-01-10T21:32:43", "h": 20807.497, "e": -93.622, "z": 46873.175, "f": 51806.39}, {"measurement_type": "SouthDown", "angle": 246.38527777778, "residual": 0.0, "time": "2020-01-10T21:37:14", "h": 20807.214, "e": -94.021, "z": 46873.18, "f": 51806.26}, {"measurement_type": "NorthUp", "angle": 66.384444444444, "residual": 0.0, "time": "2020-01-10T21:38:54", "h": 20807.679, "e": -94.545, "z": 46873.218, "f": 51806.44}, {"measurement_type": "SouthUp", "angle": 113.59916666667, "residual": 0.0, "time": "2020-01-10T21:41:09", "h": 20807.861, "e": -94.68, "z": 46873.163, "f": 51806.49}, {"measurement_type": "NorthDown", "angle": 293.60027777778, "residual": 0.0, "time": "2020-01-10T21:43:30", "h": 20806.873, "e": -94.668, "z": 46873.316, "f": 51806.24}], "metadata": {"time": "2020-01-10T21:02:33Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3181611, "baseline": 8.5790924, "starttime": "2020-01-10T21:50:07", "endtime": "2020-01-10T21:56:46", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20740.8272595, "baseline": -69.5659905, "starttime": "2020-01-10T22:01:36", "endtime": "2020-01-10T22:06:25", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.2045993, "baseline": 577.3720993, "starttime": "2020-01-10T22:01:36", "endtime": "2020-01-10T22:06:25", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57583333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.572777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.573611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.63916666667, "residual": 0.0, "time": "2020-01-10T21:50:07", "h": 20808.327, "e": -94.707, "z": 46872.807, "f": 51806.32}, {"measurement_type": "EastDown", "angle": 90.611111111111, "residual": 0.0, "time": "2020-01-10T21:52:57", "h": 20808.123, "e": -94.396, "z": 46872.909, "f": 51806.39}, {"measurement_type": "WestUp", "angle": 90.865833333333, "residual": 0.0, "time": "2020-01-10T21:55:05", "h": 20808.337, "e": -94.265, "z": 46872.902, "f": 51806.44}, {"measurement_type": "EastUp", "angle": 270.90111111111, "residual": 0.0, "time": "2020-01-10T21:56:46", "h": 20808.838, "e": -94.456, "z": 46872.93, "f": 51806.66}, {"measurement_type": "SouthDown", "angle": 246.38277777778, "residual": 0.0, "time": "2020-01-10T22:01:36", "h": 20810.14, "e": -94.623, "z": 46872.812, "f": 51807.05}, {"measurement_type": "NorthUp", "angle": 66.382222222222, "residual": 0.0, "time": "2020-01-10T22:03:13", "h": 20810.232, "e": -94.04, "z": 46872.743, "f": 51807.02}, {"measurement_type": "SouthUp", "angle": 113.60194444444, "residual": 0.0, "time": "2020-01-10T22:05:12", "h": 20810.496, "e": -93.761, "z": 46872.854, "f": 51807.24}, {"measurement_type": "NorthDown", "angle": 293.605, "residual": 0.0, "time": "2020-01-10T22:06:25", "h": 20810.705, "e": -93.843, "z": 46872.921, "f": 51807.37}], "metadata": {"time": "2020-01-10T21:02:33Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3205917, "baseline": 8.5798399, "starttime": "2020-01-10T22:10:04", "endtime": "2020-01-10T22:16:18", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20743.6458426, "baseline": -69.6216574, "starttime": "2020-01-10T22:21:03", "endtime": "2020-01-10T22:26:42", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.2275685, "baseline": 577.3853185, "starttime": "2020-01-10T22:21:03", "endtime": "2020-01-10T22:26:42", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57583333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.5725, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.572777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.64416666667, "residual": 0.0, "time": "2020-01-10T22:10:04", "h": 20810.869, "e": -93.725, "z": 46872.864, "f": 51807.38}, {"measurement_type": "EastDown", "angle": 90.608611111111, "residual": 0.0, "time": "2020-01-10T22:12:03", "h": 20811.361, "e": -93.945, "z": 46872.817, "f": 51807.58}, {"measurement_type": "WestUp", "angle": 90.868333333333, "residual": 0.0, "time": "2020-01-10T22:14:45", "h": 20811.305, "e": -93.727, "z": 46872.808, "f": 51807.49}, {"measurement_type": "EastUp", "angle": 270.90472222222, "residual": 0.0, "time": "2020-01-10T22:16:18", "h": 20811.811, "e": -94.041, "z": 46872.786, "f": 51807.75}, {"measurement_type": "SouthDown", "angle": 246.38083333333, "residual": 0.0, "time": "2020-01-10T22:21:03", "h": 20813.037, "e": -95.061, "z": 46872.817, "f": 51808.2}, {"measurement_type": "NorthUp", "angle": 66.379444444444, "residual": 0.0, "time": "2020-01-10T22:23:09", "h": 20812.858, "e": -95.226, "z": 46872.881, "f": 51808.19}, {"measurement_type": "SouthUp", "angle": 113.60611111111, "residual": 0.0, "time": "2020-01-10T22:25:23", "h": 20813.846, "e": -95.815, "z": 46872.779, "f": 51808.48}, {"measurement_type": "NorthDown", "angle": 293.6075, "residual": 0.0, "time": "2020-01-10T22:26:42", "h": 20813.329, "e": -96.159, "z": 46872.892, "f": 51808.41}], "metadata": {"time": "2020-01-10T21:02:33Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3903833, "baseline": 8.5823425, "starttime": "2020-01-16T16:15:31", "endtime": "2020-01-16T16:21:06", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20745.0357054, "baseline": -72.2232946, "starttime": "2020-01-16T16:34:00", "endtime": "2020-01-16T16:37:27", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47443.0660379, "baseline": 578.6952879, "starttime": "2020-01-16T16:34:00", "endtime": "2020-01-16T16:37:27", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.57277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.573888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.57, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57361111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.73111111111, "residual": 0.0, "time": "2020-01-16T16:15:31", "h": 20819.473, "e": -69.65, "z": 46866.642, "f": 51805.27}, {"measurement_type": "EastDown", "angle": 90.662777777778, "residual": 0.0, "time": "2020-01-16T16:17:48", "h": 20819.829, "e": -69.829, "z": 46866.308, "f": 51805.11}, {"measurement_type": "WestUp", "angle": 90.915555555556, "residual": 0.0, "time": "2020-01-16T16:19:49", "h": 20819.377, "e": -69.15, "z": 46866.027, "f": 51804.69}, {"measurement_type": "EastUp", "angle": 270.98916666667, "residual": 0.0, "time": "2020-01-16T16:21:06", "h": 20819.39, "e": -69.381, "z": 46865.983, "f": 51804.64}, {"measurement_type": "SouthDown", "angle": 246.36861111111, "residual": 0.0, "time": "2020-01-16T16:34:00", "h": 20817.523, "e": -70.136, "z": 46864.47, "f": 51802.54}, {"measurement_type": "NorthUp", "angle": 66.368611111111, "residual": 0.0, "time": "2020-01-16T16:35:13", "h": 20817.319, "e": -70.073, "z": 46864.477, "f": 51802.42}, {"measurement_type": "SouthUp", "angle": 113.60333333333, "residual": 0.0, "time": "2020-01-16T16:36:39", "h": 20817.337, "e": -70.358, "z": 46864.298, "f": 51802.27}, {"measurement_type": "NorthDown", "angle": 293.60555555556, "residual": 0.0, "time": "2020-01-16T16:37:27", "h": 20816.857, "e": -70.295, "z": 46864.238, "f": 51802.03}], "metadata": {"time": "2020-01-16T16:15:31Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3881611, "baseline": 8.5846914, "starttime": "2020-01-16T16:40:19", "endtime": "2020-01-16T16:43:54", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20741.2965392, "baseline": -71.9874608, "starttime": "2020-01-16T16:48:45", "endtime": "2020-01-16T16:52:35", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47441.876891, "baseline": 578.624641, "starttime": "2020-01-16T16:48:45", "endtime": "2020-01-16T16:52:35", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.573055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.570277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.72166666667, "residual": 0.0, "time": "2020-01-16T16:40:19", "h": 20816.207, "e": -70.696, "z": 46863.962, "f": 51801.5}, {"measurement_type": "EastDown", "angle": 90.666944444444, "residual": 0.0, "time": "2020-01-16T16:41:32", "h": 20815.84, "e": -70.975, "z": 46863.945, "f": 51801.38}, {"measurement_type": "WestUp", "angle": 90.922222222222, "residual": 0.0, "time": "2020-01-16T16:42:45", "h": 20815.583, "e": -71.15, "z": 46863.808, "f": 51801.13}, {"measurement_type": "EastUp", "angle": 270.98083333333, "residual": 0.0, "time": "2020-01-16T16:43:54", "h": 20815.332, "e": -71.758, "z": 46863.732, "f": 51800.96}, {"measurement_type": "SouthDown", "angle": 246.37138888889, "residual": 0.0, "time": "2020-01-16T16:48:45", "h": 20814.043, "e": -72.948, "z": 46863.351, "f": 51800.13}, {"measurement_type": "NorthUp", "angle": 66.371388888889, "residual": 0.0, "time": "2020-01-16T16:49:42", "h": 20813.624, "e": -72.883, "z": 46863.329, "f": 51799.93}, {"measurement_type": "SouthUp", "angle": 113.59916666667, "residual": 0.0, "time": "2020-01-16T16:51:02", "h": 20812.893, "e": -73.206, "z": 46863.276, "f": 51799.62}, {"measurement_type": "NorthDown", "angle": 293.60222222222, "residual": 0.0, "time": "2020-01-16T16:52:35", "h": 20812.576, "e": -72.687, "z": 46863.053, "f": 51799.23}], "metadata": {"time": "2020-01-16T16:15:31Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3840639, "baseline": 8.5843784, "starttime": "2020-01-16T16:55:20", "endtime": "2020-01-16T16:58:44", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20735.8258422, "baseline": -71.9296578, "starttime": "2020-01-16T17:07:00", "endtime": "2020-01-16T17:11:06", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47442.0528461, "baseline": 578.6335961, "starttime": "2020-01-16T17:07:00", "endtime": "2020-01-16T17:11:06", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.58138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.580833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.579166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.7225, "residual": 0.0, "time": "2020-01-16T16:55:20", "h": 20811.973, "e": -72.763, "z": 46862.905, "f": 51798.91}, {"measurement_type": "EastDown", "angle": 90.673333333333, "residual": 0.0, "time": "2020-01-16T16:56:23", "h": 20811.508, "e": -72.251, "z": 46862.884, "f": 51798.7}, {"measurement_type": "WestUp", "angle": 90.927222222222, "residual": 0.0, "time": "2020-01-16T16:57:32", "h": 20811.046, "e": -72.298, "z": 46863.015, "f": 51798.62}, {"measurement_type": "EastUp", "angle": 270.98194444444, "residual": 0.0, "time": "2020-01-16T16:58:44", "h": 20811.013, "e": -72.67, "z": 46863.179, "f": 51798.77}, {"measurement_type": "SouthDown", "angle": 246.37861111111, "residual": 0.0, "time": "2020-01-16T17:07:00", "h": 20808.462, "e": -72.596, "z": 46863.239, "f": 51797.78}, {"measurement_type": "NorthUp", "angle": 66.378611111111, "residual": 0.0, "time": "2020-01-16T17:08:21", "h": 20808.039, "e": -74.039, "z": 46863.634, "f": 51797.98}, {"measurement_type": "SouthUp", "angle": 113.59527777778, "residual": 0.0, "time": "2020-01-16T17:09:38", "h": 20807.647, "e": -74.425, "z": 46863.382, "f": 51797.66}, {"measurement_type": "NorthDown", "angle": 293.59805555556, "residual": 0.0, "time": "2020-01-16T17:11:06", "h": 20806.874, "e": -74.854, "z": 46863.422, "f": 51797.37}], "metadata": {"time": "2020-01-16T16:15:31Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3823278, "baseline": 8.5847947, "starttime": "2020-01-16T17:13:21", "endtime": "2020-01-16T17:17:55", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20731.5105799, "baseline": -72.7491701, "starttime": "2020-01-16T17:20:45", "endtime": "2020-01-16T17:24:06", "shift": 0.0, "valid": false}, {"element": "Z", "absolute": 47442.3645056, "baseline": 579.0087556, "starttime": "2020-01-16T17:20:45", "endtime": "2020-01-16T17:24:06", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57916666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.576944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.579166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.71555555556, "residual": 0.0, "time": "2020-01-16T17:13:21", "h": 20805.54, "e": -74.211, "z": 46863.323, "f": 51796.77}, {"measurement_type": "EastDown", "angle": 90.665833333333, "residual": 0.0, "time": "2020-01-16T17:14:28", "h": 20805.113, "e": -73.664, "z": 46863.312, "f": 51796.61}, {"measurement_type": "WestUp", "angle": 90.925555555556, "residual": 0.0, "time": "2020-01-16T17:16:24", "h": 20805.279, "e": -72.68, "z": 46863.265, "f": 51796.56}, {"measurement_type": "EastUp", "angle": 270.98388888889, "residual": 0.0, "time": "2020-01-16T17:17:55", "h": 20805.308, "e": -72.482, "z": 46863.24, "f": 51796.54}, {"measurement_type": "SouthDown", "angle": 246.38416666667, "residual": 0.0, "time": "2020-01-16T17:20:45", "h": 20804.767, "e": -72.44, "z": 46863.417, "f": 51796.53}, {"measurement_type": "NorthUp", "angle": 66.384166666667, "residual": 0.0, "time": "2020-01-16T17:21:40", "h": 20804.443, "e": -72.879, "z": 46863.295, "f": 51796.26}, {"measurement_type": "SouthUp", "angle": 113.59166666667, "residual": 0.0, "time": "2020-01-16T17:23:11", "h": 20804.143, "e": -73.01, "z": 46863.308, "f": 51796.16}, {"measurement_type": "NorthDown", "angle": 293.59472222222, "residual": 0.0, "time": "2020-01-16T17:24:06", "h": 20803.686, "e": -73.401, "z": 46863.403, "f": 51796.07}], "metadata": {"time": "2020-01-16T16:15:31Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2844806, "baseline": 8.5528701, "starttime": "2020-01-23T18:53:19", "endtime": "2020-01-23T19:02:33", "shift": 0.0, "valid": false}, {"element": "H", "absolute": 20731.193126, "baseline": -70.880624, "starttime": "2020-01-23T19:14:01", "endtime": "2020-01-23T19:20:42", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47441.3246142, "baseline": 578.3036142, "starttime": "2020-01-23T19:14:01", "endtime": "2020-01-23T19:20:42", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.57972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.578333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.578333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.74722222222, "residual": 0.0, "time": "2020-01-23T18:53:19", "h": 20801.807, "e": -95.431, "z": 46864.009, "f": 51795.92}, {"measurement_type": "EastDown", "angle": 90.396666666667, "residual": 0.0, "time": "2020-01-23T18:55:10", "h": 20801.932, "e": -96.039, "z": 46863.944, "f": 51795.94}, {"measurement_type": "WestUp", "angle": 90.945277777778, "residual": 0.0, "time": "2020-01-23T18:59:35", "h": 20802.158, "e": -97.955, "z": 46863.82, "f": 51795.92}, {"measurement_type": "EastUp", "angle": 270.81166666667, "residual": 0.0, "time": "2020-01-23T19:02:33", "h": 20802.238, "e": -99.018, "z": 46863.568, "f": 51795.78}, {"measurement_type": "SouthDown", "angle": 246.38611111111, "residual": 0.0, "time": "2020-01-23T19:14:01", "h": 20802.353, "e": -102.173, "z": 46863.064, "f": 51795.32}, {"measurement_type": "NorthUp", "angle": 66.386111111111, "residual": 0.0, "time": "2020-01-23T19:16:07", "h": 20802.012, "e": -102.616, "z": 46862.961, "f": 51795.15}, {"measurement_type": "SouthUp", "angle": 113.59472222222, "residual": 0.0, "time": "2020-01-23T19:18:40", "h": 20802.067, "e": -103.363, "z": 46863.082, "f": 51795.21}, {"measurement_type": "NorthDown", "angle": 293.59611111111, "residual": 0.0, "time": "2020-01-23T19:20:42", "h": 20801.863, "e": -103.78, "z": 46862.977, "f": 51795.02}], "metadata": {"time": "2020-01-23T18:53:19Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2799667, "baseline": 8.5735568, "starttime": "2020-01-23T19:28:34", "endtime": "2020-01-23T19:34:25", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20730.8038684, "baseline": -71.5791316, "starttime": "2020-01-23T19:41:43", "endtime": "2020-01-23T19:50:56", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47442.1576799, "baseline": 578.6511799, "starttime": "2020-01-23T19:41:43", "endtime": "2020-01-23T19:50:56", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.578333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.58, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.578333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.61527777778, "residual": 0.0, "time": "2020-01-23T19:28:34", "h": 20802.451, "e": -105.582, "z": 46862.998, "f": 51795.32}, {"measurement_type": "EastDown", "angle": 90.568055555556, "residual": 0.0, "time": "2020-01-23T19:30:28", "h": 20802.437, "e": -106.07, "z": 46863.016, "f": 51795.33}, {"measurement_type": "WestUp", "angle": 90.8425, "residual": 0.0, "time": "2020-01-23T19:32:57", "h": 20802.044, "e": -106.498, "z": 46863.1, "f": 51795.23}, {"measurement_type": "EastUp", "angle": 270.85722222222, "residual": 0.0, "time": "2020-01-23T19:34:25", "h": 20802.201, "e": -106.758, "z": 46863.095, "f": 51795.31}, {"measurement_type": "SouthDown", "angle": 246.38805555556, "residual": 0.0, "time": "2020-01-23T19:41:43", "h": 20802.576, "e": -108.159, "z": 46863.336, "f": 51795.71}, {"measurement_type": "NorthUp", "angle": 66.390277777778, "residual": 0.0, "time": "2020-01-23T19:44:28", "h": 20802.207, "e": -108.364, "z": 46863.413, "f": 51795.62}, {"measurement_type": "SouthUp", "angle": 113.59527777778, "residual": 0.0, "time": "2020-01-23T19:47:46", "h": 20802.251, "e": -108.724, "z": 46863.546, "f": 51795.76}, {"measurement_type": "NorthDown", "angle": 293.59861111111, "residual": 0.0, "time": "2020-01-23T19:50:56", "h": 20802.498, "e": -108.905, "z": 46863.731, "f": 51796.04}], "metadata": {"time": "2020-01-23T18:53:19Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2765639, "baseline": 8.5783181, "starttime": "2020-01-23T19:57:53", "endtime": "2020-01-23T20:04:54", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.3484852, "baseline": -70.8540148, "starttime": "2020-01-23T20:10:49", "endtime": "2020-01-23T20:18:57", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47443.9043971, "baseline": 578.3721471, "starttime": "2020-01-23T20:10:49", "endtime": "2020-01-23T20:18:57", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.58, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.578333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.578333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60472222222, "residual": 0.0, "time": "2020-01-23T19:57:53", "h": 20801.579, "e": -108.69, "z": 46864.157, "f": 51796.09}, {"measurement_type": "EastDown", "angle": 90.567222222222, "residual": 0.0, "time": "2020-01-23T19:59:47", "h": 20801.119, "e": -108.665, "z": 46864.385, "f": 51796.09}, {"measurement_type": "WestUp", "angle": 90.831666666667, "residual": 0.0, "time": "2020-01-23T20:02:29", "h": 20801.323, "e": -109.505, "z": 46864.606, "f": 51796.4}, {"measurement_type": "EastUp", "angle": 270.86583333333, "residual": 0.0, "time": "2020-01-23T20:04:54", "h": 20800.96, "e": -109.812, "z": 46864.796, "f": 51796.44}, {"measurement_type": "SouthDown", "angle": 246.39222222222, "residual": 0.0, "time": "2020-01-23T20:10:49", "h": 20799.043, "e": -110.084, "z": 46865.311, "f": 51796.11}, {"measurement_type": "NorthUp", "angle": 66.391666666667, "residual": 0.0, "time": "2020-01-23T20:12:40", "h": 20799.192, "e": -110.305, "z": 46865.426, "f": 51796.28}, {"measurement_type": "SouthUp", "angle": 113.59222222222, "residual": 0.0, "time": "2020-01-23T20:16:21", "h": 20799.31, "e": -110.786, "z": 46865.649, "f": 51796.58}, {"measurement_type": "NorthDown", "angle": 293.59416666667, "residual": 0.0, "time": "2020-01-23T20:18:57", "h": 20799.265, "e": -110.88, "z": 46865.743, "f": 51796.63}], "metadata": {"time": "2020-01-23T18:53:19Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2653139, "baseline": 8.574089, "starttime": "2020-01-23T20:23:36", "endtime": "2020-01-23T20:28:44", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.8583957, "baseline": -70.4406043, "starttime": "2020-01-23T20:33:27", "endtime": "2020-01-23T20:39:37", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47444.914758, "baseline": 578.202008, "starttime": "2020-01-23T20:33:27", "endtime": "2020-01-23T20:39:37", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.578055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.578055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.59583333333, "residual": 0.0, "time": "2020-01-23T20:23:36", "h": 20798.367, "e": -111.656, "z": 46866.091, "f": 51796.6}, {"measurement_type": "EastDown", "angle": 90.557222222222, "residual": 0.0, "time": "2020-01-23T20:25:06", "h": 20798.604, "e": -111.728, "z": 46866.053, "f": 51796.68}, {"measurement_type": "WestUp", "angle": 90.828333333333, "residual": 0.0, "time": "2020-01-23T20:27:34", "h": 20798.312, "e": -111.567, "z": 46866.317, "f": 51796.83}, {"measurement_type": "EastUp", "angle": 270.8425, "residual": 0.0, "time": "2020-01-23T20:28:44", "h": 20798.398, "e": -111.892, "z": 46866.384, "f": 51796.86}, {"measurement_type": "SouthDown", "angle": 246.3925, "residual": 0.0, "time": "2020-01-23T20:33:27", "h": 20799.024, "e": -112.663, "z": 46866.58, "f": 51797.26}, {"measurement_type": "NorthUp", "angle": 66.393055555556, "residual": 0.0, "time": "2020-01-23T20:35:19", "h": 20799.16, "e": -112.97, "z": 46866.666, "f": 51797.45}, {"measurement_type": "SouthUp", "angle": 113.59305555556, "residual": 0.0, "time": "2020-01-23T20:36:55", "h": 20799.395, "e": -113.135, "z": 46866.779, "f": 51797.64}, {"measurement_type": "NorthDown", "angle": 293.59527777778, "residual": 0.0, "time": "2020-01-23T20:39:37", "h": 20799.617, "e": -113.353, "z": 46866.826, "f": 51797.77}], "metadata": {"time": "2020-01-23T18:53:19Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3228833, "baseline": 8.5847253, "starttime": "2020-01-29T19:15:08", "endtime": "2020-01-29T19:19:08", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20726.2385931, "baseline": -70.6696569, "starttime": "2020-01-29T19:23:40", "endtime": "2020-01-29T19:27:19", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47439.0751885, "baseline": 577.9456885, "starttime": "2020-01-29T19:23:40", "endtime": "2020-01-29T19:27:19", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.58, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.5775, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.579444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57916666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.64833333333, "residual": 0.0, "time": "2020-01-29T19:15:08", "h": 20796.972, "e": -94.319, "z": 46861.2, "f": 51791.26}, {"measurement_type": "EastDown", "angle": 90.616944444444, "residual": 0.0, "time": "2020-01-29T19:16:21", "h": 20797.519, "e": -94.746, "z": 46861.052, "f": 51791.31}, {"measurement_type": "WestUp", "angle": 90.873888888889, "residual": 0.0, "time": "2020-01-29T19:17:31", "h": 20798.281, "e": -94.969, "z": 46860.945, "f": 51791.53}, {"measurement_type": "EastUp", "angle": 270.91527777778, "residual": 0.0, "time": "2020-01-29T19:19:08", "h": 20799.308, "e": -94.842, "z": 46860.73, "f": 51791.72}, {"measurement_type": "SouthDown", "angle": 246.39305555556, "residual": 0.0, "time": "2020-01-29T19:23:40", "h": 20796.363, "e": -91.884, "z": 46861.152, "f": 51790.96}, {"measurement_type": "NorthUp", "angle": 66.391666666667, "residual": 0.0, "time": "2020-01-29T19:24:46", "h": 20796.382, "e": -91.852, "z": 46861.108, "f": 51790.9}, {"measurement_type": "SouthUp", "angle": 113.59194444444, "residual": 0.0, "time": "2020-01-29T19:25:59", "h": 20797.175, "e": -91.897, "z": 46861.156, "f": 51791.24}, {"measurement_type": "NorthDown", "angle": 293.59527777778, "residual": 0.0, "time": "2020-01-29T19:27:19", "h": 20797.713, "e": -92.225, "z": 46861.102, "f": 51791.42}], "metadata": {"time": "2020-01-29T19:15:08Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3355222, "baseline": 8.5940581, "starttime": "2020-01-29T19:29:40", "endtime": "2020-01-29T19:33:06", "shift": 0.0, "valid": false}, {"element": "H", "absolute": 20727.9474051, "baseline": -70.4580949, "starttime": "2020-01-29T19:35:58", "endtime": "2020-01-29T19:39:32", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47439.3816642, "baseline": 577.8446642, "starttime": "2020-01-29T19:35:58", "endtime": "2020-01-29T19:39:32", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.579166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57916666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57972222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.578611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.68333333333, "residual": 0.0, "time": "2020-01-29T19:29:40", "h": 20797.396, "e": -92.846, "z": 46861.421, "f": 51791.61}, {"measurement_type": "EastDown", "angle": 90.625277777778, "residual": 0.0, "time": "2020-01-29T19:30:56", "h": 20797.923, "e": -93.535, "z": 46861.389, "f": 51791.77}, {"measurement_type": "WestUp", "angle": 90.883333333333, "residual": 0.0, "time": "2020-01-29T19:31:58", "h": 20797.783, "e": -93.749, "z": 46861.372, "f": 51791.69}, {"measurement_type": "EastUp", "angle": 270.91361111111, "residual": 0.0, "time": "2020-01-29T19:33:06", "h": 20797.785, "e": -93.993, "z": 46861.396, "f": 51791.72}, {"measurement_type": "SouthDown", "angle": 246.39222222222, "residual": 0.0, "time": "2020-01-29T19:35:58", "h": 20798.195, "e": -94.843, "z": 46861.423, "f": 51791.9}, {"measurement_type": "NorthUp", "angle": 66.390277777778, "residual": 0.0, "time": "2020-01-29T19:37:12", "h": 20798.45, "e": -94.985, "z": 46861.498, "f": 51792.05}, {"measurement_type": "SouthUp", "angle": 113.59416666667, "residual": 0.0, "time": "2020-01-29T19:38:37", "h": 20798.264, "e": -95.694, "z": 46861.609, "f": 51792.11}, {"measurement_type": "NorthDown", "angle": 293.59722222222, "residual": 0.0, "time": "2020-01-29T19:39:32", "h": 20798.713, "e": -96.297, "z": 46861.618, "f": 51792.32}], "metadata": {"time": "2020-01-29T19:15:08Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3144111, "baseline": 8.5851027, "starttime": "2020-01-29T19:41:57", "endtime": "2020-01-29T19:45:53", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.4060623, "baseline": -70.5186877, "starttime": "2020-01-29T19:48:49", "endtime": "2020-01-29T19:52:07", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47439.6477858, "baseline": 577.9110358, "starttime": "2020-01-29T19:48:49", "endtime": "2020-01-29T19:52:07", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.5775, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57916666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.576944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.578333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.63666666667, "residual": 0.0, "time": "2020-01-29T19:41:57", "h": 20798.578, "e": -97.145, "z": 46861.631, "f": 51792.27}, {"measurement_type": "EastDown", "angle": 90.612777777778, "residual": 0.0, "time": "2020-01-29T19:43:25", "h": 20799.012, "e": -97.768, "z": 46861.586, "f": 51792.4}, {"measurement_type": "WestUp", "angle": 90.869166666667, "residual": 0.0, "time": "2020-01-29T19:44:54", "h": 20798.761, "e": -98.213, "z": 46861.631, "f": 51792.35}, {"measurement_type": "EastUp", "angle": 270.89777777778, "residual": 0.0, "time": "2020-01-29T19:45:53", "h": 20798.962, "e": -98.596, "z": 46861.612, "f": 51792.42}, {"measurement_type": "SouthDown", "angle": 246.39166666667, "residual": 0.0, "time": "2020-01-29T19:48:49", "h": 20799.208, "e": -99.287, "z": 46861.615, "f": 51792.52}, {"measurement_type": "NorthUp", "angle": 66.390833333333, "residual": 0.0, "time": "2020-01-29T19:49:52", "h": 20799.085, "e": -99.426, "z": 46861.676, "f": 51792.55}, {"measurement_type": "SouthUp", "angle": 113.59583333333, "residual": 0.0, "time": "2020-01-29T19:51:20", "h": 20798.862, "e": -99.806, "z": 46861.803, "f": 51792.53}, {"measurement_type": "NorthDown", "angle": 293.59694444444, "residual": 0.0, "time": "2020-01-29T19:52:07", "h": 20798.544, "e": -99.923, "z": 46861.853, "f": 51792.49}], "metadata": {"time": "2020-01-29T19:15:08Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3113556, "baseline": 8.5869182, "starttime": "2020-01-29T19:54:32", "endtime": "2020-01-29T19:58:06", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20725.703051, "baseline": -70.144949, "starttime": "2020-01-29T20:01:15", "endtime": "2020-01-29T20:04:40", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47440.6705215, "baseline": 577.7835215, "starttime": "2020-01-29T20:01:15", "endtime": "2020-01-29T20:04:40", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.578333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.578611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.63138888889, "residual": 0.0, "time": "2020-01-29T19:54:32", "h": 20798.511, "e": -99.676, "z": 46861.843, "f": 51792.45}, {"measurement_type": "EastDown", "angle": 90.61, "residual": 0.0, "time": "2020-01-29T19:55:55", "h": 20798.108, "e": -99.928, "z": 46862.203, "f": 51792.62}, {"measurement_type": "WestUp", "angle": 90.869444444444, "residual": 0.0, "time": "2020-01-29T19:56:53", "h": 20797.878, "e": -99.574, "z": 46862.142, "f": 51792.46}, {"measurement_type": "EastUp", "angle": 270.89611111111, "residual": 0.0, "time": "2020-01-29T19:58:06", "h": 20797.16, "e": -99.541, "z": 46862.438, "f": 51792.48}, {"measurement_type": "SouthDown", "angle": 246.39444444444, "residual": 0.0, "time": "2020-01-29T20:01:15", "h": 20796.834, "e": -99.217, "z": 46862.652, "f": 51792.53}, {"measurement_type": "NorthUp", "angle": 66.394444444444, "residual": 0.0, "time": "2020-01-29T20:02:18", "h": 20796.202, "e": -98.829, "z": 46862.758, "f": 51792.4}, {"measurement_type": "SouthUp", "angle": 113.5925, "residual": 0.0, "time": "2020-01-29T20:03:33", "h": 20795.584, "e": -98.472, "z": 46862.979, "f": 51792.36}, {"measurement_type": "NorthDown", "angle": 293.59388888889, "residual": 0.0, "time": "2020-01-29T20:04:40", "h": 20794.772, "e": -97.998, "z": 46863.159, "f": 51792.22}], "metadata": {"time": "2020-01-29T19:15:08Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2748972, "baseline": 8.5857315, "starttime": "2020-02-14T21:11:48", "endtime": "2020-02-14T21:17:16", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20739.9191066, "baseline": -70.6718934, "starttime": "2020-02-14T21:30:04", "endtime": "2020-02-14T21:34:21", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47436.2199471, "baseline": 577.6059471, "starttime": "2020-02-14T21:30:04", "endtime": "2020-02-14T21:34:21", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.55222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.551111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.550277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.55333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.57777777778, "residual": 0.0, "time": "2020-02-14T21:11:48", "h": 20809.39, "e": -112.535, "z": 46860.495, "f": 51795.25}, {"measurement_type": "EastDown", "angle": 90.533611111111, "residual": 0.0, "time": "2020-02-14T21:14:49", "h": 20808.855, "e": -112.156, "z": 46860.221, "f": 51794.78}, {"measurement_type": "WestUp", "angle": 90.796944444444, "residual": 0.0, "time": "2020-02-14T21:16:12", "h": 20809.936, "e": -112.591, "z": 46860.172, "f": 51795.21}, {"measurement_type": "EastUp", "angle": 270.845, "residual": 0.0, "time": "2020-02-14T21:17:16", "h": 20809.842, "e": -112.781, "z": 46860.293, "f": 51795.28}, {"measurement_type": "SouthDown", "angle": 246.37888888889, "residual": 0.0, "time": "2020-02-14T21:30:04", "h": 20809.564, "e": -110.342, "z": 46858.858, "f": 51793.82}, {"measurement_type": "NorthUp", "angle": 66.374722222222, "residual": 0.0, "time": "2020-02-14T21:31:27", "h": 20810.386, "e": -110.507, "z": 46858.564, "f": 51793.85}, {"measurement_type": "SouthUp", "angle": 113.60638888889, "residual": 0.0, "time": "2020-02-14T21:32:50", "h": 20810.43, "e": -110.981, "z": 46858.567, "f": 51793.91}, {"measurement_type": "NorthDown", "angle": 293.61027777778, "residual": 0.0, "time": "2020-02-14T21:34:21", "h": 20811.984, "e": -111.727, "z": 46858.467, "f": 51794.39}], "metadata": {"time": "2020-02-14T21:11:48Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2775361, "baseline": 8.5865563, "starttime": "2020-02-14T21:39:38", "endtime": "2020-02-14T21:43:33", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20743.5159564, "baseline": -70.5452936, "starttime": "2020-02-14T21:48:24", "endtime": "2020-02-14T21:52:35", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47435.0482843, "baseline": 577.5237843, "starttime": "2020-02-14T21:48:24", "endtime": "2020-02-14T21:52:35", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.548055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.54916666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.5525, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.548055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.56138888889, "residual": 0.0, "time": "2020-02-14T21:39:38", "h": 20812.772, "e": -112.296, "z": 46858.182, "f": 51794.54}, {"measurement_type": "EastDown", "angle": 90.545, "residual": 0.0, "time": "2020-02-14T21:41:02", "h": 20812.93, "e": -111.681, "z": 46857.968, "f": 51794.33}, {"measurement_type": "WestUp", "angle": 90.809444444444, "residual": 0.0, "time": "2020-02-14T21:42:04", "h": 20813.106, "e": -111.864, "z": 46858.07, "f": 51794.55}, {"measurement_type": "EastUp", "angle": 270.83888888889, "residual": 0.0, "time": "2020-02-14T21:43:33", "h": 20813.674, "e": -111.673, "z": 46858.056, "f": 51794.74}, {"measurement_type": "SouthDown", "angle": 246.375, "residual": 0.0, "time": "2020-02-14T21:48:24", "h": 20813.504, "e": -110.707, "z": 46857.594, "f": 51794.22}, {"measurement_type": "NorthUp", "angle": 66.374166666667, "residual": 0.0, "time": "2020-02-14T21:49:19", "h": 20813.378, "e": -110.715, "z": 46857.495, "f": 51794.04}, {"measurement_type": "SouthUp", "angle": 113.61277777778, "residual": 0.0, "time": "2020-02-14T21:50:59", "h": 20814.565, "e": -111.16, "z": 46857.512, "f": 51794.5}, {"measurement_type": "NorthDown", "angle": 293.61611111111, "residual": 0.0, "time": "2020-02-14T21:52:35", "h": 20814.798, "e": -111.231, "z": 46857.497, "f": 51794.68}], "metadata": {"time": "2020-02-14T21:11:48Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2799667, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}, {"element": "H", "absolute": null, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}, {"element": "Z", "absolute": null, "baseline": null, "starttime": null, "endtime": null, "shift": 0.0, "valid": false}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "WestUp", "angle": 90.8, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SouthDown", "angle": 246.36666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "NorthUp", "angle": 66.366666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.53333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SouthUp", "angle": 113.6, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.55, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.533333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "EastDown", "angle": 90.533333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.55, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.533333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "NorthDown", "angle": 293.6, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "EastUp", "angle": 270.83333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}], "metadata": {"time": "2020-02-14T21:11:48Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2798972, "baseline": 8.5865473, "starttime": "2020-02-14T21:55:20", "endtime": "2020-02-14T21:59:58", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20747.1286145, "baseline": -70.0841355, "starttime": "2020-02-14T22:07:08", "endtime": "2020-02-14T22:11:14", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47434.3823995, "baseline": 577.3121495, "starttime": "2020-02-14T22:07:08", "endtime": "2020-02-14T22:11:14", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.55111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.55083333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.550833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.547222222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.56555555556, "residual": 0.0, "time": "2020-02-14T21:55:20", "h": 20814.736, "e": -110.949, "z": 46857.271, "f": 51794.47}, {"measurement_type": "EastDown", "angle": 90.549722222222, "residual": 0.0, "time": "2020-02-14T21:56:28", "h": 20814.289, "e": -111.055, "z": 46857.292, "f": 51794.29}, {"measurement_type": "WestUp", "angle": 90.811666666667, "residual": 0.0, "time": "2020-02-14T21:57:34", "h": 20814.946, "e": -110.981, "z": 46857.214, "f": 51794.45}, {"measurement_type": "EastUp", "angle": 270.83944444444, "residual": 0.0, "time": "2020-02-14T21:59:58", "h": 20815.566, "e": -111.174, "z": 46856.994, "f": 51794.52}, {"measurement_type": "SouthDown", "angle": 246.3725, "residual": 0.0, "time": "2020-02-14T22:07:08", "h": 20816.895, "e": -110.895, "z": 46857.023, "f": 51795.06}, {"measurement_type": "NorthUp", "angle": 66.371944444444, "residual": 0.0, "time": "2020-02-14T22:08:19", "h": 20816.962, "e": -111.162, "z": 46857.145, "f": 51795.16}, {"measurement_type": "SouthUp", "angle": 113.61888888889, "residual": 0.0, "time": "2020-02-14T22:10:22", "h": 20817.356, "e": -110.876, "z": 46857.09, "f": 51795.25}, {"measurement_type": "NorthDown", "angle": 293.62111111111, "residual": 0.0, "time": "2020-02-14T22:11:14", "h": 20817.638, "e": -110.655, "z": 46857.023, "f": 51795.32}], "metadata": {"time": "2020-02-14T21:11:48Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3016333, "baseline": 8.5824828, "starttime": "2020-02-21T18:21:02", "endtime": "2020-02-21T18:25:07", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20734.0315384, "baseline": -70.3832116, "starttime": "2020-02-21T18:31:12", "endtime": "2020-02-21T18:35:33", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47428.5484972, "baseline": 577.7262472, "starttime": "2020-02-21T18:31:12", "endtime": "2020-02-21T18:35:33", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.55388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.549166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.549166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.55194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60916666667, "residual": 0.0, "time": "2020-02-21T18:21:02", "h": 20803.861, "e": -101.613, "z": 46851.097, "f": 51784.72}, {"measurement_type": "EastDown", "angle": 90.570277777778, "residual": 0.0, "time": "2020-02-21T18:22:16", "h": 20804.034, "e": -101.22, "z": 46851.052, "f": 51784.7}, {"measurement_type": "WestUp", "angle": 90.810555555556, "residual": 0.0, "time": "2020-02-21T18:24:00", "h": 20804.062, "e": -101.961, "z": 46851.209, "f": 51784.85}, {"measurement_type": "EastUp", "angle": 270.8675, "residual": 0.0, "time": "2020-02-21T18:25:07", "h": 20804.08, "e": -101.738, "z": 46851.012, "f": 51784.73}, {"measurement_type": "SouthDown", "angle": 246.37916666667, "residual": 0.0, "time": "2020-02-21T18:31:12", "h": 20804.975, "e": -100.808, "z": 46850.817, "f": 51784.83}, {"measurement_type": "NorthUp", "angle": 66.376666666667, "residual": 0.0, "time": "2020-02-21T18:32:19", "h": 20804.354, "e": -100.38, "z": 46850.907, "f": 51784.62}, {"measurement_type": "SouthUp", "angle": 113.60111111111, "residual": 0.0, "time": "2020-02-21T18:34:30", "h": 20804.097, "e": -99.232, "z": 46850.714, "f": 51784.4}, {"measurement_type": "NorthDown", "angle": 293.6075, "residual": 0.0, "time": "2020-02-21T18:35:33", "h": 20804.233, "e": -99.397, "z": 46850.851, "f": 51784.57}], "metadata": {"time": "2020-02-21T18:21:02Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3083, "baseline": 8.5818462, "starttime": "2020-02-21T18:39:00", "endtime": "2020-02-21T18:43:18", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20732.5449719, "baseline": -71.1135281, "starttime": "2020-02-21T18:49:59", "endtime": "2020-02-21T18:53:49", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47428.4371135, "baseline": 578.0933635, "starttime": "2020-02-21T18:49:59", "endtime": "2020-02-21T18:53:49", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.565277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.5675, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.56527777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.565833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.62416666667, "residual": 0.0, "time": "2020-02-21T18:39:00", "h": 20804.102, "e": -99.311, "z": 46850.779, "f": 51784.47}, {"measurement_type": "EastDown", "angle": 90.594444444444, "residual": 0.0, "time": "2020-02-21T18:40:22", "h": 20804.211, "e": -99.049, "z": 46850.678, "f": 51784.42}, {"measurement_type": "WestUp", "angle": 90.841666666667, "residual": 0.0, "time": "2020-02-21T18:42:00", "h": 20803.836, "e": -98.628, "z": 46850.666, "f": 51784.3}, {"measurement_type": "EastUp", "angle": 270.88361111111, "residual": 0.0, "time": "2020-02-21T18:43:18", "h": 20803.763, "e": -98.944, "z": 46850.737, "f": 51784.3}, {"measurement_type": "SouthDown", "angle": 246.3825, "residual": 0.0, "time": "2020-02-21T18:49:59", "h": 20803.836, "e": -99.882, "z": 46850.501, "f": 51784.11}, {"measurement_type": "NorthUp", "angle": 66.381388888889, "residual": 0.0, "time": "2020-02-21T18:51:09", "h": 20803.598, "e": -99.598, "z": 46850.339, "f": 51783.89}, {"measurement_type": "SouthUp", "angle": 113.60194444444, "residual": 0.0, "time": "2020-02-21T18:52:50", "h": 20803.393, "e": -99.991, "z": 46850.343, "f": 51783.82}, {"measurement_type": "NorthDown", "angle": 293.60888888889, "residual": 0.0, "time": "2020-02-21T18:53:49", "h": 20803.807, "e": -100.075, "z": 46850.192, "f": 51783.81}], "metadata": {"time": "2020-02-21T18:21:02Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3034389, "baseline": 8.5834661, "starttime": "2020-02-21T18:57:12", "endtime": "2020-02-21T19:00:47", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20731.2277393, "baseline": -70.7680107, "starttime": "2020-02-21T19:06:11", "endtime": "2020-02-21T19:09:50", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47427.1466742, "baseline": 577.9631742, "starttime": "2020-02-21T19:06:11", "endtime": "2020-02-21T19:09:50", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.56861111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.56166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.566666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.561944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.61916666667, "residual": 0.0, "time": "2020-02-21T18:57:12", "h": 20803.782, "e": -100.361, "z": 46849.944, "f": 51783.56}, {"measurement_type": "EastDown", "angle": 90.590277777778, "residual": 0.0, "time": "2020-02-21T18:58:36", "h": 20804.464, "e": -101.161, "z": 46849.737, "f": 51783.65}, {"measurement_type": "WestUp", "angle": 90.836111111111, "residual": 0.0, "time": "2020-02-21T18:59:44", "h": 20803.764, "e": -101.798, "z": 46850.058, "f": 51783.64}, {"measurement_type": "EastUp", "angle": 270.87388888889, "residual": 0.0, "time": "2020-02-21T19:00:47", "h": 20803.798, "e": -101.967, "z": 46849.812, "f": 51783.43}, {"measurement_type": "SouthDown", "angle": 246.38416666667, "residual": 0.0, "time": "2020-02-21T19:06:11", "h": 20802.19, "e": -102.864, "z": 46849.295, "f": 51782.3}, {"measurement_type": "NorthUp", "angle": 66.383055555556, "residual": 0.0, "time": "2020-02-21T19:07:06", "h": 20801.777, "e": -103.142, "z": 46849.217, "f": 51782.17}, {"measurement_type": "SouthUp", "angle": 113.6025, "residual": 0.0, "time": "2020-02-21T19:08:29", "h": 20801.758, "e": -103.905, "z": 46849.177, "f": 51782.14}, {"measurement_type": "NorthDown", "angle": 293.60861111111, "residual": 0.0, "time": "2020-02-21T19:09:50", "h": 20802.258, "e": -104.14, "z": 46849.045, "f": 51782.18}], "metadata": {"time": "2020-02-21T18:21:02Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2955917, "baseline": 8.5832537, "starttime": "2020-02-21T19:12:29", "endtime": "2020-02-21T19:16:46", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20731.5333996, "baseline": -70.8456004, "starttime": "2020-02-21T19:21:49", "endtime": "2020-02-21T19:25:39", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47426.5928877, "baseline": 578.0258877, "starttime": "2020-02-21T19:21:49", "endtime": "2020-02-21T19:25:39", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.56694444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.56083333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.564722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.560833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60527777778, "residual": 0.0, "time": "2020-02-21T19:12:29", "h": 20803.104, "e": -103.941, "z": 46848.993, "f": 51782.38}, {"measurement_type": "EastDown", "angle": 90.583055555556, "residual": 0.0, "time": "2020-02-21T19:13:47", "h": 20803.097, "e": -104.161, "z": 46849.071, "f": 51782.47}, {"measurement_type": "WestUp", "angle": 90.833611111111, "residual": 0.0, "time": "2020-02-21T19:15:00", "h": 20802.67, "e": -103.961, "z": 46848.884, "f": 51782.15}, {"measurement_type": "EastUp", "angle": 270.86055555556, "residual": 0.0, "time": "2020-02-21T19:16:46", "h": 20802.803, "e": -104.28, "z": 46848.759, "f": 51782.13}, {"measurement_type": "SouthDown", "angle": 246.38472222222, "residual": 0.0, "time": "2020-02-21T19:21:49", "h": 20801.958, "e": -106.572, "z": 46848.905, "f": 51781.93}, {"measurement_type": "NorthUp", "angle": 66.381388888889, "residual": 0.0, "time": "2020-02-21T19:22:53", "h": 20802.86, "e": -107.886, "z": 46848.571, "f": 51782.04}, {"measurement_type": "SouthUp", "angle": 113.60361111111, "residual": 0.0, "time": "2020-02-21T19:24:27", "h": 20802.333, "e": -107.433, "z": 46848.136, "f": 51781.37}, {"measurement_type": "NorthDown", "angle": 293.60861111111, "residual": 0.0, "time": "2020-02-21T19:25:39", "h": 20802.365, "e": -106.905, "z": 46848.656, "f": 51781.91}], "metadata": {"time": "2020-02-21T18:21:02Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Mark Nelson", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2869806, "baseline": 8.5768364, "starttime": "2020-02-27T19:10:27", "endtime": "2020-02-27T19:17:20", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20733.1496651, "baseline": -69.8563349, "starttime": "2020-02-27T19:26:17", "endtime": "2020-02-27T19:33:22", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47419.6408326, "baseline": 577.4295826, "starttime": "2020-02-27T19:26:17", "endtime": "2020-02-27T19:33:22", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.57333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.571388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.570555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57388888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.62277777778, "residual": 0.0, "time": "2020-02-27T19:10:27", "h": 20804.591, "e": -104.213, "z": 46840.624, "f": 51775.22}, {"measurement_type": "EastDown", "angle": 90.571388888889, "residual": 0.0, "time": "2020-02-27T19:12:16", "h": 20804.293, "e": -104.614, "z": 46840.803, "f": 51775.29}, {"measurement_type": "WestUp", "angle": 90.8325, "residual": 0.0, "time": "2020-02-27T19:15:24", "h": 20803.718, "e": -105.233, "z": 46841.064, "f": 51775.34}, {"measurement_type": "EastUp", "angle": 270.85722222222, "residual": 0.0, "time": "2020-02-27T19:17:20", "h": 20803.383, "e": -105.491, "z": 46841.22, "f": 51775.35}, {"measurement_type": "SouthDown", "angle": 246.3775, "residual": 0.0, "time": "2020-02-27T19:26:17", "h": 20802.578, "e": -106.619, "z": 46841.897, "f": 51775.64}, {"measurement_type": "NorthUp", "angle": 66.375555555556, "residual": 0.0, "time": "2020-02-27T19:28:03", "h": 20802.872, "e": -106.943, "z": 46842.014, "f": 51775.88}, {"measurement_type": "SouthUp", "angle": 113.60777777778, "residual": 0.0, "time": "2020-02-27T19:31:22", "h": 20803.356, "e": -107.72, "z": 46842.384, "f": 51776.37}, {"measurement_type": "NorthDown", "angle": 293.61027777778, "residual": 0.0, "time": "2020-02-27T19:33:22", "h": 20803.218, "e": -107.86, "z": 46842.55, "f": 51776.47}], "metadata": {"time": "2020-02-27T19:10:27Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2767722, "baseline": 8.5779143, "starttime": "2020-02-27T19:37:54", "endtime": "2020-02-27T19:43:26", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20732.4769084, "baseline": -70.6175916, "starttime": "2020-02-27T19:49:15", "endtime": "2020-02-27T19:57:45", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47422.0168114, "baseline": 577.8023114, "starttime": "2020-02-27T19:49:15", "endtime": "2020-02-27T19:57:45", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.568611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.568333333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.60305555556, "residual": 0.0, "time": "2020-02-27T19:37:54", "h": 20802.994, "e": -108.82, "z": 46842.859, "f": 51776.69}, {"measurement_type": "EastDown", "angle": 90.562777777778, "residual": 0.0, "time": "2020-02-27T19:39:36", "h": 20803.192, "e": -108.966, "z": 46842.958, "f": 51776.87}, {"measurement_type": "WestUp", "angle": 90.82, "residual": 0.0, "time": "2020-02-27T19:42:04", "h": 20802.867, "e": -108.948, "z": 46843.195, "f": 51776.98}, {"measurement_type": "EastUp", "angle": 270.8475, "residual": 0.0, "time": "2020-02-27T19:43:26", "h": 20802.901, "e": -109.139, "z": 46843.36, "f": 51777.13}, {"measurement_type": "SouthDown", "angle": 246.37888888889, "residual": 0.0, "time": "2020-02-27T19:49:15", "h": 20803.213, "e": -110.488, "z": 46843.9, "f": 51777.75}, {"measurement_type": "NorthUp", "angle": 66.376666666667, "residual": 0.0, "time": "2020-02-27T19:51:39", "h": 20803.402, "e": -110.831, "z": 46844.062, "f": 51777.97}, {"measurement_type": "SouthUp", "angle": 113.605, "residual": 0.0, "time": "2020-02-27T19:54:25", "h": 20802.975, "e": -110.933, "z": 46844.305, "f": 51778.02}, {"measurement_type": "NorthDown", "angle": 293.60861111111, "residual": 0.0, "time": "2020-02-27T19:57:45", "h": 20802.788, "e": -111.298, "z": 46844.591, "f": 51778.25}], "metadata": {"time": "2020-02-27T19:10:27Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2683694, "baseline": 8.5780491, "starttime": "2020-02-27T20:05:42", "endtime": "2020-02-27T20:11:23", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20734.2678711, "baseline": -70.2031289, "starttime": "2020-02-27T20:18:56", "endtime": "2020-02-27T20:26:39", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47424.3906643, "baseline": 577.6509143, "starttime": "2020-02-27T20:18:56", "endtime": "2020-02-27T20:26:39", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57083333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.567777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.5675, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.58888888889, "residual": 0.0, "time": "2020-02-27T20:05:42", "h": 20803.28, "e": -111.996, "z": 46845.299, "f": 51779.08}, {"measurement_type": "EastDown", "angle": 90.553611111111, "residual": 0.0, "time": "2020-02-27T20:07:08", "h": 20802.905, "e": -111.983, "z": 46845.534, "f": 51779.13}, {"measurement_type": "WestUp", "angle": 90.814166666667, "residual": 0.0, "time": "2020-02-27T20:09:51", "h": 20802.924, "e": -112.159, "z": 46845.719, "f": 51779.3}, {"measurement_type": "EastUp", "angle": 270.84027777778, "residual": 0.0, "time": "2020-02-27T20:11:23", "h": 20803.502, "e": -112.131, "z": 46845.797, "f": 51779.61}, {"measurement_type": "SouthDown", "angle": 246.37972222222, "residual": 0.0, "time": "2020-02-27T20:18:56", "h": 20804.214, "e": -113.769, "z": 46846.545, "f": 51780.61}, {"measurement_type": "NorthUp", "angle": 66.377777777778, "residual": 0.0, "time": "2020-02-27T20:20:42", "h": 20803.926, "e": -113.838, "z": 46846.609, "f": 51780.56}, {"measurement_type": "SouthUp", "angle": 113.60722222222, "residual": 0.0, "time": "2020-02-27T20:23:52", "h": 20804.723, "e": -113.972, "z": 46846.8, "f": 51781.06}, {"measurement_type": "NorthDown", "angle": 293.61138888889, "residual": 0.0, "time": "2020-02-27T20:26:39", "h": 20805.021, "e": -114.12, "z": 46847.005, "f": 51781.33}], "metadata": {"time": "2020-02-27T19:10:27Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.268925, "baseline": 8.581803, "starttime": "2020-02-27T20:34:31", "endtime": "2020-02-27T20:39:55", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20737.9005597, "baseline": -70.3304403, "starttime": "2020-02-27T20:48:02", "endtime": "2020-02-27T20:57:56", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47426.2784612, "baseline": 577.6542112, "starttime": "2020-02-27T20:48:02", "endtime": "2020-02-27T20:57:56", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.5675, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.56805555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.564722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.564166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.58388888889, "residual": 0.0, "time": "2020-02-27T20:34:31", "h": 20805.433, "e": -113.346, "z": 46847.411, "f": 51781.87}, {"measurement_type": "EastDown", "angle": 90.550555555556, "residual": 0.0, "time": "2020-02-27T20:35:58", "h": 20805.637, "e": -113.157, "z": 46847.501, "f": 51782.03}, {"measurement_type": "WestUp", "angle": 90.812777777778, "residual": 0.0, "time": "2020-02-27T20:38:26", "h": 20806.232, "e": -113.229, "z": 46847.674, "f": 51782.46}, {"measurement_type": "EastUp", "angle": 270.83972222222, "residual": 0.0, "time": "2020-02-27T20:39:55", "h": 20806.737, "e": -113.246, "z": 46847.739, "f": 51782.71}, {"measurement_type": "SouthDown", "angle": 246.37666666667, "residual": 0.0, "time": "2020-02-27T20:48:02", "h": 20807.983, "e": -113.245, "z": 46848.247, "f": 51783.63}, {"measurement_type": "NorthUp", "angle": 66.374722222222, "residual": 0.0, "time": "2020-02-27T20:50:53", "h": 20808.253, "e": -113.52, "z": 46848.52, "f": 51783.99}, {"measurement_type": "SouthUp", "angle": 113.61055555556, "residual": 0.0, "time": "2020-02-27T20:55:23", "h": 20807.99, "e": -113.509, "z": 46848.772, "f": 51784.13}, {"measurement_type": "NorthDown", "angle": 293.61333333333, "residual": 0.0, "time": "2020-02-27T20:57:56", "h": 20808.698, "e": -113.78, "z": 46848.958, "f": 51784.55}], "metadata": {"time": "2020-02-27T19:10:27Z", "reviewed": true, "electronics": "0110", "theodolite": "109648", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Payton Cain", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}] \ No newline at end of file diff --git a/etc/residual/BOU20200101.json b/etc/residual/BOU20200101.json new file mode 100644 index 0000000000000000000000000000000000000000..89697e4f9b53fd1a60c0fc499fdfb1500c42d580 --- /dev/null +++ b/etc/residual/BOU20200101.json @@ -0,0 +1 @@ +[{"absolutes": [{"element": "D", "absolute": 8.3851056, "baseline": 8.58571, "starttime": "2020-01-03T17:12:47", "endtime": "2020-01-03T17:16:21", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.0650365, "baseline": -71.7177135, "starttime": "2020-01-03T17:20:48", "endtime": "2020-01-03T17:24:40", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.1529433, "baseline": 578.2041933, "starttime": "2020-01-03T17:20:48", "endtime": "2020-01-03T17:24:40", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.58305555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.583055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.581111111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.58305555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.72055555556, "residual": 0.0, "time": "2020-01-03T17:12:47", "h": 20800.329, "e": -72.242, "z": 46871.49, "f": 51801.81}, {"measurement_type": "EastDown", "angle": 90.682222222222, "residual": 0.0, "time": "2020-01-03T17:14:13", "h": 20800.259, "e": -72.636, "z": 46871.641, "f": 51801.92}, {"measurement_type": "WestUp", "angle": 90.937222222222, "residual": 0.0, "time": "2020-01-03T17:15:22", "h": 20800.259, "e": -72.657, "z": 46871.521, "f": 51801.82}, {"measurement_type": "EastUp", "angle": 270.9775, "residual": 0.0, "time": "2020-01-03T17:16:21", "h": 20800.086, "e": -72.758, "z": 46871.707, "f": 51801.92}, {"measurement_type": "SouthDown", "angle": 246.39555555556, "residual": 0.0, "time": "2020-01-03T17:20:48", "h": 20799.796, "e": -72.898, "z": 46871.802, "f": 51801.89}, {"measurement_type": "NorthUp", "angle": 66.393055555556, "residual": 0.0, "time": "2020-01-03T17:21:46", "h": 20799.852, "e": -72.8, "z": 46871.919, "f": 51802.01}, {"measurement_type": "SouthUp", "angle": 113.58805555556, "residual": 0.0, "time": "2020-01-03T17:22:59", "h": 20799.668, "e": -72.775, "z": 46871.997, "f": 51802.01}, {"measurement_type": "NorthDown", "angle": 293.59083333333, "residual": 0.0, "time": "2020-01-03T17:24:40", "h": 20799.815, "e": -72.813, "z": 46872.077, "f": 51802.14}], "metadata": {"time": "2020-01-03T17:12:47Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3819111, "baseline": 8.5852195, "starttime": "2020-01-03T17:28:08", "endtime": "2020-01-03T17:32:18", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20727.7191551, "baseline": -71.6995949, "starttime": "2020-01-03T17:35:44", "endtime": "2020-01-03T17:39:04", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47451.2425073, "baseline": 578.2107573, "starttime": "2020-01-03T17:35:44", "endtime": "2020-01-03T17:39:04", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.580833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.58138888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58194444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.580555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.7125, "residual": 0.0, "time": "2020-01-03T17:28:08", "h": 20799.734, "e": -72.917, "z": 46872.404, "f": 51802.4}, {"measurement_type": "EastDown", "angle": 90.680277777778, "residual": 0.0, "time": "2020-01-03T17:29:28", "h": 20799.839, "e": -73.319, "z": 46872.542, "f": 51802.54}, {"measurement_type": "WestUp", "angle": 90.935277777778, "residual": 0.0, "time": "2020-01-03T17:30:49", "h": 20800.006, "e": -73.791, "z": 46872.632, "f": 51802.71}, {"measurement_type": "EastUp", "angle": 270.97111111111, "residual": 0.0, "time": "2020-01-03T17:32:18", "h": 20799.867, "e": -74.174, "z": 46872.79, "f": 51802.79}, {"measurement_type": "SouthDown", "angle": 246.39611111111, "residual": 0.0, "time": "2020-01-03T17:35:44", "h": 20799.682, "e": -74.909, "z": 46872.939, "f": 51802.88}, {"measurement_type": "NorthUp", "angle": 66.396388888889, "residual": 0.0, "time": "2020-01-03T17:36:55", "h": 20799.458, "e": -74.806, "z": 46872.973, "f": 51802.84}, {"measurement_type": "SouthUp", "angle": 113.58916666667, "residual": 0.0, "time": "2020-01-03T17:38:15", "h": 20799.393, "e": -74.635, "z": 46873.083, "f": 51802.89}, {"measurement_type": "NorthDown", "angle": 293.59027777778, "residual": 0.0, "time": "2020-01-03T17:39:04", "h": 20799.142, "e": -74.597, "z": 46873.132, "f": 51802.88}], "metadata": {"time": "2020-01-03T17:12:47Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3787167, "baseline": 8.5851619, "starttime": "2020-01-03T17:42:11", "endtime": "2020-01-03T17:45:30", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20727.6357972, "baseline": -71.8987028, "starttime": "2020-01-03T17:47:36", "endtime": "2020-01-03T17:50:24", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47452.1491847, "baseline": 578.2884347, "starttime": "2020-01-03T17:47:36", "endtime": "2020-01-03T17:50:24", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.576944444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.579444444444, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.70388888889, "residual": 0.0, "time": "2020-01-03T17:42:11", "h": 20799.321, "e": -74.172, "z": 46873.369, "f": 51803.12}, {"measurement_type": "EastDown", "angle": 90.6775, "residual": 0.0, "time": "2020-01-03T17:43:20", "h": 20799.265, "e": -74.395, "z": 46873.502, "f": 51803.25}, {"measurement_type": "WestUp", "angle": 90.929444444444, "residual": 0.0, "time": "2020-01-03T17:44:20", "h": 20799.555, "e": -74.818, "z": 46873.588, "f": 51803.4}, {"measurement_type": "EastUp", "angle": 270.96722222222, "residual": 0.0, "time": "2020-01-03T17:45:30", "h": 20799.794, "e": -75.354, "z": 46873.613, "f": 51803.61}, {"measurement_type": "SouthDown", "angle": 246.39722222222, "residual": 0.0, "time": "2020-01-03T17:47:36", "h": 20799.599, "e": -75.677, "z": 46873.788, "f": 51803.62}, {"measurement_type": "NorthUp", "angle": 66.397222222222, "residual": 0.0, "time": "2020-01-03T17:48:29", "h": 20799.463, "e": -75.814, "z": 46873.847, "f": 51803.64}, {"measurement_type": "SouthUp", "angle": 113.58888888889, "residual": 0.0, "time": "2020-01-03T17:49:35", "h": 20799.572, "e": -76.029, "z": 46873.869, "f": 51803.7}, {"measurement_type": "NorthDown", "angle": 293.59055555556, "residual": 0.0, "time": "2020-01-03T17:50:24", "h": 20799.504, "e": -76.076, "z": 46873.939, "f": 51803.72}], "metadata": {"time": "2020-01-03T17:12:47Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3734389, "baseline": 8.5867341, "starttime": "2020-01-03T17:52:25", "endtime": "2020-01-03T17:55:04", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20728.0154327, "baseline": -71.2608173, "starttime": "2020-01-03T17:56:47", "endtime": "2020-01-03T17:59:05", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47452.2343397, "baseline": 578.0225897, "starttime": "2020-01-03T17:56:47", "endtime": "2020-01-03T17:59:05", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.58, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.58027777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.578055555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.5775, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.70166666667, "residual": 0.0, "time": "2020-01-03T17:52:25", "h": 20799.573, "e": -76.779, "z": 46874.079, "f": 51803.87}, {"measurement_type": "EastDown", "angle": 90.670833333333, "residual": 0.0, "time": "2020-01-03T17:53:22", "h": 20799.478, "e": -77.109, "z": 46874.081, "f": 51803.87}, {"measurement_type": "WestUp", "angle": 90.925277777778, "residual": 0.0, "time": "2020-01-03T17:54:09", "h": 20799.423, "e": -77.207, "z": 46874.099, "f": 51803.81}, {"measurement_type": "EastUp", "angle": 270.95861111111, "residual": 0.0, "time": "2020-01-03T17:55:04", "h": 20799.361, "e": -77.562, "z": 46874.168, "f": 51803.89}, {"measurement_type": "SouthDown", "angle": 246.39777777778, "residual": 0.0, "time": "2020-01-03T17:56:47", "h": 20799.248, "e": -77.903, "z": 46874.189, "f": 51803.87}, {"measurement_type": "NorthUp", "angle": 66.397222222222, "residual": 0.0, "time": "2020-01-03T17:57:23", "h": 20799.27, "e": -77.977, "z": 46874.177, "f": 51803.87}, {"measurement_type": "SouthUp", "angle": 113.58972222222, "residual": 0.0, "time": "2020-01-03T17:58:22", "h": 20799.315, "e": -78.154, "z": 46874.254, "f": 51803.93}, {"measurement_type": "NorthDown", "angle": 293.59166666667, "residual": 0.0, "time": "2020-01-03T17:59:05", "h": 20799.272, "e": -78.287, "z": 46874.227, "f": 51803.93}], "metadata": {"time": "2020-01-03T17:12:47Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Jake Morris", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.315175, "baseline": 8.5804386, "starttime": "2020-01-10T21:02:33", "endtime": "2020-01-10T21:07:51", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20736.9818715, "baseline": -69.4726285, "starttime": "2020-01-10T21:12:58", "endtime": "2020-01-10T21:24:22", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.1827973, "baseline": 577.3772973, "starttime": "2020-01-10T21:12:58", "endtime": "2020-01-10T21:24:22", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.57833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.573888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.574166666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57638888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.64916666667, "residual": 0.0, "time": "2020-01-10T21:02:33", "h": 20806.986, "e": -96.226, "z": 46872.425, "f": 51805.46}, {"measurement_type": "EastDown", "angle": 90.601388888889, "residual": 0.0, "time": "2020-01-10T21:04:31", "h": 20806.491, "e": -95.51, "z": 46872.54, "f": 51805.34}, {"measurement_type": "WestUp", "angle": 90.857222222222, "residual": 0.0, "time": "2020-01-10T21:06:28", "h": 20805.724, "e": -95.767, "z": 46872.606, "f": 51805.12}, {"measurement_type": "EastUp", "angle": 270.9025, "residual": 0.0, "time": "2020-01-10T21:07:51", "h": 20805.655, "e": -96.523, "z": 46872.748, "f": 51805.23}, {"measurement_type": "SouthDown", "angle": 246.385, "residual": 0.0, "time": "2020-01-10T21:12:58", "h": 20806.285, "e": -95.897, "z": 46872.621, "f": 51805.37}, {"measurement_type": "NorthUp", "angle": 66.384722222222, "residual": 0.0, "time": "2020-01-10T21:14:18", "h": 20806.004, "e": -95.296, "z": 46872.652, "f": 51805.31}, {"measurement_type": "SouthUp", "angle": 113.59638888889, "residual": 0.0, "time": "2020-01-10T21:16:49", "h": 20806.466, "e": -95.092, "z": 46872.803, "f": 51805.63}, {"measurement_type": "NorthDown", "angle": 293.59972222222, "residual": 0.0, "time": "2020-01-10T21:24:22", "h": 20807.063, "e": -94.084, "z": 46873.146, "f": 51806.13}], "metadata": {"time": "2020-01-10T21:02:33Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3208694, "baseline": 8.5803502, "starttime": "2020-01-10T21:26:21", "endtime": "2020-01-10T21:32:43", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20737.971354, "baseline": -69.435396, "starttime": "2020-01-10T21:37:14", "endtime": "2020-01-10T21:43:30", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.5661274, "baseline": 577.3468774, "starttime": "2020-01-10T21:37:14", "endtime": "2020-01-10T21:43:30", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.573888888889, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.57777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57666666667, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.574722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.6475, "residual": 0.0, "time": "2020-01-10T21:26:21", "h": 20806.756, "e": -93.799, "z": 46872.891, "f": 51805.79}, {"measurement_type": "EastDown", "angle": 90.610833333333, "residual": 0.0, "time": "2020-01-10T21:28:12", "h": 20806.435, "e": -94.385, "z": 46873.256, "f": 51806.01}, {"measurement_type": "WestUp", "angle": 90.865833333333, "residual": 0.0, "time": "2020-01-10T21:30:44", "h": 20806.354, "e": -93.866, "z": 46873.286, "f": 51805.97}, {"measurement_type": "EastUp", "angle": 270.90916666667, "residual": 0.0, "time": "2020-01-10T21:32:43", "h": 20807.497, "e": -93.622, "z": 46873.175, "f": 51806.39}, {"measurement_type": "SouthDown", "angle": 246.38527777778, "residual": 0.0, "time": "2020-01-10T21:37:14", "h": 20807.214, "e": -94.021, "z": 46873.18, "f": 51806.26}, {"measurement_type": "NorthUp", "angle": 66.384444444444, "residual": 0.0, "time": "2020-01-10T21:38:54", "h": 20807.679, "e": -94.545, "z": 46873.218, "f": 51806.44}, {"measurement_type": "SouthUp", "angle": 113.59916666667, "residual": 0.0, "time": "2020-01-10T21:41:09", "h": 20807.861, "e": -94.68, "z": 46873.163, "f": 51806.49}, {"measurement_type": "NorthDown", "angle": 293.60027777778, "residual": 0.0, "time": "2020-01-10T21:43:30", "h": 20806.873, "e": -94.668, "z": 46873.316, "f": 51806.24}], "metadata": {"time": "2020-01-10T21:02:33Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3181611, "baseline": 8.5790924, "starttime": "2020-01-10T21:50:07", "endtime": "2020-01-10T21:56:46", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20740.8272595, "baseline": -69.5659905, "starttime": "2020-01-10T22:01:36", "endtime": "2020-01-10T22:06:25", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.2045993, "baseline": 577.3720993, "starttime": "2020-01-10T22:01:36", "endtime": "2020-01-10T22:06:25", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57583333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.572777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.573611111111, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.63916666667, "residual": 0.0, "time": "2020-01-10T21:50:07", "h": 20808.327, "e": -94.707, "z": 46872.807, "f": 51806.32}, {"measurement_type": "EastDown", "angle": 90.611111111111, "residual": 0.0, "time": "2020-01-10T21:52:57", "h": 20808.123, "e": -94.396, "z": 46872.909, "f": 51806.39}, {"measurement_type": "WestUp", "angle": 90.865833333333, "residual": 0.0, "time": "2020-01-10T21:55:05", "h": 20808.337, "e": -94.265, "z": 46872.902, "f": 51806.44}, {"measurement_type": "EastUp", "angle": 270.90111111111, "residual": 0.0, "time": "2020-01-10T21:56:46", "h": 20808.838, "e": -94.456, "z": 46872.93, "f": 51806.66}, {"measurement_type": "SouthDown", "angle": 246.38277777778, "residual": 0.0, "time": "2020-01-10T22:01:36", "h": 20810.14, "e": -94.623, "z": 46872.812, "f": 51807.05}, {"measurement_type": "NorthUp", "angle": 66.382222222222, "residual": 0.0, "time": "2020-01-10T22:03:13", "h": 20810.232, "e": -94.04, "z": 46872.743, "f": 51807.02}, {"measurement_type": "SouthUp", "angle": 113.60194444444, "residual": 0.0, "time": "2020-01-10T22:05:12", "h": 20810.496, "e": -93.761, "z": 46872.854, "f": 51807.24}, {"measurement_type": "NorthDown", "angle": 293.605, "residual": 0.0, "time": "2020-01-10T22:06:25", "h": 20810.705, "e": -93.843, "z": 46872.921, "f": 51807.37}], "metadata": {"time": "2020-01-10T21:02:33Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.3205917, "baseline": 8.5798399, "starttime": "2020-01-10T22:10:04", "endtime": "2020-01-10T22:16:18", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20743.6458426, "baseline": -69.6216574, "starttime": "2020-01-10T22:21:03", "endtime": "2020-01-10T22:26:42", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47450.2275685, "baseline": 577.3853185, "starttime": "2020-01-10T22:21:03", "endtime": "2020-01-10T22:26:42", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.57555555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.57583333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.5725, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.572777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.64416666667, "residual": 0.0, "time": "2020-01-10T22:10:04", "h": 20810.869, "e": -93.725, "z": 46872.864, "f": 51807.38}, {"measurement_type": "EastDown", "angle": 90.608611111111, "residual": 0.0, "time": "2020-01-10T22:12:03", "h": 20811.361, "e": -93.945, "z": 46872.817, "f": 51807.58}, {"measurement_type": "WestUp", "angle": 90.868333333333, "residual": 0.0, "time": "2020-01-10T22:14:45", "h": 20811.305, "e": -93.727, "z": 46872.808, "f": 51807.49}, {"measurement_type": "EastUp", "angle": 270.90472222222, "residual": 0.0, "time": "2020-01-10T22:16:18", "h": 20811.811, "e": -94.041, "z": 46872.786, "f": 51807.75}, {"measurement_type": "SouthDown", "angle": 246.38083333333, "residual": 0.0, "time": "2020-01-10T22:21:03", "h": 20813.037, "e": -95.061, "z": 46872.817, "f": 51808.2}, {"measurement_type": "NorthUp", "angle": 66.379444444444, "residual": 0.0, "time": "2020-01-10T22:23:09", "h": 20812.858, "e": -95.226, "z": 46872.881, "f": 51808.19}, {"measurement_type": "SouthUp", "angle": 113.60611111111, "residual": 0.0, "time": "2020-01-10T22:25:23", "h": 20813.846, "e": -95.815, "z": 46872.779, "f": 51808.48}, {"measurement_type": "NorthDown", "angle": 293.6075, "residual": 0.0, "time": "2020-01-10T22:26:42", "h": 20813.329, "e": -96.159, "z": 46872.892, "f": 51808.41}], "metadata": {"time": "2020-01-10T21:02:33Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "Abe Claycomb", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}] \ No newline at end of file diff --git a/etc/residual/BOU20200422.json b/etc/residual/BOU20200422.json new file mode 100644 index 0000000000000000000000000000000000000000..17c02fb56a26989edd5c6056d94ffced22b99d68 --- /dev/null +++ b/etc/residual/BOU20200422.json @@ -0,0 +1 @@ +[{"absolutes": [{"element": "D", "absolute": 8.2878833, "baseline": 8.5874457, "starttime": "2020-04-22T16:58:21", "endtime": "2020-04-22T17:02:24", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20717.9633432, "baseline": -70.8844068, "starttime": "2020-04-22T17:06:24", "endtime": "2020-04-22T17:10:19", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47410.5797096, "baseline": 577.1532096, "starttime": "2020-04-22T17:06:24", "endtime": "2020-04-22T17:10:19", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkDown", "angle": 191.56722222222, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.565277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.565833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.56805555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.59472222222, "residual": 0.0, "time": "2020-04-22T16:58:21", "h": 20787.821, "e": -107.791, "z": 46833.4, "f": 51761.21}, {"measurement_type": "EastDown", "angle": 90.578055555556, "residual": 0.0, "time": "2020-04-22T16:59:42", "h": 20787.828, "e": -108.208, "z": 46833.349, "f": 51761.22}, {"measurement_type": "WestUp", "angle": 90.837222222222, "residual": 0.0, "time": "2020-04-22T17:01:05", "h": 20787.92, "e": -108.66, "z": 46833.414, "f": 51761.27}, {"measurement_type": "EastUp", "angle": 270.85472222222, "residual": 0.0, "time": "2020-04-22T17:02:24", "h": 20787.914, "e": -108.624, "z": 46833.246, "f": 51761.17}, {"measurement_type": "SouthDown", "angle": 246.39194444444, "residual": 0.0, "time": "2020-04-22T17:06:24", "h": 20788.918, "e": -109.507, "z": 46833.406, "f": 51761.77}, {"measurement_type": "NorthUp", "angle": 66.391666666667, "residual": 0.0, "time": "2020-04-22T17:07:20", "h": 20788.93, "e": -109.323, "z": 46833.435, "f": 51761.69}, {"measurement_type": "SouthUp", "angle": 113.6, "residual": 0.0, "time": "2020-04-22T17:09:36", "h": 20788.716, "e": -109.682, "z": 46833.468, "f": 51761.67}, {"measurement_type": "NorthDown", "angle": 293.60305555556, "residual": 0.0, "time": "2020-04-22T17:10:19", "h": 20788.827, "e": -109.924, "z": 46833.397, "f": 51761.69}], "metadata": {"time": "2020-04-22T16:58:21Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2796889, "baseline": 8.5865885, "starttime": "2020-04-22T17:13:01", "endtime": "2020-04-22T17:16:27", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20720.412631, "baseline": -70.600369, "starttime": "2020-04-22T17:20:46", "endtime": "2020-04-22T17:24:16", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47410.0768134, "baseline": 577.0655634, "starttime": "2020-04-22T17:20:46", "endtime": "2020-04-22T17:24:16", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "SecondMarkUp", "angle": 11.565833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkDown", "angle": 191.56777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.56777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.565277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.58388888889, "residual": 0.0, "time": "2020-04-22T17:13:01", "h": 20789.331, "e": -110.782, "z": 46833.405, "f": 51761.86}, {"measurement_type": "EastDown", "angle": 90.568611111111, "residual": 0.0, "time": "2020-04-22T17:14:16", "h": 20789.409, "e": -110.917, "z": 46833.333, "f": 51761.83}, {"measurement_type": "WestUp", "angle": 90.828055555556, "residual": 0.0, "time": "2020-04-22T17:15:21", "h": 20789.489, "e": -111.064, "z": 46833.343, "f": 51761.9}, {"measurement_type": "EastUp", "angle": 270.85166666667, "residual": 0.0, "time": "2020-04-22T17:16:27", "h": 20789.68, "e": -111.185, "z": 46833.233, "f": 51761.86}, {"measurement_type": "SouthDown", "angle": 246.39027777778, "residual": 0.0, "time": "2020-04-22T17:20:46", "h": 20790.54, "e": -112.483, "z": 46833.095, "f": 51762.13}, {"measurement_type": "NorthUp", "angle": 66.389722222222, "residual": 0.0, "time": "2020-04-22T17:22:13", "h": 20791.241, "e": -112.957, "z": 46833.016, "f": 51762.31}, {"measurement_type": "SouthUp", "angle": 113.60361111111, "residual": 0.0, "time": "2020-04-22T17:23:16", "h": 20790.985, "e": -113.068, "z": 46832.981, "f": 51762.19}, {"measurement_type": "NorthDown", "angle": 293.60666666667, "residual": 0.0, "time": "2020-04-22T17:24:16", "h": 20791.286, "e": -113.117, "z": 46832.953, "f": 51762.27}], "metadata": {"time": "2020-04-22T16:58:21Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2700361, "baseline": 8.5860246, "starttime": "2020-04-22T17:26:53", "endtime": "2020-04-22T17:30:19", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20722.4714763, "baseline": -70.2677737, "starttime": "2020-04-22T17:34:20", "endtime": "2020-04-22T17:37:43", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47409.4634293, "baseline": 576.9001793, "starttime": "2020-04-22T17:34:20", "endtime": "2020-04-22T17:37:43", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.56805555556, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.56833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.565277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.565833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.57666666667, "residual": 0.0, "time": "2020-04-22T17:26:53", "h": 20791.409, "e": -114.21, "z": 46832.93, "f": 51762.31}, {"measurement_type": "EastDown", "angle": 90.561666666667, "residual": 0.0, "time": "2020-04-22T17:27:57", "h": 20791.255, "e": -114.262, "z": 46832.86, "f": 51762.15}, {"measurement_type": "WestUp", "angle": 90.820833333333, "residual": 0.0, "time": "2020-04-22T17:29:03", "h": 20790.607, "e": -114.163, "z": 46832.832, "f": 51761.89}, {"measurement_type": "EastUp", "angle": 270.83527777778, "residual": 0.0, "time": "2020-04-22T17:30:19", "h": 20790.798, "e": -114.506, "z": 46832.57, "f": 51761.75}, {"measurement_type": "SouthDown", "angle": 246.38611111111, "residual": 0.0, "time": "2020-04-22T17:34:20", "h": 20793.23, "e": -115.927, "z": 46832.451, "f": 51762.5}, {"measurement_type": "NorthUp", "angle": 66.386944444444, "residual": 0.0, "time": "2020-04-22T17:34:59", "h": 20793.178, "e": -115.832, "z": 46832.436, "f": 51762.57}, {"measurement_type": "SouthUp", "angle": 113.60555555556, "residual": 0.0, "time": "2020-04-22T17:36:18", "h": 20792.705, "e": -115.434, "z": 46832.579, "f": 51762.53}, {"measurement_type": "NorthDown", "angle": 293.60722222222, "residual": 0.0, "time": "2020-04-22T17:37:43", "h": 20791.844, "e": -115.239, "z": 46832.787, "f": 51762.35}], "metadata": {"time": "2020-04-22T16:58:21Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}, {"absolutes": [{"element": "D", "absolute": 8.2633694, "baseline": 8.5865679, "starttime": "2020-04-22T17:39:50", "endtime": "2020-04-22T17:43:07", "shift": 0.0, "valid": true}, {"element": "H", "absolute": 20722.5313831, "baseline": -70.6621169, "starttime": "2020-04-22T17:46:19", "endtime": "2020-04-22T17:49:39", "shift": 0.0, "valid": true}, {"element": "Z", "absolute": 47410.070229, "baseline": 577.129229, "starttime": "2020-04-22T17:46:19", "endtime": "2020-04-22T17:49:39", "shift": 0.0, "valid": true}], "azimuth": 199.1383, "hemisphere": 1, "measurements": [{"measurement_type": "FirstMarkDown", "angle": 191.56777777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkDown", "angle": 191.5675, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "FirstMarkUp", "angle": 11.565833333333, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "SecondMarkUp", "angle": 11.565277777778, "residual": 0.0, "time": null, "h": null, "e": null, "z": null, "f": null}, {"measurement_type": "WestDown", "angle": 270.56805555556, "residual": 0.0, "time": "2020-04-22T17:39:50", "h": 20791.975, "e": -116.134, "z": 46832.855, "f": 51762.39}, {"measurement_type": "EastDown", "angle": 90.555555555556, "residual": 0.0, "time": "2020-04-22T17:40:44", "h": 20791.849, "e": -116.444, "z": 46832.734, "f": 51762.37}, {"measurement_type": "WestUp", "angle": 90.809444444444, "residual": 0.0, "time": "2020-04-22T17:42:04", "h": 20792.026, "e": -117.444, "z": 46832.8, "f": 51762.47}, {"measurement_type": "EastUp", "angle": 270.83361111111, "residual": 0.0, "time": "2020-04-22T17:43:07", "h": 20792.017, "e": -117.551, "z": 46832.739, "f": 51762.4}, {"measurement_type": "SouthDown", "angle": 246.38888888889, "residual": 0.0, "time": "2020-04-22T17:46:19", "h": 20792.558, "e": -117.926, "z": 46832.81, "f": 51762.72}, {"measurement_type": "NorthUp", "angle": 66.388055555556, "residual": 0.0, "time": "2020-04-22T17:47:10", "h": 20793.064, "e": -118.251, "z": 46832.932, "f": 51762.94}, {"measurement_type": "SouthUp", "angle": 113.60694444444, "residual": 0.0, "time": "2020-04-22T17:48:34", "h": 20793.514, "e": -118.953, "z": 46832.973, "f": 51763.25}, {"measurement_type": "NorthDown", "angle": 293.60888888889, "residual": 0.0, "time": "2020-04-22T17:49:39", "h": 20793.638, "e": -119.445, "z": 46833.049, "f": 51763.36}], "metadata": {"time": "2020-04-22T16:58:21Z", "reviewed": true, "electronics": "0110", "theodolite": "108449", "mark_name": "AZ", "mark_azimuth": 199.1383, "pier_name": "MainPCDCP", "pier_correction": -22, "observer": "John Spritzer", "reviewer": "Bill Worthington", "station": "BOU"}, "pier_correction": -22.0, "scale_value": null}] \ No newline at end of file diff --git a/etc/residual/Caldata/CMO/2015/CMO20150242140.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150242140.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..e9221ae181e6dccb1c040610c79e2000a729fe24 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150242140.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20150312045.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150312045.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..7e9743e99d9d87831c35c4963dfd48f7f5d47a89 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150312045.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20150382118.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150382118.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..2b424cbffe3f699d2cd550af7fe015184a813ddc Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150382118.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20150452047.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150452047.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..6e04c369c4342e9aa4d38a7fcaa978bf255644b8 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150452047.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20150522238.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150522238.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..11e1ee2d6c58ba00c91f41176af07171ad3b9d02 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150522238.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20150592055.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150592055.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..2b309ed9bb2fc50b5f9e7a8f17ba2765dc730b44 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150592055.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20150802144.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150802144.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..1ac0109771cee477605e36ce0ba589f174a2adfa Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150802144.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20150871947.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150871947.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..9e2d23551f99ac30eb0aff54142d825ffdd139e8 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150871947.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20150942001.xlsm b/etc/residual/Caldata/CMO/2015/CMO20150942001.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..dcd5ddb137697c94303b84927590cb7f0f4c9d34 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20150942001.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151022138.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151022138.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..f841e626ee818de49f97d5d2beacc60b04932dad Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151022138.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151081856.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151081856.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..ac8edf5e1798b2a39e97c7f47d1ce5663c7b4939 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151081856.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151151919.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151151919.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..d0f2f1af89e1c51e1880d9e7311235b6aca9182e Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151151919.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151371803.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151371803.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..3ad463298420adb86047fcaa93c4d04fc82a310f Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151371803.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151572030.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151572030.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..352b7ae77612d310c67238bc428ffdcd618d9f09 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151572030.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151642030.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151642030.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..5442be0f2072dd950ed62625075eee7e635969b0 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151642030.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151720045.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151720045.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..b6dac6d9e4b0b7cb9b3327681cbbde6ca6b6ebda Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151720045.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151782044.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151782044.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..dca6870c64d52b0b58433e91a617dfde891862b3 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151782044.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151852045.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151852045.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..bbe550bf8cd9d18d27ef528f5955f46d9fc3535e Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151852045.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20151921943.xlsm b/etc/residual/Caldata/CMO/2015/CMO20151921943.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..3b4d3da3d825e4027376c945eb70f34ab6bc388e Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20151921943.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152092206.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152092206.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..80c5d0b8fa383a236506bbed79ba16b9b2acb328 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152092206.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152142145.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152142145.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..6d186975721fe5a107551571e6f1d29ca39a06dc Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152142145.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152210109.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152210109.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..7bf2410db112bd9a7719e39e989a7055c989a686 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152210109.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152271939.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152271939.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..7cbc062630410c97683b0bb3f38c383c1d82e586 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152271939.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152350029.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152350029.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..0c1edbfb7d937163217bfb07ba181726f4a51de8 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152350029.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152412009.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152412009.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..b917b73dd0d522ff0877eced39816822c6421287 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152412009.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152481957.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152481957.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..4e317c8785f0d9e38e2e71753fe0fa343d5f7cdc Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152481957.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152551948.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152551948.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..3098c657e5fc09e31f7d785b4153f1ef78c8a6c7 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152551948.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152640443.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152640443.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..7602b9745e59a33bde2fc32c831aaee7f43d1f66 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152640443.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152691948.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152691948.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..7199388a3ee0bd4f8d13d84b7efa2fc241aacf32 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152691948.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152761954.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152761954.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..832f4c7ee8aaf50b2a180ad92495ee1e0c8b855d Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152761954.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152840043.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152840043.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..83a50d0b760482b91c561043519acb06573cbd97 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152840043.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152910229.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152910229.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..7460546f42f234fa86e1559dfd1981671eb4852c Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152910229.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20152971940.xlsm b/etc/residual/Caldata/CMO/2015/CMO20152971940.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..5784ac3e3f2ecb353fae79b449de03052e58c54b Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20152971940.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153042027.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153042027.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..7eb824c58a7da46fa47d3398d9298da7204084ab Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153042027.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153112110.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153112110.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..1d7190e13c952ecdde7117b808e0086f67768c5b Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153112110.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153182110.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153182110.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..ab80b2945818aac2e163685c39498ed62d3743df Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153182110.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153270200.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153270200.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..115cfcfd237f783c01ba45fe54379d62975d0888 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153270200.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153312150.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153312150.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..22c12be201a7112ada79084967a6526ff9f490fd Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153312150.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153392115.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153392115.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..fe5c971293d77dc33a0a43e20257b27c21bdf920 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153392115.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153462126.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153462126.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..76add8f1fc5a2eab2879d5861c87790814bd6774 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153462126.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153552019.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153552019.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..7878fd89e5b29714d386b3a024822db04b5ecc3d Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153552019.xlsm differ diff --git a/etc/residual/Caldata/CMO/2015/CMO20153592215.xlsm b/etc/residual/Caldata/CMO/2015/CMO20153592215.xlsm new file mode 100755 index 0000000000000000000000000000000000000000..81fc2ed6e1c386388abc9cc8f3cac09eb2643026 Binary files /dev/null and b/etc/residual/Caldata/CMO/2015/CMO20153592215.xlsm differ diff --git a/geomagio/Controller.py b/geomagio/Controller.py index f6517a4cc9bf97149c9305fc519953c0112622af..d05f45ae4e8d4848da8e062adddb8540f839e3ef 100644 --- a/geomagio/Controller.py +++ b/geomagio/Controller.py @@ -1,12 +1,14 @@ """Controller class for geomag algorithms""" -from __future__ import absolute_import, print_function -from builtins import str as unicode import argparse -import sys from io import BytesIO +import sys +from typing import List, Optional, Tuple, Union + from obspy.core import Stream, UTCDateTime -from .algorithm import algorithms, AlgorithmException + +from .algorithm import Algorithm, algorithms, AlgorithmException +from .DerivedTimeseriesFactory import DerivedTimeseriesFactory from .PlotTimeseriesFactory import PlotTimeseriesFactory from .StreamTimeseriesFactory import StreamTimeseriesFactory from . import TimeseriesUtility, Util @@ -46,12 +48,29 @@ class Controller(object): recursively backup so it can update all missing data. """ - def __init__(self, inputFactory, outputFactory, algorithm): - self._inputFactory = inputFactory + def __init__( + self, + inputFactory, + outputFactory, + algorithm: Optional[Algorithm] = None, + inputInterval: Optional[str] = None, + outputInterval: Optional[str] = None, + ): self._algorithm = algorithm + self._inputFactory = inputFactory + self._inputInterval = inputInterval self._outputFactory = outputFactory - - def _get_input_timeseries(self, observatory, channels, starttime, endtime): + self._outputInterval = outputInterval + + def _get_input_timeseries( + self, + observatory, + channels, + starttime, + endtime, + algorithm=None, + interval=None, + ): """Get timeseries from the input factory for requested options. Parameters @@ -74,12 +93,13 @@ class Controller(object): ------- timeseries : obspy.core.Stream """ + algorithm = algorithm or self._algorithm timeseries = Stream() for obs in observatory: # get input interval for observatory # do this per observatory in case an # algorithm needs different amounts of data - input_start, input_end = self._algorithm.get_input_interval( + input_start, input_end = algorithm.get_input_interval( start=starttime, end=endtime, observatory=obs, channels=channels ) if input_start is None or input_end is None: @@ -89,6 +109,7 @@ class Controller(object): starttime=input_start, endtime=input_end, channels=channels, + interval=interval or self._inputInterval, ) return timeseries @@ -115,7 +136,14 @@ class Controller(object): t.stats.channel = to_name return timeseries - def _get_output_timeseries(self, observatory, channels, starttime, endtime): + def _get_output_timeseries( + self, + observatory, + channels, + starttime, + endtime, + interval=None, + ): """Get timeseries from the output factory for requested options. Parameters @@ -136,11 +164,15 @@ class Controller(object): timeseries = Stream() for obs in observatory: timeseries += self._outputFactory.get_timeseries( - observatory=obs, starttime=starttime, endtime=endtime, channels=channels + observatory=obs, + starttime=starttime, + endtime=endtime, + channels=channels, + interval=interval or self._outputInterval, ) return timeseries - def run(self, options, input_timeseries=None): + def _run(self, options, input_timeseries=None): """run controller Parameters ---------- @@ -151,47 +183,136 @@ class Controller(object): Used by run_as_update to save a double input read, since it has already read the input to confirm data can be produced. """ - algorithm = self._algorithm - input_channels = options.inchannels or algorithm.get_input_channels() - output_channels = options.outchannels or algorithm.get_output_channels() + self.run( + observatory=options.observatory, + starttime=options.starttime, + endtime=options.endtime, + input_channels=options.inchannels, + input_timeseries=input_timeseries, + output_channels=options.outchannels, + input_interval=options.input_interval or options.interval, + output_interval=options.output_interval or options.interval, + no_trim=options.no_trim, + rename_input_channel=options.rename_input_channel, + rename_output_channel=options.rename_output_channel, + realtime=options.realtime, + ) + + def _run_as_update(self, options, update_count=0): + """Updates data. + Parameters + ---------- + options: dictionary + The dictionary of all the command line arguments. Could in theory + contain other options passed in by the controller. + + Notes + ----- + Finds gaps in the target data, and if there's new data in the input + source, calls run with the start/end time of a given gap to fill + in. + It checks the start of the target data, and if it's missing, and + there's new data available, it backs up the starttime/endtime, + and recursively calls itself, to check the previous period, to see + if new data is available there as well. Calls run for each new + period, oldest to newest. + """ + self.run_as_update( + observatory=options.observatory, + output_observatory=options.output_observatory, + starttime=options.starttime, + endtime=options.endtime, + input_channels=options.inchannels, + output_channels=options.outchannels, + input_interval=options.input_interval or options.interval, + output_interval=options.output_interval or options.interval, + no_trim=options.no_trim, + realtime=options.realtime, + rename_input_channel=options.rename_input_channel, + rename_output_channel=options.rename_output_channel, + update_limit=options.update_limit, + ) + + def run( + self, + observatory: List[str], + starttime: UTCDateTime, + endtime: UTCDateTime, + algorithm: Optional[Algorithm] = None, + input_channels: Optional[List[str]] = None, + input_timeseries: Optional[Stream] = None, + output_channels: Optional[List[str]] = None, + input_interval: Optional[str] = None, + output_interval: Optional[str] = None, + no_trim: bool = False, + realtime: Union[bool, int] = False, + rename_input_channel: Optional[List[List[str]]] = None, + rename_output_channel: Optional[List[List[str]]] = None, + ): + """Run algorithm for a specific time range. + + Parameters + ---------- + observatory: the observatory or list of observatories for processing + starttime: time of first data + endtime: time of last data + input_channels: list of channels to read + input_timeseries: used by run_as_update, which has already read input. + output_channels: list of channels to write + input_interval: input data interval + output_interval: output data interval + no_trim: whether to trim output to starttime/endtime interval + realtime: number of seconds in realtime interval + rename_input_channel: list of input channel renames + rename_output_channel: list of output channel renames + """ + # ensure realtime is a valid value: + if realtime <= 0: + realtime = False + algorithm = algorithm or self._algorithm + input_channels = input_channels or algorithm.get_input_channels() + output_channels = output_channels or algorithm.get_output_channels() + input_interval = input_interval or self._inputInterval + output_interval = output_interval or self._outputInterval next_starttime = algorithm.get_next_starttime() - starttime = next_starttime or options.starttime - endtime = options.endtime + starttime = next_starttime or starttime # input timeseries = input_timeseries or self._get_input_timeseries( - observatory=options.observatory, + algorithm=algorithm, + observatory=observatory, starttime=starttime, endtime=endtime, channels=input_channels, + interval=input_interval, ) if timeseries.count() == 0: # no data to process return # pre-process - if next_starttime and options.realtime: + if next_starttime and realtime: # when running a stateful algorithms with the realtime option # pad/trim timeseries to the interval: # [next_starttime, max(timeseries.endtime, now-options.realtime)] input_start, input_end = TimeseriesUtility.get_stream_start_end_times( timeseries, without_gaps=True ) - realtime_gap = endtime - options.realtime + realtime_gap = endtime - realtime if input_end < realtime_gap: input_end = realtime_gap # pad to the start of the "realtime gap" TimeseriesUtility.pad_timeseries(timeseries, next_starttime, input_end) # process - if options.rename_input_channel: + if rename_input_channel: timeseries = self._rename_channels( - timeseries=timeseries, renames=options.rename_input_channel + timeseries=timeseries, renames=rename_input_channel ) processed = algorithm.process(timeseries) # trim if --no-trim is not set - if not options.no_trim: + if not no_trim: processed.trim(starttime=starttime, endtime=endtime) - if options.rename_output_channel: + if rename_output_channel: processed = self._rename_channels( - timeseries=processed, renames=options.rename_output_channel + timeseries=processed, renames=rename_output_channel ) # output self._outputFactory.put_timeseries( @@ -199,15 +320,44 @@ class Controller(object): starttime=starttime, endtime=endtime, channels=output_channels, + interval=output_interval, ) - def run_as_update(self, options, update_count=0): - """Updates data. + def run_as_update( + self, + observatory: List[str], + output_observatory: List[str], + starttime: UTCDateTime, + endtime: UTCDateTime, + algorithm: Optional[Algorithm] = None, + input_channels: Optional[List[str]] = None, + output_channels: Optional[List[str]] = None, + input_interval: Optional[str] = None, + output_interval: Optional[str] = None, + no_trim: bool = False, + realtime: Union[bool, int] = False, + rename_input_channel: Optional[List[List[str]]] = None, + rename_output_channel: Optional[List[List[str]]] = None, + update_limit: int = 1, + update_count: int = 0, + ): + """Try to fill gaps in output data. + Parameters ---------- - options: dictionary - The dictionary of all the command line arguments. Could in theory - contain other options passed in by the controller. + observatory: list of observatories for input + output_observatory: list of observatories for output + starttime: time of first data + endtime: time of last data + input_channels: list of channels to read + input_timeseries: used by run_as_update, which has already read input. + output_channels: list of channels to write + input_interval: input data interval + output_interval: output data interval + no_trim: whether to trim output to starttime/endtime interval + realtime: number of seconds in realtime interval + rename_input_channel: list of input channel renames + rename_output_channel: list of output channel renames Notes ----- @@ -221,29 +371,30 @@ class Controller(object): period, oldest to newest. """ # If an update_limit is set, make certain we don't step past it. - if options.update_limit != 0: - if update_count >= options.update_limit: - return - algorithm = self._algorithm + if update_limit > 0 and update_count >= update_limit: + return + algorithm = algorithm or self._algorithm if algorithm.get_next_starttime() is not None: raise AlgorithmException("Stateful algorithms cannot use run_as_update") - input_channels = options.inchannels or algorithm.get_input_channels() - output_observatory = options.output_observatory - output_channels = options.outchannels or algorithm.get_output_channels() + input_channels = input_channels or algorithm.get_input_channels() + output_channels = output_channels or algorithm.get_output_channels() + input_interval = input_interval or self._inputInterval + output_interval = output_interval or self._outputInterval print( "checking gaps", - options.starttime, - options.endtime, + starttime, + endtime, output_observatory, output_channels, file=sys.stderr, ) # request output to see what has already been generated output_timeseries = self._get_output_timeseries( - observatory=options.output_observatory, - starttime=options.starttime, - endtime=options.endtime, + observatory=output_observatory, + starttime=starttime, + endtime=endtime, channels=output_channels, + interval=output_interval, ) if len(output_timeseries) > 0: # find gaps in output, so they can be updated @@ -253,44 +404,74 @@ class Controller(object): else: output_gaps = [ [ - options.starttime, - options.endtime, + starttime, + endtime, # next sample time not used None, ] ] for output_gap in output_gaps: input_timeseries = self._get_input_timeseries( - observatory=options.observatory, + algorithm=algorithm, + observatory=observatory, starttime=output_gap[0], endtime=output_gap[1], channels=input_channels, + interval=input_interval, ) if not algorithm.can_produce_data( starttime=output_gap[0], endtime=output_gap[1], stream=input_timeseries ): continue # check for fillable gap at start - if output_gap[0] == options.starttime: + if output_gap[0] == starttime: # found fillable gap at start, recurse to previous interval - interval = options.endtime - options.starttime - starttime = options.starttime - interval - endtime = options.starttime - 1 - options.starttime = starttime - options.endtime = endtime - self.run_as_update(options, update_count + 1) + interval = endtime - starttime + recurse_starttime = starttime - interval + recurse_endtime = starttime - 1 + self.run_as_update( + algorithm=algorithm, + observatory=observatory, + output_observatory=output_observatory, + starttime=recurse_starttime, + endtime=recurse_endtime, + input_channels=input_channels, + output_channels=output_channels, + input_interval=input_interval, + output_interval=output_interval, + no_trim=no_trim, + realtime=realtime, + rename_input_channel=rename_input_channel, + rename_output_channel=rename_output_channel, + update_limit=update_limit, + update_count=update_count + 1, + ) # fill gap - options.starttime = output_gap[0] - options.endtime = output_gap[1] + gap_starttime = output_gap[0] + gap_endtime = output_gap[1] print( "processing", - options.starttime, - options.endtime, + gap_starttime, + gap_endtime, output_observatory, output_channels, file=sys.stderr, ) - self.run(options, input_timeseries) + self.run( + algorithm=algorithm, + observatory=observatory, + starttime=gap_starttime, + endtime=gap_endtime, + input_channels=input_channels, + input_timeseries=input_timeseries, + output_channels=output_channels, + input_interval=input_interval, + output_interval=output_interval, + no_trim=no_trim, + realtime=realtime, + rename_input_channel=rename_input_channel, + rename_output_channel=rename_output_channel, + ) def get_input_factory(args): @@ -424,7 +605,7 @@ def get_output_factory(args): elif output_type == "miniseed": # TODO: deal with other miniseed arguments locationcode = args.outlocationcode or args.locationcode or None - output_factory = edge.EdgeFactory( + output_factory = edge.MiniSeedFactory( host=args.output_host, port=args.output_read_port, write_port=args.output_port, @@ -455,7 +636,15 @@ def get_output_factory(args): return output_factory -def main(args): +def get_realtime_interval(interval_seconds: int) -> Tuple[UTCDateTime, UTCDateTime]: + # calculate endtime/starttime + now = UTCDateTime() + endtime = UTCDateTime(now.year, now.month, now.day, now.hour, now.minute) + starttime = endtime - interval_seconds + return starttime, endtime + + +def main(args: Optional[List[str]] = None): """command line factory for geomag algorithms Inputs @@ -467,13 +656,16 @@ def main(args): parses command line options using argparse, then calls the controller with instantiated I/O factories, and algorithm(s) """ + # parse command line arguments by default + if args is None: + args = parse_args(sys.argv[1:]) # only try to parse deprecated arguments if they've been enabled if args.enable_deprecated_arguments: parse_deprecated_arguments(args) # make sure observatory is a tuple - if isinstance(args.observatory, (str, unicode)): + if isinstance(args.observatory, str): args.observatory = (args.observatory,) if args.output_observatory is None: @@ -490,14 +682,16 @@ def main(args): if args.realtime: if args.realtime is True: # convert interval to number of seconds - if args.interval == "minute": + if args.interval == "day": + args.realtime = 172800 + elif args.interval == "hour": + args.realtime = 7200 + elif args.interval == "minute": args.realtime = 3600 else: args.realtime = 600 # calculate endtime/starttime - now = UTCDateTime() - args.endtime = UTCDateTime(now.year, now.month, now.day, now.hour, now.minute) - args.starttime = args.endtime - args.realtime + args.starttime, args.endtime = get_realtime_interval(args.realtime) if args.observatory_foreach: observatory = args.observatory @@ -531,15 +725,17 @@ def _main(args): """ # create controller input_factory = get_input_factory(args) + if args.input_derived: + input_factory = DerivedTimeseriesFactory(input_factory) output_factory = get_output_factory(args) algorithm = algorithms[args.algorithm]() algorithm.configure(args) controller = Controller(input_factory, output_factory, algorithm) if args.update: - controller.run_as_update(args) + controller._run_as_update(args) else: - controller.run(args) + controller._run(args) def parse_args(args): @@ -572,6 +768,12 @@ def parse_args(args): help='Input format (Default "edge")', ) + input_group.add_argument( + "--input-derived", + action="store_true", + default=False, + help="Wrap the input factory in a DerivedTimeseriesFactory", + ) input_group.add_argument( "--input-file", help="Read from specified file", metavar="FILE" ) @@ -700,6 +902,8 @@ def parse_args(args): const=True, help=""" Run the last N seconds. + Default 172800 (last 2 days) when interval is day, + Default 7200 (last 2 hours) when interval is hour, Default 3600 (last hour) when interval is minute, Default 600 (last 10 minutes) otherwise. """, diff --git a/geomagio/DerivedTimeseriesFactory.py b/geomagio/DerivedTimeseriesFactory.py new file mode 100644 index 0000000000000000000000000000000000000000..273b18dd25a8d8409c45e69cf0274c03bd003647 --- /dev/null +++ b/geomagio/DerivedTimeseriesFactory.py @@ -0,0 +1,207 @@ +from typing import List, Optional + +from obspy import Stream, Trace, UTCDateTime + +from .algorithm import Algorithm, DeltaFAlgorithm, XYZAlgorithm +from .TimeseriesFactory import TimeseriesFactory, TimeseriesUtility + + +class DerivedTimeseriesFactory(TimeseriesFactory): + factory: TimeseriesFactory + + def __init__(self, factory: TimeseriesFactory): + self.factory = factory + super().__init__( + observatory=factory.observatory, + channels=factory.channels, + type=factory.type, + interval=factory.interval, + urlTemplate=factory.urlTemplate, + urlInterval=factory.urlInterval, + ) + + def get_timeseries( + self, + starttime: UTCDateTime, + endtime: UTCDateTime, + observatory: str, + channels: List[str], + interval: str, + add_empty_channels: bool = True, + derive_missing: bool = True, + type: Optional[str] = None, + ) -> Stream: + type = type or self.type + timeseries = self.factory.get_timeseries( + starttime=starttime, + endtime=endtime, + observatory=observatory, + channels=channels, + type=type, + interval=interval, + add_empty_channels=False, + ) + missing = get_missing(timeseries, channels) + if missing and derive_missing: + timeseries += self._get_derived_channels( + starttime=starttime, + endtime=endtime, + observatory=observatory, + channels=channels, + data_type=type, + interval=interval, + timeseries=timeseries, + ) + missing = get_missing(timeseries, channels) + if missing and add_empty_channels: + for channel in missing: + timeseries += self._get_empty_trace( + starttime=starttime, + endtime=endtime, + observatory=observatory, + channel=channel, + data_type=type, + interval=interval, + ) + # file-based factories return all channels found in file + timeseries = Stream([t for t in timeseries if t.stats.channel in channels]) + for channel in channels: + self._set_metadata( + stream=timeseries.select(channel=channel), + observatory=observatory, + channel=channel, + type=type, + interval=interval, + ) + return timeseries + + def _get_derived_channels( + self, + starttime: UTCDateTime, + endtime: UTCDateTime, + observatory: str, + channels: List[str], + data_type: str, + interval: str, + timeseries: Stream, + ): + """calculate derived channels""" + input_timeseries = timeseries.copy() + input_channels = [] + for channel in channels: + input_channels += self._get_derived_input_channels(channel, data_type) + missing_inputs = get_missing(input_timeseries, list(set(input_channels))) + if missing_inputs: + input_timeseries += self.factory.get_timeseries( + starttime=starttime, + endtime=endtime, + observatory=observatory, + channels=missing_inputs, + type=data_type, + interval=interval, + add_empty_channels=True, + ) + output_timeseries = Stream() + for channel in channels: + if channel in get_missing(output_timeseries, channels): + derived = self._derive_trace( + input_timeseries=input_timeseries, + channel=channel, + data_type=data_type, + ) + for channel in get_missing( + output_timeseries, TimeseriesUtility.get_channels(stream=derived) + ): + output_timeseries += derived.select(channel=channel) + return output_timeseries + + def _get_derived_input_channels(self, channel: str, data_type: str) -> List[str]: + """get channels required to calculate desired channel""" + if data_type == "variation": + if channel == "G": + return ["H", "E", "Z", "F"] + elif channel in ["X", "Y", "D"]: + return ["H", "E"] + else: + if channel == "G": + return ["X", "Y", "Z", "F"] + elif channel in ["H", "D"]: + return ["X", "Y"] + return [] + + def _derive_trace( + self, input_timeseries: Stream, channel: str, data_type: str + ) -> Stream: + """Process timeseries based on desired channel + + Note: All derived channels are returned + """ + if data_type == "variation": + if channel == "G": + return DeltaFAlgorithm(informat="obs").process( + timeseries=input_timeseries + ) + elif channel in ["X", "Y"]: + return XYZAlgorithm(informat="obs", outformat="geo").process( + timeseries=input_timeseries + ) + elif channel == "D": + return XYZAlgorithm(informat="obs", outformat="obsd").process( + timeseries=input_timeseries + ) + else: + if channel == "G": + return DeltaFAlgorithm(informat="geo").process( + timeseries=input_timeseries + ) + elif channel in ["H", "D"]: + return XYZAlgorithm(informat="geo", outformat="mag").process( + timeseries=input_timeseries + ) + return Stream() + + def _get_empty_trace( + self, + starttime: UTCDateTime, + endtime: UTCDateTime, + observatory: str, + channel: str, + data_type: str, + interval: str, + network: str = "NT", + location: str = "", + ) -> Trace: + """creates empty trace""" + return self.factory._get_empty_trace( + starttime, + endtime, + observatory, + channel, + data_type, + interval, + network=network, + location=location, + ) + + def _set_metadata( + self, stream: Stream, observatory: str, channel: str, type: str, interval: str + ): + """set metadata for a given stream/channel + Parameters + ---------- + observatory + observatory code + channel + edge channel code {MVH, MVE, MVD, ...} + type + data type {definitive, quasi-definitive, variation} + interval + interval length {minute, second} + """ + return self.factory._set_metadata(stream, observatory, channel, type, interval) + + +def get_missing(input: Stream, desired: List[str]) -> List[str]: + """Return missing channels from input""" + present = TimeseriesUtility.get_channels(stream=input) + return list(set(desired).difference(set(present))) diff --git a/geomagio/Metadata.py b/geomagio/Metadata.py index ded8a141ddf9a20a39e5c1f4e6c994a136320f3b..b29a1e247e8f061a352e9eaf88d0700616ca4201 100644 --- a/geomagio/Metadata.py +++ b/geomagio/Metadata.py @@ -60,6 +60,29 @@ _INSTRUMENT_METADATA = [ }, }, }, + { + "network": "NT", + "station": "BXX", + "start_time": None, + "end_time": None, + "instrument": { + "type": "Narod", + "channels": { + "U": [ + {"channel": "U_Volt", "offset": 0, "scale": 100}, + {"channel": "U_Bin", "offset": 0, "scale": 500}, + ], + "V": [ + {"channel": "V_Volt", "offset": 0, "scale": 100}, + {"channel": "V_Bin", "offset": 0, "scale": 500}, + ], + "W": [ + {"channel": "W_Volt", "offset": 0, "scale": 100}, + {"channel": "W_Bin", "offset": 0, "scale": 500}, + ], + }, + }, + }, { "network": "NT", "station": "BRT", @@ -83,6 +106,60 @@ _INSTRUMENT_METADATA = [ }, }, }, + { + "network": "NT", + "station": "CMO", + "start_time": None, + "end_time": None, + "instrument": { + "type": "Narod", + "channels": { + "U": [ + {"channel": "U_Volt", "offset": 0, "scale": 99.4}, + {"channel": "U_Bin", "offset": 0, "scale": 502.5}, + ], + "V": [ + {"channel": "V_Volt", "offset": 0, "scale": 101.5}, + {"channel": "V_Bin", "offset": 0, "scale": 512.5}, + ], + "W": [ + {"channel": "W_Volt", "offset": 0, "scale": 100.98}, + {"channel": "W_Bin", "offset": 0, "scale": 509.15}, + ], + }, + }, + }, + { + "network": "NT", + "station": "CMT", + "start_time": None, + "end_time": None, + "instrument": { + "type": "FGE", + "channels": { + # each channel maps to a list of components to calculate nT + # TODO: calculate these lists based on "FGE" type + "U": [{"channel": "U_Volt", "offset": 0, "scale": 967.7}], + "V": [{"channel": "V_Volt", "offset": 0, "scale": 969.7}], + "W": [{"channel": "W_Volt", "offset": 0, "scale": 973.4}], + }, + "electronics": { + "serial": "E0568", + # these scale values are used to convert voltage + "x-scale": 967.7, # V/nT + "y-scale": 969.7, # V/nT + "z-scale": 973.4, # V/nT + "temperature-scale": 0.01, # V/K + }, + "sensor": { + "serial": "S0443", + # these constants combine with instrument setting for offset + "x-constant": 37062, # nT/mA + "y-constant": 37141, # nT/mA + "z-constant": 37281, # nT/mA + }, + }, + }, { "network": "NT", "station": "FDT", @@ -152,6 +229,29 @@ _INSTRUMENT_METADATA = [ }, }, }, + { + "network": "NT", + "station": "SHU", + "start_time": None, + "end_time": None, + "instrument": { + "type": "Narod", + "channels": { + "U": [ + {"channel": "U_Volt", "offset": 0, "scale": 100}, + {"channel": "U_Bin", "offset": 0, "scale": 505}, + ], + "V": [ + {"channel": "V_Volt", "offset": 0, "scale": 100}, + {"channel": "V_Bin", "offset": 0, "scale": 505}, + ], + "W": [ + {"channel": "W_Volt", "offset": 0, "scale": 100}, + {"channel": "W_Bin", "offset": 0, "scale": 505}, + ], + }, + }, + }, { "network": "NT", "station": "SJT", diff --git a/geomagio/ObservatoryMetadata.py b/geomagio/ObservatoryMetadata.py index deac24f23aa2e6c0cd3ca6fa8b97525d1ebfac3e..593310d86bc5921bd98f63ee7155ee5bbafba78c 100644 --- a/geomagio/ObservatoryMetadata.py +++ b/geomagio/ObservatoryMetadata.py @@ -1,5 +1,32 @@ """Factory that loads metadata for an observatory""" +# default metadata for available time intervals +DEFAULT_INTERVAL_SPECIFIC = { + "day": { + "data_interval_type": "1-day (00:00-23:59)", + "filter_comments": [ + "Scalar and Vector 1-day values are computed from average of 1-minute values in the day (00:00-23:59)", + ], + }, + "hour": { + "data_interval_type": "1-hour (00-59)", + "filter_comments": [ + "Scalar and Vector 1-hour values are computed from average of 1-minute values in the hour (00-59)", + ], + }, + "minute": { + "data_interval_type": "1-minute", + "filter_comments": [ + "Scalar and Vector 1-minute values are computed from 1 Hz values using an INTERMAGNET gaussian filter centered on the start of the minute (00:30-01:30)." + ], + }, + "second": { + "data_interval_type": "1-second", + "filter_comments": [ + "Vector 1-second values are computed from 10 Hz values using a Blackman filter (123 taps, cutoff 0.25Hz) centered on the start of the second." + ], + }, +} # default metadata for the 14 USGS observatories. DEFAULT_METADATA = { @@ -19,19 +46,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "BOU": { "metadata": { @@ -49,19 +64,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "TST": { "metadata": { @@ -79,19 +82,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "BRW": { "metadata": { @@ -109,19 +100,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "data_interval_type": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "BRT": { "metadata": { @@ -139,19 +118,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "data_interval_type": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "BSL": { "metadata": { @@ -169,19 +136,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "CMO": { "metadata": { @@ -198,7 +153,8 @@ DEFAULT_METADATA = { "conditions_of_use": "The Conditions of Use for data provided" + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", - } + }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "CMT": { "metadata": { @@ -215,7 +171,8 @@ DEFAULT_METADATA = { "conditions_of_use": "The Conditions of Use for data provided" + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", - } + }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "DED": { "metadata": { @@ -233,19 +190,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "DHT": { "metadata": { @@ -263,19 +208,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "FRD": { "metadata": { @@ -293,19 +226,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "FDT": { "metadata": { @@ -323,19 +244,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "FRN": { "metadata": { @@ -353,19 +262,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "GUA": { "metadata": { @@ -383,19 +280,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "HON": { "metadata": { @@ -413,19 +298,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "KAK": { "metadata": { @@ -443,10 +316,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "NEW": { "metadata": { @@ -464,19 +334,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "SHU": { "metadata": { @@ -494,19 +352,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "SIT": { "metadata": { @@ -524,19 +370,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "SJG": { "metadata": { @@ -554,19 +388,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "TUC": { "metadata": { @@ -584,19 +406,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "USGS": { "metadata": { @@ -614,13 +424,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "1-minute calculated", - "filter_comments": [], - }, - "hourly": {"data_interval_type": "1-hour calculated"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "BLC": { "metadata": { @@ -638,10 +442,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "BRD": { "metadata": { @@ -659,10 +460,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "CBB": { "metadata": { @@ -680,10 +478,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "EUA": { "metadata": { @@ -701,10 +496,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "FCC": { "metadata": { @@ -722,10 +514,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "HAD": { "metadata": { @@ -743,10 +532,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "HER": { "metadata": { @@ -764,10 +550,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "IQA": { "metadata": { @@ -785,10 +568,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "MEA": { "metadata": { @@ -806,10 +586,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "OTT": { "metadata": { @@ -827,10 +604,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "RES": { "metadata": { @@ -848,10 +622,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "SNK": { "metadata": { @@ -869,10 +640,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "STJ": { "metadata": { @@ -890,10 +658,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "VIC": { "metadata": { @@ -911,10 +676,7 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, "YKC": { "metadata": { @@ -932,20 +694,11 @@ DEFAULT_METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45)"}, - "second": {"data_interval_type": ""}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, }, } -DEFAULT_INTERVAL_SPECIFIC = { - "minute": {"data_interval_type": "filtered 1-minute (00:15-01:45) "}, - "second": {"data_interval_type": "Average 1-Second"}, -} - - class ObservatoryMetadata(object): """Helper class for providing all the metadata needed for a geomag timeseries. diff --git a/geomagio/PlotTimeseriesFactory.py b/geomagio/PlotTimeseriesFactory.py index a892b5e03f9cff0ba9f81d99741db9b9f43d710e..7cfa3677b9ce7740031ebdf63da687e2e698b34b 100644 --- a/geomagio/PlotTimeseriesFactory.py +++ b/geomagio/PlotTimeseriesFactory.py @@ -50,7 +50,7 @@ class PlotTimeseriesFactory(TimeseriesFactory): type : {'definitive', 'provisional', 'quasi-definitive', 'variation'} data type, optional. uses default if unspecified. - interval : {'daily', 'hourly', 'minute', 'monthly', 'second'} + interval : {'day', 'hour', 'minute', 'month', 'second'} data interval, optional. uses default if unspecified. Raises diff --git a/geomagio/TimeseriesFactory.py b/geomagio/TimeseriesFactory.py index 99af57bac74bed1d66cc3e02efe6e016693298d3..3d92f9c58f3b0ed26f24c4c97801747bf98b8d90 100644 --- a/geomagio/TimeseriesFactory.py +++ b/geomagio/TimeseriesFactory.py @@ -31,7 +31,7 @@ class TimeseriesFactory(object): type : {'definitive', 'provisional', 'quasi-definitive', 'variation'} default data type, optional. default 'variation'. - interval : {'daily', 'hourly', 'minute', 'monthly', 'second'} + interval : {'day', 'hour', 'minute', 'month, 'second'} data interval, optional. default 'minute'. urlTemplate : str @@ -67,6 +67,7 @@ class TimeseriesFactory(object): channels=None, type=None, interval=None, + add_empty_channels: bool = True, ): """Get timeseries data. @@ -90,9 +91,11 @@ class TimeseriesFactory(object): type : {'definitive', 'provisional', 'quasi-definitive', 'variation'} data type, optional. uses default if unspecified. - interval : {'daily', 'hourly', 'minute', 'monthly', 'second'} + interval : {'day', 'hour', 'minute', 'month', 'second'} data interval, optional. uses default if unspecified. + add_empty_channels + if True, returns channels without data as empty traces Returns ------- @@ -196,7 +199,7 @@ class TimeseriesFactory(object): type : {'definitive', 'provisional', 'quasi-definitive', 'variation'} data type, optional. uses default if unspecified. - interval : {'daily', 'hourly', 'minute', 'monthly', 'second'} + interval : {'day', 'hour', 'minute', 'month', 'second'} data interval, optional. uses default if unspecified. Raises @@ -294,6 +297,31 @@ class TimeseriesFactory(object): """ raise NotImplementedError('"write_file" not implemented') + def _get_empty_trace( + self, + starttime: obspy.core.UTCDateTime, + endtime: obspy.core.UTCDateTime, + observatory: str, + channel: str, + data_type: str, + interval: str, + network: str = "NT", + location: str = "", + ) -> obspy.core.Trace: + """creates empty trace""" + trace = TimeseriesUtility.create_empty_trace( + starttime=starttime, + endtime=endtime, + observatory=observatory, + channel=channel, + type=data_type, + interval=interval, + station=observatory, + network=network, + location=location, + ) + return trace + def _get_file_from_url(self, url): """Get a file for writing. @@ -339,7 +367,7 @@ class TimeseriesFactory(object): type : {'variation', 'reported', 'provisional', 'adjusted', 'quasi-definitive', 'definitive'} data type. - interval : {'minute', 'second', 'hourly', 'daily'} + interval : {'minute', 'second', 'hour', 'day'} data interval. channels : list list of data channels being requested @@ -384,7 +412,7 @@ class TimeseriesFactory(object): Parameters ---------- - interval : {'daily', 'hourly', 'minute', 'monthly', 'second'} + interval : {'day', 'hour', 'minute', 'month', 'second'} Returns ------- @@ -395,14 +423,15 @@ class TimeseriesFactory(object): TimeseriesFactoryException if ``interval`` is not supported. """ + interval_abbr = None - if interval == "daily": + if interval == "day": interval_abbr = "day" - elif interval == "hourly": + elif interval == "hour": interval_abbr = "hor" elif interval == "minute": interval_abbr = "min" - elif interval == "monthly": + elif interval == "month": interval_abbr = "mon" elif interval == "second": interval_abbr = "sec" @@ -433,7 +462,7 @@ class TimeseriesFactory(object): interval_name = "OneMinute" elif interval == "second": interval_name = "OneSecond" - elif interval == "hourly": + elif interval == "hour": interval_name = "OneHour" else: raise TimeseriesFactoryException('Unsupported interval "%s"' % interval) @@ -501,3 +530,25 @@ class TimeseriesFactory(object): else: raise TimeseriesFactoryException('Unsupported type "%s"' % type) return type_name + + def _set_metadata( + self, + stream: obspy.core.Stream, + observatory: str, + channel: str, + type: str, + interval: str, + ): + """set metadata for a given stream/channel + Parameters + ---------- + observatory + observatory code + channel + edge channel code {MVH, MVE, MVD, ...} + type + data type {definitive, quasi-definitive, variation} + interval + interval length {minute, second} + """ + pass diff --git a/geomagio/TimeseriesUtility.py b/geomagio/TimeseriesUtility.py index c40ba8609576b60bf0bcc68d6af85abec6192a56..e0729c21b81796707a06cff1184765aeab5be29c 100644 --- a/geomagio/TimeseriesUtility.py +++ b/geomagio/TimeseriesUtility.py @@ -2,8 +2,11 @@ from builtins import range from datetime import datetime import math +import sys import numpy -import obspy.core +from obspy.core import Stats, Stream, Trace, UTCDateTime + +from .Util import get_intervals def create_empty_trace( @@ -13,9 +16,9 @@ def create_empty_trace( Parameters ---------- - starttime: obspy.core.UTCDateTime + starttime: UTCDateTime the starttime of the requested data - endtime: obspy.core.UTCDateTime + endtime: UTCDateTime the endtime of the requested data observatory : str observatory code @@ -33,26 +36,54 @@ def create_empty_trace( the location code Returns ------- - obspy.core.Trace + Trace: trace for the requested channel """ delta = get_delta_from_interval(interval) - stats = obspy.core.Stats() + stats = Stats() stats.network = network stats.station = station stats.location = location stats.channel = channel # Calculate first valid sample time based on interval - trace_starttime = obspy.core.UTCDateTime( - numpy.ceil(starttime.timestamp / delta) * delta - ) + trace_starttime = UTCDateTime(numpy.ceil(starttime.timestamp / delta) * delta) + if delta > 60.0: + trace_starttime += (delta - 60) / 2 + if trace_starttime > endtime: + sys.stderr.write( + "Starttime greater than endtime, adjusting request to one sample" + ) + endtime = trace_starttime stats.starttime = trace_starttime stats.delta = delta # Calculate number of valid samples up to or before endtime length = int((endtime - trace_starttime) / delta) stats.npts = length + 1 data = numpy.full(stats.npts, numpy.nan, dtype=numpy.float64) - return obspy.core.Trace(data, stats) + return Trace(data, stats) + + +def encode_stream(stream: Stream, encoding: str) -> Stream: + """Ensures that factory encoding matches output data encoding + + Parameters: + ----------- + stream: Stream + stream of input data + + Returns: + -------- + out_stream: Stream + stream with matching data encoding to factory specification + + """ + out_stream = Stream() + for trace in stream: + trace_out = trace.copy() + if trace_out.data.dtype != encoding: + trace_out.data = trace_out.data.astype(encoding) + out_stream += trace_out + return out_stream def get_delta_from_interval(data_interval): @@ -117,20 +148,20 @@ def get_stream_start_end_times(timeseries, without_gaps=False): the latest endtime. Parameters ---------- - timeseries: obspy.core.stream + timeseries: Stream The timeseries stream Returns ------- tuple: (starttime, endtime) - starttime: obspy.core.UTCDateTime - endtime: obspy.core.UTCDateTime + starttime: UTCDateTime + endtime: UTCDateTime NOTE: when the entire timeseries is a gap, and without_gaps is True, the returned endtime will be one delta earlier than starttime. """ - starttime = obspy.core.UTCDateTime(datetime.now()) - endtime = obspy.core.UTCDateTime(0) + starttime = UTCDateTime(datetime.now()) + endtime = UTCDateTime(0) for trace in timeseries: if trace.stats.starttime < starttime: starttime = trace.stats.starttime @@ -152,7 +183,7 @@ def get_stream_gaps(stream, channels=None): """Get gaps in a given stream Parameters ---------- - stream: obspy.core.Stream + stream: Stream the stream to check for gaps channels: array_like list of channels to check for gaps @@ -180,7 +211,7 @@ def get_trace_gaps(trace): """Gets gaps in a trace representing a single channel Parameters ---------- - trace: obspy.core.Trace + trace: Trace a stream containing a single channel of data. Returns @@ -262,7 +293,7 @@ def get_channels(stream): Parameters ---------- - stream : obspy.core.Stream + stream : Stream Returns ------- @@ -281,8 +312,8 @@ def get_trace_value(traces, time, default=None): Parameters ---------- - trace : obspy.core.Trace - time : obspy.core.UTCDateTime + trace : Trace + time : UTCDateTime Returns ------- @@ -308,7 +339,7 @@ def has_all_channels(stream, channels, starttime, endtime): Parameters ---------- - stream: obspy.core.Stream + stream: Stream The input stream we want to make certain has data channels: array_like The list of channels that we want to have concurrent data @@ -338,7 +369,7 @@ def has_any_channels(stream, channels, starttime, endtime): Parameters ---------- - stream: obspy.core.Stream + stream: Stream The input stream we want to make certain has data channels: array_like The list of channels that we want to have concurrent data @@ -373,17 +404,17 @@ def mask_stream(stream): Parameters ---------- - stream : obspy.core.Stream + stream : Stream stream to mask Returns ------- - obspy.core.Stream + Stream stream with new Trace objects with numpy masked array data. """ - masked = obspy.core.Stream() + masked = Stream() for trace in stream: - masked += obspy.core.Trace(numpy.ma.masked_invalid(trace.data), trace.stats) + masked += Trace(numpy.ma.masked_invalid(trace.data), trace.stats) return masked @@ -392,18 +423,18 @@ def unmask_stream(stream): Parameters ---------- - stream : obspy.core.Stream + stream : Stream stream to unmask Returns ------- - obspy.core.Stream + Stream stream with new Trace objects with numpy array data, with numpy.nan as a fill value in a filled array. """ - unmasked = obspy.core.Stream() + unmasked = Stream() for trace in stream: - unmasked += obspy.core.Trace( + unmasked += Trace( trace.data.filled(fill_value=numpy.nan) if isinstance(trace.data, numpy.ma.MaskedArray) else trace.data, @@ -417,15 +448,15 @@ def merge_streams(*streams): Parameters ---------- - *streams : obspy.core.Stream + *streams : Stream one or more streams to merge Returns ------- - obspy.core.Stream + Stream stream with contiguous traces merged, and gaps filled with numpy.nan """ - merged = obspy.core.Stream() + merged = Stream() # sort out empty for stream in streams: @@ -437,7 +468,7 @@ def merge_streams(*streams): split = split.split() # Re-add any empty traces that were removed by split() - readd = obspy.core.Stream() + readd = Stream() for trace in merged: stats = trace.stats split_stream = split.select( @@ -472,11 +503,11 @@ def pad_timeseries(timeseries, starttime, endtime): Parameters ---------- - timeseries: obspy.core.stream + timeseries: Stream The timeseries stream as returned by the call to getWaveform - starttime: obspy.core.UTCDateTime + starttime: UTCDateTime the starttime of the requested data - endtime: obspy.core.UTCDateTime + endtime: UTCDateTime the endtime of the requested data Notes: the original timeseries object is changed. @@ -493,17 +524,17 @@ def pad_and_trim_trace(trace, starttime, endtime): Parameters ---------- - trace: obspy.core.Trace + trace: Trace One trace to be processed - starttime: obspy.core.UTCDateTime + starttime: UTCDateTime the starttime of the requested data - endtime: obspy.core.UTCDateTime + endtime: UTCDateTime the endtime of the requested data Notes: the original timeseries object is changed. """ - trace_starttime = obspy.core.UTCDateTime(trace.stats.starttime) - trace_endtime = obspy.core.UTCDateTime(trace.stats.endtime) + trace_starttime = UTCDateTime(trace.stats.starttime) + trace_endtime = UTCDateTime(trace.stats.endtime) trace_delta = trace.stats.delta if trace_starttime < starttime: # trim to starttime @@ -535,3 +566,53 @@ def pad_and_trim_trace(trace, starttime, endtime): trace.data = numpy.concatenate( [trace.data, numpy.full(cnt, numpy.nan, dtype=numpy.float64)] ) + + +def round_usecs(time): + """Rounds residual microseconds to milliseconds. + + Parameters + ---------- + time: UTCDateTime + time containing microsecond values + + Returns + ---------- + time: UTCDateTime + time containing rounded(or non-rounded) microsecond values + """ + usecs = time.microsecond + # round microseconds to nearest millisecond + rounded_usecs = int(round(usecs / 1000, 0)) * 1000 + # reset microseconds to 0 at top of second, add second to input time + if rounded_usecs > 999000: + rounded_usecs = 0 + time += 1 + if rounded_usecs != usecs: + time = time.replace(microsecond=rounded_usecs) + return time + + +def split_stream(stream: Stream, size: int = 86400) -> Stream: + out_stream = Stream() + for trace in stream: + out_stream += split_trace(trace, size) + return out_stream + + +def split_trace(trace: Trace, size: int = 86400) -> Stream: + # copy in case original trace changes later + stream = Stream() + out_trace = trace.copy() + for interval in get_intervals( + starttime=out_trace.stats.starttime, + endtime=out_trace.stats.endtime, + size=size, + trim=True, + ): + stream += out_trace.slice( + starttime=interval["start"], + endtime=interval["end"] - out_trace.stats.delta, + nearest_sample=False, + ) + return stream diff --git a/geomagio/WebService.py b/geomagio/WebService.py deleted file mode 100644 index 324029279f7a1300ca71be3f0b596e44228c0043..0000000000000000000000000000000000000000 --- a/geomagio/WebService.py +++ /dev/null @@ -1,417 +0,0 @@ -"""WSGI implementation of Intermagnet Web Service -""" - -from __future__ import print_function -from html import escape -from urllib.parse import parse_qs - -from collections import OrderedDict -from datetime import datetime -from json import dumps -import sys - -from geomagio.edge import EdgeFactory -from geomagio.iaga2002 import IAGA2002Writer -from geomagio.imfjson import IMFJSONWriter -from geomagio.ObservatoryMetadata import ObservatoryMetadata -from geomagio.WebServiceUsage import WebServiceUsage -from obspy.core import UTCDateTime - - -DEFAULT_DATA_TYPE = "variation" -DEFAULT_ELEMENTS = ("X", "Y", "Z", "F") -DEFAULT_OUTPUT_FORMAT = "iaga2002" -DEFAULT_SAMPLING_PERIOD = "60" -ERROR_CODE_MESSAGES = { - 204: "No Data", - 400: "Bad Request", - 404: "Not Found", - 409: "Conflict", - 500: "Internal Server Error", - 501: "Not Implemented", - 503: "Service Unavailable", -} -VALID_DATA_TYPES = ["variation", "adjusted", "quasi-definitive", "definitive"] -VALID_OUTPUT_FORMATS = ["iaga2002", "json"] -VALID_SAMPLING_PERIODS = ["1", "60"] - - -def _get_param(params, key, required=False): - """Get parameter from dictionary. - - Parameters - ---------- - params : dict - parameters dictionary. - key : str - parameter name. - required : bool - if required parameter. - - Returns - ------- - value : str - value from dictionary. - - Raises - ------ - WebServiceException - if the parameter is specified more than once - or if required paramenter is not specified. - """ - value = params.get(key) - if isinstance(value, (list, tuple)): - if len(value) > 1: - raise WebServiceException('"' + key + '" may only be specified once.') - value = escape(value[0]) - if value is None: - if required: - raise WebServiceException('"' + key + '" is a required parameter.') - return value - - -class WebService(object): - def __init__( - self, - factory=None, - version=None, - metadata=None, - usage_documentation=None, - error_stream=sys.stderr, - ): - self.error_stream = error_stream - self.factory = factory or EdgeFactory() - self.metadata = metadata or ObservatoryMetadata().metadata - self.version = version - self.usage_documentation = usage_documentation or WebServiceUsage() - - def __call__(self, environ, start_response): - """Implement WSGI interface""" - if environ["QUERY_STRING"] == "": - return self.usage_documentation.__call__(environ, start_response) - try: - # parse params - query = self.parse(parse_qs(environ["QUERY_STRING"])) - query._verify_parameters() - self.output_format = query.output_format - except Exception as e: - message = str(e) - ftype = parse_qs(environ["QUERY_STRING"]).get("format", [""])[0] - if ftype == "json": - self.output_format = "json" - else: - self.output_format = "iaga2002" - error_body = self.error(400, message, environ, start_response) - return [error_body] - try: - # fetch timeseries - timeseries = self.fetch(query) - # format timeseries - timeseries_string = self.format_data( - query, timeseries, start_response, environ - ) - if isinstance(timeseries_string, str): - timeseries_string = timeseries_string.encode("utf8") - except Exception as e: - if self.error_stream: - print("Error processing request: %s" % str(e), file=self.error_stream) - message = "Server error." - error_body = self.error(500, message, environ, start_response) - return [error_body] - return [timeseries_string] - - def error(self, code, message, environ, start_response): - """Assign error_body value based on error format.""" - error_body = self.http_error(code, message, environ) - status = str(code) + " " + ERROR_CODE_MESSAGES[code] - start_response(status, [("Content-Type", "text/plain")]) - if isinstance(error_body, str): - error_body = error_body.encode("utf8") - return error_body - - def fetch(self, query): - """Get requested timeseries. - - Parameters - ---------- - query : dict - parsed query parameters - - Returns - ------- - obspy.core.Stream - timeseries object with requested data. - """ - if query.sampling_period == "1": - sampling_period = "second" - if query.sampling_period == "60": - sampling_period = "minute" - timeseries = self.factory.get_timeseries( - observatory=query.observatory_id, - channels=query.elements, - starttime=query.starttime, - endtime=query.endtime, - type=query.data_type, - interval=sampling_period, - ) - return timeseries - - def format_data(self, query, timeseries, start_response, environ): - """Format requested timeseries. - - Parameters - ---------- - query : dictionary of parsed query parameters - timeseries : obspy.core.Stream - timeseries object with data to be written - - Returns - ------- - unicode - IAGA2002 formatted string. - """ - url = environ["HTTP_HOST"] + environ["PATH_INFO"] + environ["QUERY_STRING"] - if query.output_format == "json": - timeseries_string = IMFJSONWriter.format(timeseries, query.elements, url) - else: - timeseries_string = IAGA2002Writer.format(timeseries, query.elements) - start_response("200 OK", [("Content-Type", "text/plain")]) - return timeseries_string - - def http_error(self, code, message, environ): - """Format http error message. - - Returns - ------- - http_error_body : str - body of http error message. - """ - query_string = environ["QUERY_STRING"] - path_info = environ["PATH_INFO"] - host = environ["HTTP_HOST"] - if self.output_format == "json": - http_error_body = self.json_error( - code, message, path_info, query_string, host - ) - else: - http_error_body = self.iaga2002_error( - code, message, path_info, query_string - ) - return http_error_body - - def iaga2002_error(self, code, message, path_info, query_string): - """Format iaga2002 error message. - - Returns - ------- - error_body : str - body of iaga2002 error message. - """ - status_message = ERROR_CODE_MESSAGES[code] - error_body = ( - "Error " - + str(code) - + ": " - + status_message - + "\n\n" - + message - + "\n\n" - + "Usage details are available from " - + "http://geomag.usgs.gov/ws/edge/ \n\n" - + "Request:\n" - + path_info - + "?" - + query_string - + "\n\n" - + "Request Submitted:\n" - + datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") - + "\n\n" - ) - # Check if there is version information available - if self.version is not None: - error_body += "Service version:\n" + str(self.version) - return error_body - - def json_error(self, code, message, path_info, query_string, host): - """Format json error message. - - Returns - ------- - error_body : str - body of json error message. - """ - error_dict = OrderedDict() - error_dict["type"] = "Error" - error_dict["metadata"] = OrderedDict() - error_dict["metadata"]["status"] = 400 - date = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") - error_dict["metadata"]["generated"] = date - error_dict["metadata"]["url"] = host + path_info + "?" + query_string - status_message = ERROR_CODE_MESSAGES[code] - error_dict["metadata"]["title"] = status_message - error_dict["metadata"]["api"] = str(self.version) - error_dict["metadata"]["error"] = message - error_body = dumps(error_dict, ensure_ascii=True).encode("utf8") - error_body = str(error_body) - return error_body - - def parse(self, params): - """Parse query string parameters and set defaults. - - Parameters - ---------- - params : dictionary - parameters dictionary. - - Returns - ------- - WebServiceQuery - parsed query object. - - Raises - ------ - WebServiceException - if any parameters are not supported. - """ - # Get values - observatory_id = _get_param(params, "id", required=True) - starttime = _get_param(params, "starttime") - endtime = _get_param(params, "endtime") - elements = _get_param(params, "elements") - sampling_period = _get_param(params, "sampling_period") - data_type = _get_param(params, "type") - output_format = _get_param(params, "format") - # Assign values or defaults - if not output_format: - output_format = DEFAULT_OUTPUT_FORMAT - else: - output_format = output_format.lower() - observatory_id = observatory_id.upper() - if observatory_id not in list(self.metadata.keys()): - raise WebServiceException( - 'Bad id value "%s".' - " Valid values are: %s" % (observatory_id, list(self.metadata.keys())) - ) - if not starttime: - now = datetime.now() - today = UTCDateTime(year=now.year, month=now.month, day=now.day, hour=0) - starttime = today - else: - try: - starttime = UTCDateTime(starttime) - except Exception: - raise WebServiceException( - 'Bad starttime value "%s".' - " Valid values are ISO-8601 timestamps." % starttime - ) - if not endtime: - endtime = starttime + (24 * 60 * 60 - 1) - else: - try: - endtime = UTCDateTime(endtime) - except Exception: - raise WebServiceException( - 'Bad endtime value "%s".' - " Valid values are ISO-8601 timestamps." % endtime - ) - if not elements: - elements = DEFAULT_ELEMENTS - else: - elements = [e.strip().upper() for e in elements.replace(",", "")] - if not sampling_period: - sampling_period = DEFAULT_SAMPLING_PERIOD - else: - sampling_period = sampling_period - if not data_type: - data_type = DEFAULT_DATA_TYPE - else: - data_type = data_type.lower() - # Create WebServiceQuery object and set properties - query = WebServiceQuery() - query.observatory_id = observatory_id - query.starttime = starttime - query.endtime = endtime - query.elements = elements - query.sampling_period = sampling_period - query.data_type = data_type - query.output_format = output_format - return query - - -class WebServiceQuery(object): - """Query parameters for a web service request. - - Parameters - ---------- - observatory_id : str - observatory - starttime : obspy.core.UTCDateTime - time of first requested sample - endtime : obspy.core.UTCDateTime - time of last requested sample - elements : array_like - list of requested elements - sampling_period : int - period between samples in seconds - default 60. - data_type : {'variation', 'adjusted', 'quasi-definitive', 'definitive'} - data type - default 'variation'. - output_format : {'iaga2002', 'json'} - output format. - default 'iaga2002'. - """ - - def __init__( - self, - observatory_id=None, - starttime=None, - endtime=None, - elements=None, - sampling_period=60, - data_type="variation", - output_format="iaga2002", - ): - self.observatory_id = observatory_id - self.starttime = starttime - self.endtime = endtime - self.elements = elements - self.sampling_period = sampling_period - self.data_type = data_type - self.output_format = output_format - - def _verify_parameters(self): - """Verify that parameters are valid. - - Raises - ------ - WebServiceException - if any parameters are not supported. - """ - if len(self.elements) > 4 and self.output_format == "iaga2002": - raise WebServiceException( - "No more than four elements allowed for iaga2002 format." - ) - if self.starttime > self.endtime: - raise WebServiceException("Starttime must be before endtime.") - if self.data_type not in VALID_DATA_TYPES: - raise WebServiceException( - 'Bad type value "%s".' - " Valid values are: %s" % (self.data_type, VALID_DATA_TYPES) - ) - if self.sampling_period not in VALID_SAMPLING_PERIODS: - raise WebServiceException( - 'Bad sampling_period value "%s".' - " Valid values are: %s" % (self.sampling_period, VALID_SAMPLING_PERIODS) - ) - if self.output_format not in VALID_OUTPUT_FORMATS: - raise WebServiceException( - 'Bad format value "%s".' - " Valid values are: %s" % (self.output_format, VALID_OUTPUT_FORMATS) - ) - - -class WebServiceException(Exception): - """Base class for exceptions thrown by web services.""" - - pass diff --git a/geomagio/WebServiceUsage.py b/geomagio/WebServiceUsage.py deleted file mode 100644 index 1530c9248f3191b04f398f916d6c75fc874036fb..0000000000000000000000000000000000000000 --- a/geomagio/WebServiceUsage.py +++ /dev/null @@ -1,191 +0,0 @@ -"""Factory that loads html for Web Service Usage Documentation""" -from datetime import datetime -from geomagio.ObservatoryMetadata import ObservatoryMetadata - - -class WebServiceUsage(object): - def __init__(self, metadata=None, mount_path=None, host_prefix=None): - metadata = metadata or list(ObservatoryMetadata().metadata.keys()) - self.date = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") - self.metadata = ", ".join(sorted(metadata)) - self.mount_path = mount_path - self.host_prefix = host_prefix - - def __call__(self, environ, start_response): - """Implement documentation page""" - start_response("200 OK", [("Content-Type", "text/html")]) - if self.mount_path is None: - self.mount_path = "/ws/edge" - if self.host_prefix is None: - self.host_prefix = environ["HTTP_HOST"] - usage_page = self.set_usage_page() - return [usage_page] - - def set_usage_page(self): - """Set body of Web Service Usage Documentation Page""" - stylesheet = "https://geomag.usgs.gov/theme/site/geomag/index.css" - ids = "" - observatories = self.metadata.split(", ") - for idx, obs_id in enumerate(observatories): - ids += "<code>" + obs_id + "</code>" - if idx != len(observatories) - 1: - ids += ", " - if idx % 9 == 0 and idx != 0: - ids += "<br/>" - usage_body = """ - <!doctype html> - <html> - <head> - <title>Geomag Web Service Usage</title> - <base href={host_prefix}> - <meta charset="utf-8"/> - <meta name="viewport" content="width=device-width, - initial-scale=1"/> - <link rel="stylesheet" href={stylesheet} type="text/css"> - <style> - code, - pre {{ - background: #f8f8f8; - border-radius: 3px; - color: #555; - font-family: monospace; - }} - </style> - </head> - - <body style="font-size:135%"> - <main role="main" class="page" aria-labelledby="page-header"> - <header class="page-header" id="page-header"> - <h1>Geomag Web Service Usage</h1> - </header> - - - <h2>Example Requests</h3> - <dl> - <dt>BOU observatory data for current UTC day in IAGA2002 - format</dt> - <dd> - <a href="{link1}"> - {link1}</a> - </dd> - <dt>BOU observatory data for current UTC day in JSON - format</dt> - <dd> - <a href="{link2}"> - {link2}</a> - </dd> - <dt>BOU electric field data for current UTC day in - IAGA2002 format</dt> - <dd> - <a href="{link3}"> - {link3}</a> - </dd> - - <h2>Parameters</h2> - <dl> - <dt>id</dt> - <dd> - Observatory code. - Required.<br/> - Valid values:<br/> - {metadata} - </dd> - - <dt>starttime</dt> - <dd> - Time of first requested data.<br/> - Default: start of current UTC day<br/> - Format: ISO8601 - (<code>YYYY-MM-DDTHH:MM:SSZ</code>)<br/> - Example: <code>{date}</code> - </dd> - - <dt>endtime</dt> - <dd> - Time of last requested data.<br/> - Default: starttime + 24 hours<br/> - Format: ISO8601 - (<code>YYYY-MM-DDTHH:MM:SSZ</code>)<br/> - Example: <code>{date}</code> - </dd> - - <dt>elements</dt> - <dd> - Comma separated list of requested elements.<br/> - Default: <code>X</code>,<code>Y</code>,<code>Z</code>, - <code>F</code><br/> - Valid values: <code>D</code>, <code>DIST</code>, - <code>DST</code>, <code>E</code>, - <code>E-E</code>, <code>E-N</code>, - <code>F</code>, <code>G</code>, - <code>H</code>, <code>SQ</code>, - <code>SV</code>, <code>UK1</code>, - <code>UK2</code>, <code>UK3</code>, - <code>UK4</code>, <code>X</code>, - <code>Y</code>, <code>Z</code> - <br/> - </dd> - <dt>sampling_period</dt> - <dd> - Interval in seconds between values.<br/> - Default: <code>60</code><br/> - Valid values: - <code>1</code>, - <code>60</code> - </dd> - - <dt>type</dt> - <dd> - Type of data.<br/> - Default: <code>variation</code><br/> - Valid values: - <code>variation</code>, - <code>adjusted</code>, - <code>quasi-definitive</code>, - <code>definitive</code><br/> - <small> - NOTE: the USGS web service also supports specific - EDGE location codes. - For example: - <code>R0</code> is "internet variation", - <code>R1</code> is "satellite variation". - </small> - </dd> - - <dt>format</dt> - <dd> - Output format.<br/> - Default: <code>iaga2002</code><br/> - Valid values: - <code>iaga2002</code>. - </dd> - </dl> - </main> - - - <nav class="site-footer"> - <p> Not what you were looking for?<br/> - Search usa.gov: </p> - <form class="site-search" role="search" - action="//search.usa.gov/search" method="get" - accept-charset="UTF-8"> - <input name="utf8" type="hidden" value="x"/> - <input name="affiliate" type="hidden" value="usgs"/> - <input name="sitelimit" type="hidden" /> - <input id="query" name="query" type="search" - placeholder="Search usa.gov..." title="Search"/> - <button type="submit">Search</button> - </form> - </nav> - </body> - </html> - """.format( - metadata=ids, - date=self.date, - host_prefix=self.host_prefix, - stylesheet=stylesheet, - link1=self.host_prefix + self.mount_path + "/?id=BOU", - link2=self.host_prefix + self.mount_path + "/?id=BOU&format=json", - link3=self.host_prefix + self.mount_path + "/?id=BOU&elements=E-N,E-E", - ) - return usage_body diff --git a/geomagio/__init__.py b/geomagio/__init__.py index 5c391414edd5b3cb148be27ed8c6ea6cc56bc6e8..c9698e9e15f0ad6cd06c63083a013a1f8c961297 100644 --- a/geomagio/__init__.py +++ b/geomagio/__init__.py @@ -9,16 +9,17 @@ from . import TimeseriesUtility from . import Util from .Controller import Controller +from .DerivedTimeseriesFactory import DerivedTimeseriesFactory from .ObservatoryMetadata import ObservatoryMetadata from .PlotTimeseriesFactory import PlotTimeseriesFactory from .TimeseriesFactory import TimeseriesFactory from .TimeseriesFactoryException import TimeseriesFactoryException -from .WebService import WebService __all__ = [ "ChannelConverter", "Controller", "DeltaFAlgorithm", + "DerivedTimeseriesFactory", "ObservatoryMetadata", "PlotTimeseriesFactory", "StreamConverter", @@ -26,5 +27,4 @@ __all__ = [ "TimeseriesFactoryException", "TimeseriesUtility", "Util", - "WebService", ] diff --git a/geomagio/adjusted/AdjustedMatrix.py b/geomagio/adjusted/AdjustedMatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..ea3f4cc929b723ebd620b7815192da0dd330d026 --- /dev/null +++ b/geomagio/adjusted/AdjustedMatrix.py @@ -0,0 +1,81 @@ +import numpy as np +from obspy import Stream, UTCDateTime +from pydantic import BaseModel +from typing import Any, List, Optional + +from ..residual.Reading import Reading, get_absolutes_xyz, get_ordinates +from .. import ChannelConverter +from .. import pydantic_utcdatetime +from .Metric import Metric, get_metric + + +class AdjustedMatrix(BaseModel): + """Attributes pertaining to adjusted(affine) matrices, applied by the AdjustedAlgorithm + + Attributes + ---------- + matrix: affine matrix generated by Affine's calculate method + pier_correction: pier correction generated by Affine's calculate method + starttime: beginning of interval that matrix is valid for + endtime: end of interval that matrix is valid for + NOTE: valid intervals are only generated when bad data is encountered. + Matrix is non-constrained otherwise + """ + + matrix: Optional[List[List[float]]] = None + pier_correction: float = 0 + metrics: Optional[List[Metric]] = None + starttime: Optional[UTCDateTime] = None + endtime: Optional[UTCDateTime] = None + time: Optional[UTCDateTime] = None + + def process( + self, + stream: Stream, + inchannels=["H", "E", "Z", "F"], + outchannels=["X", "Y", "Z", "F"], + ): + """Apply matrix to raw data. Apply pier correction to F when necessary""" + raws = np.vstack( + [ + stream.select(channel=channel)[0].data + for channel in inchannels + if channel != "F" + ] + + [np.ones_like(stream[0].data)] + ) + adjusted = self.matrix @ raws + if "F" in inchannels and "F" in outchannels: + f = stream.select(channel="F")[0].data + self.pier_correction + adjusted[-1] = f + return adjusted + + def get_metrics(self, readings: List[Reading]) -> List[Metric]: + """Computes mean absolute error and standard deviation between expected and predicted values + Metrics are computed for X, Y, Z, and dF values + + Attributes + ---------- + readings: list of valid readings + matrix: composed matrix + + Outputs + ------- + metrics: list of Metric objects + """ + absolutes = get_absolutes_xyz(readings=readings) + ordinates = get_ordinates(readings=readings) + stacked_ordinates = np.vstack((ordinates, np.ones_like(ordinates[0]))) + predicted = self.matrix @ stacked_ordinates + metrics = [] + elements = ["X", "Y", "Z", "dF"] + expected = list(absolutes) + [ + ChannelConverter.get_computed_f_using_squares(*absolutes) + ] + predicted = list(predicted[0:3]) + [ + ChannelConverter.get_computed_f_using_squares(*predicted[0:3]) + ] + return [ + get_metric(element=elements[i], expected=expected[i], actual=predicted[i]) + for i in range(len(elements)) + ] diff --git a/geomagio/adjusted/Affine.py b/geomagio/adjusted/Affine.py new file mode 100644 index 0000000000000000000000000000000000000000..28e14c2413fc5bebd318bd04cced4d0fe3563bb8 --- /dev/null +++ b/geomagio/adjusted/Affine.py @@ -0,0 +1,156 @@ +from functools import reduce +import numpy as np +from obspy import UTCDateTime +from pydantic import BaseModel, Field +from typing import List, Optional, Tuple + +from ..residual.Reading import ( + Reading, + get_absolutes_xyz, + get_ordinates, +) +from .. import pydantic_utcdatetime +from .AdjustedMatrix import AdjustedMatrix +from .transform import RotationTranslationXY, TranslateOrigins, Transform + + +class Affine(BaseModel): + """Creates AdjustedMatrix objects from readings + + Attributes + ---------- + observatory: 3-letter observatory code + starttime: beginning time for matrix creation + endtime: end time for matrix creation + update_interval: window of time(in seconds) a matrix is representative of + transforms: methods for matrix calculations + """ + + observatory: str = None + starttime: UTCDateTime = Field(default_factory=lambda: UTCDateTime() - (86400 * 7)) + endtime: UTCDateTime = Field(default_factory=lambda: UTCDateTime()) + update_interval: Optional[int] = 86400 * 7 + transforms: List[Transform] = [ + RotationTranslationXY(memory=(86400 * 100), acausal=True), + TranslateOrigins(memory=(86400 * 10), acausal=True), + ] + + def calculate( + self, readings: List[Reading], epochs: Optional[List[UTCDateTime]] = None + ) -> List[AdjustedMatrix]: + """Calculates affine matrices for a range of times + + Attributes + ---------- + readings: readings containing absolutes + epochs: optional time markers for unreliable observations + + Outputs + ------- + Ms: AdjustedMatrix objects created from calculations + """ + # default set to create one matrix between starttime and endtime + update_interval = self.update_interval or (self.endtime - self.starttime) + all_readings = [r for r in readings if r.valid] + Ms = [] + time = self.starttime + # search for "bad" H values + epochs = epochs or [ + r.time for r in all_readings if r.get_absolute("H").absolute == 0 + ] + while time < self.endtime: + # update epochs for current time + epoch_start, epoch_end = get_epochs(epochs=epochs, time=time) + # utilize readings that occur after or before a bad reading + readings = [ + r + for r in all_readings + if (epoch_start is None or r.time > epoch_start) + or (epoch_end is None or r.time < epoch_end) + ] + M = self.calculate_matrix(time, readings) + M.starttime = epoch_start + M.endtime = epoch_end + time += update_interval + + Ms.append(M) + + return Ms + + def calculate_matrix( + self, time: UTCDateTime, readings: List[Reading] + ) -> AdjustedMatrix: + """Calculates affine matrix for a given time + + Attributes + ---------- + time: time within calculation interval + readings: list of valid readings + + Outputs + ------- + AdjustedMatrix object containing result + """ + absolutes = get_absolutes_xyz(readings) + ordinates = get_ordinates(readings) + Ms = [] + weights = [] + inputs = ordinates + + for transform in self.transforms: + weights = transform.get_weights( + readings=readings, + time=time.timestamp, + ) + # raise ValueError if no valid observations + if np.sum(weights) == 0: + raise ValueError(f"No valid observations for: {time}") + + M = transform.calculate( + ordinates=inputs, absolutes=absolutes, weights=weights + ) + + # apply latest M matrix to inputs to get intermediate inputs + inputs = np.vstack([*inputs, np.ones_like(inputs[0])]) + inputs = np.dot(M, inputs)[0:3] + Ms.append(M) + + # compose affine transform matrices using reverse ordered matrices + M_composed = reduce(np.dot, reversed(Ms)) + pier_correction = np.average( + [reading.pier_correction for reading in readings], weights=weights + ) + matrix = AdjustedMatrix( + matrix=M_composed.tolist(), + pier_correction=pier_correction, + ) + matrix.metrics = matrix.get_metrics(readings=readings) + return matrix + + +def get_epochs( + epochs: List[float], + time: UTCDateTime, +) -> Tuple[float, float]: + """Updates valid start/end time for a given interval + + Attributes + ---------- + epochs: bad data times + time: current time epoch is being evaluated at + + Outputs + ------- + epoch_start: start of current valid interval + epoch_end: end of current valid interval + """ + epoch_start = None + epoch_end = None + for e in epochs: + if e > time: + if epoch_end is None or e < epoch_end: + epoch_end = e + if e < time: + if epoch_start is None or e > epoch_start: + epoch_start = e + return epoch_start, epoch_end diff --git a/geomagio/adjusted/Metric.py b/geomagio/adjusted/Metric.py new file mode 100644 index 0000000000000000000000000000000000000000..bb60398f2c5ce4f84083d89881df7b6dafd000dd --- /dev/null +++ b/geomagio/adjusted/Metric.py @@ -0,0 +1,24 @@ +from typing import List + +import numpy as np +from pydantic import BaseModel + + +class Metric(BaseModel): + """Mean absolute error and standard deviation for a given element + + Attributes + ---------- + element: Channel that metrics are representative of + absmean: mean absolute error + stddev: standard deviation + """ + + element: str + absmean: float = None + stddev: float = None + + +def get_metric(element: str, expected: List[float], actual: List[float]) -> Metric: + diff = np.array(expected) - np.array(actual) + return Metric(element=element, absmean=np.average(abs(diff)), stddev=np.std(diff)) diff --git a/geomagio/adjusted/__init__.py b/geomagio/adjusted/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b86d5d92707e5d9bb6fd5b3395d88fa59261c661 --- /dev/null +++ b/geomagio/adjusted/__init__.py @@ -0,0 +1,9 @@ +from .AdjustedMatrix import AdjustedMatrix +from .Affine import Affine +from .Metric import Metric + +__all__ = [ + "AdjustedMatrix", + "Affine", + "Metric", +] diff --git a/geomagio/adjusted/transform/LeastSq.py b/geomagio/adjusted/transform/LeastSq.py new file mode 100644 index 0000000000000000000000000000000000000000..4e330108d99b4944f59a0fa3b25aee7b3afbf4aa --- /dev/null +++ b/geomagio/adjusted/transform/LeastSq.py @@ -0,0 +1,136 @@ +import numpy as np +import scipy.linalg as spl +from typing import List, Optional, Tuple, Union + +from .Transform import Transform + + +class LeastSq(Transform): + """Intance of Transform. Applies least squares to generate matrices""" + + def calculate( + self, + ordinates: Tuple[List[float], List[float], List[float]], + absolutes: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]] = None, + ) -> np.array: + """Calculates matrix with least squares and accompanying methods + Defaults to least squares calculation with no constraints + """ + abs_stacked, ord_stacked = self.get_stacked_values( + absolutes, ordinates, weights + ) + ord_stacked = self.get_weighted_values(ord_stacked, weights) + abs_stacked = self.get_weighted_values(abs_stacked, weights) + # regression matrix M that minimizes L2 norm + matrix, res, rank, sigma = spl.lstsq(ord_stacked.T, abs_stacked.T) + if self.valid(rank): + return self.get_matrix(matrix, absolutes, ordinates, weights) + print("Poorly conditioned or singular matrix, returning NaNs") + return np.nan * np.ones((4, 4)) + + def get_matrix( + self, + matrix: List[List[float]], + absolutes: Optional[Tuple[List[float], List[float], List[float]]] = None, + ordinates: Optional[Tuple[List[float], List[float], List[float]]] = None, + weights: Optional[List[float]] = None, + ) -> np.array: + """Returns matrix formatted for no constraints + NOTE: absolutes, ordinates, and weights are only used by QRFactorization's child function + """ + return np.array( + [ + [matrix[0], matrix[1], matrix[2], matrix[3]], + [matrix[4], matrix[5], matrix[6], matrix[7]], + [matrix[8], matrix[9], matrix[10], matrix[11]], + [0.0, 0.0, 0.0, 1.0], + ] + ) + + def get_stacked_absolutes( + self, absolutes: Tuple[List[float], List[float], List[float]] + ) -> List[float]: + """Formats absolutes for least squares method + + Attributes + ---------- + absolutes: Rotated X, Y, and Z absolutes + + Output + ------ + X, Y and Z absolutes placed end to end and transposed + """ + return np.vstack([absolutes[0], absolutes[1], absolutes[2]]).T.ravel() + + def get_stacked_ordinates( + self, ordinates: Tuple[List[float], List[float], List[float]] + ) -> List[List[float]]: + """Formats ordinates for least squares method""" + # (reduces degrees of freedom by 4: + # - 4 for the last row of zeros and a one) + ord_stacked = np.zeros((12, len(ordinates[0]) * 3)) + ord_stacked[0, 0::3] = ordinates[0] + ord_stacked[1, 0::3] = ordinates[1] + ord_stacked[2, 0::3] = ordinates[2] + ord_stacked[3, 0::3] = 1.0 + ord_stacked[4, 1::3] = ordinates[0] + ord_stacked[5, 1::3] = ordinates[1] + ord_stacked[6, 1::3] = ordinates[2] + ord_stacked[7, 1::3] = 1.0 + ord_stacked[8, 2::3] = ordinates[0] + ord_stacked[9, 2::3] = ordinates[1] + ord_stacked[10, 2::3] = ordinates[2] + ord_stacked[11, 2::3] = 1.0 + + return ord_stacked + + def get_stacked_values( + self, + absolutes: Tuple[List[float], List[float], List[float]], + ordinates: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]] = None, + ) -> Tuple[List[float], List[List[float]]]: + """Gathers stacked stacked absolutes/ordinates + NOTE: weights are only used in QRFactorization's child function + """ + # LHS, or dependent variables + # [A[0,0], A[1,0], A[2,0], A[0,1], A[1,1], A[2,1], ...] + abs_stacked = self.get_stacked_absolutes(absolutes) + # RHS, or independent variables + # [ + # [o[0,0], 0, 0, o[0,1], 0, 0, ...], + # [0, o[1,0], 0, 0, o[1,1], 0, ...], + # [0, 0, o[2,0], 0, 0, o[2,1], ...], + # ... + # ] + ord_stacked = self.get_stacked_ordinates(ordinates) + return abs_stacked, ord_stacked + + def get_weighted_values( + self, + values: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]] = None, + ) -> Union[List[float], List[List[float]]]: + """Application of weights for least squares methods, which calls for square roots + + Attributes + ---------- + values: absolutes or ordinates + + Outputs + ------- + tuple of weights applied to each element of values + + """ + if weights is None: + return values + weights = np.sqrt(weights) + weights = np.vstack((weights, weights, weights)).T.ravel() + return values * weights + + def valid(self, rank: float) -> bool: + """validates whether or not a matrix can reliably transform the method's number of dimensions""" + if rank < self.ndims: + return False + return True diff --git a/geomagio/adjusted/transform/QRFactorization.py b/geomagio/adjusted/transform/QRFactorization.py new file mode 100644 index 0000000000000000000000000000000000000000..87c5ee2e00cf4acb91f08b43a114012267596888 --- /dev/null +++ b/geomagio/adjusted/transform/QRFactorization.py @@ -0,0 +1,92 @@ +import numpy as np +from typing import List, Optional, Tuple + +from .LeastSq import LeastSq +from .SVD import SVD + + +class QRFactorization(LeastSq): + """Calculates affine using least squares with QR factorization""" + + ndims: int = 2 + svd: SVD = SVD(ndims=ndims) + + def get_matrix( + self, + matrix: List[List[float]], + absolutes: Tuple[List[float], List[float], List[float]], + ordinates: Tuple[List[float], List[float], List[float]], + weights: List[float], + ) -> np.array: + """performs QR factorization steps and formats result within the returned matrix""" + # QR fatorization + # NOTE: forcing the diagonal elements of Q to be positive + # ensures that the determinant is 1, not -1, and is + # therefore a rotation, not a reflection + Q, R = np.linalg.qr(matrix.T) + neg = np.diag(Q) < 0 + Q[:, neg] = -1 * Q[:, neg] + R[neg, :] = -1 * R[neg, :] + + # isolate scales from shear + S = np.diag(np.diag(R)) + H = np.dot(np.linalg.inv(S), R) + + # combine shear and rotation + QH = np.dot(Q, H) + + weighted_absolutes = self.svd.get_weighted_values(absolutes, weights) + weighted_ordinates = self.svd.get_weighted_values(ordinates, weights) + + # now get translation using weighted centroids and R + T = self.svd.get_translation_matrix(QH, weighted_absolutes, weighted_ordinates) + + return [ + [QH[0, 0], QH[0, 1], 0.0, T[0]], + [QH[1, 0], QH[1, 1], 0.0, T[1]], + [ + 0.0, + 0.0, + 1.0, + np.array(weighted_absolutes[self.ndims]) + - np.array(weighted_ordinates[self.ndims]), + ], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_stacked_values( + self, + absolutes: Tuple[List[float], List[float], List[float]], + ordinates: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]] = None, + ) -> Tuple[List[List[float]], List[List[float]]]: + """stacks and weights absolutes/ordinates""" + weighted_absolutes = self.svd.get_weighted_values( + values=absolutes, weights=weights + ) + weighted_ordinates = self.svd.get_weighted_values( + values=ordinates, weights=weights + ) + # LHS, or dependent variables + abs_stacked = self.svd.get_stacked_values( + values=absolutes, + weighted_values=weighted_absolutes, + ) + + # RHS, or independent variables + ord_stacked = self.svd.get_stacked_values( + values=ordinates, + weighted_values=weighted_ordinates, + ) + return abs_stacked, ord_stacked + + def get_weighted_values( + self, + values: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]] = None, + ) -> List[List[float]]: + """Applies least squares weights in two dimensions(X and Y)""" + if weights is None: + return values + weights = np.sqrt(weights) + return np.array([values[i] * weights for i in range(self.ndims)]) diff --git a/geomagio/adjusted/transform/Rescale3D.py b/geomagio/adjusted/transform/Rescale3D.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1c58b2f9bfeae68e716c6eb5e5c0f4dfa9a819 --- /dev/null +++ b/geomagio/adjusted/transform/Rescale3D.py @@ -0,0 +1,38 @@ +import numpy as np +from typing import List, Optional, Tuple + +from .LeastSq import LeastSq + + +class Rescale3D(LeastSq): + """Calculates affine using using least squares, constrained to re-scale each axis""" + + def get_matrix( + self, + matrix: List[List[float]], + absolutes: Optional[Tuple[List[float], List[float], List[float]]] = None, + ordinates: Optional[Tuple[List[float], List[float], List[float]]] = None, + weights: Optional[List[float]] = None, + ) -> np.array: + return [ + [matrix[0], 0.0, 0.0, 0.0], + [0.0, matrix[1], 0.0, 0.0], + [0.0, 0.0, matrix[2], 0.0], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_stacked_ordinates( + self, ordinates: Tuple[List[float], List[float], List[float]] + ) -> List[List[float]]: + # (reduces degrees of freedom by 13: + # - 2 for making x independent of y,z; + # - 2 for making y,z independent of x; + # - 1 for making y independent of z; + # - 1 for making z independent of y; + # - 3 for not translating xyz + # - 4 for the last row of zeros and a one) + ord_stacked = np.zeros((3, len(ordinates[0]) * 3)) + ord_stacked[0, 0::3] = ordinates[0] + ord_stacked[1, 1::3] = ordinates[1] + ord_stacked[2, 2::3] = ordinates[2] + return ord_stacked diff --git a/geomagio/adjusted/transform/RotationTranslationXY.py b/geomagio/adjusted/transform/RotationTranslationXY.py new file mode 100644 index 0000000000000000000000000000000000000000..e6a352a15e03147083063b2eae623c18a9f7e420 --- /dev/null +++ b/geomagio/adjusted/transform/RotationTranslationXY.py @@ -0,0 +1,37 @@ +import numpy as np +from typing import List, Tuple + +from .SVD import SVD + + +class RotationTranslationXY(SVD): + """Calculates affine using singular value decomposition, + constrained to rotation and translation in XY(no scale or shear), + and only translation in Z""" + + ndims: int = 2 + + def get_matrix( + self, + R: List[List[float]], + T: List[List[float]], + weighted_absolutes: Tuple[List[float], List[float], List[float]], + weighted_ordinates: Tuple[List[float], List[float], List[float]], + ) -> np.array: + return [ + [R[0, 0], R[0, 1], 0.0, T[0]], + [R[1, 0], R[1, 1], 0.0, T[1]], + [ + 0.0, + 0.0, + 1.0, + np.array(weighted_absolutes[self.ndims]) + - np.array(weighted_ordinates[self.ndims]), + ], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_rotation_matrix( + self, U: List[List[float]], Vh: List[List[float]] + ) -> List[List[float]]: + return np.dot(Vh.T, np.dot(np.diag([1, np.linalg.det(np.dot(Vh.T, U.T))]), U.T)) diff --git a/geomagio/adjusted/transform/SVD.py b/geomagio/adjusted/transform/SVD.py new file mode 100644 index 0000000000000000000000000000000000000000..fa570c3533f1ceed0685fdd8a4b8a36bc310b0ff --- /dev/null +++ b/geomagio/adjusted/transform/SVD.py @@ -0,0 +1,136 @@ +import numpy as np +from typing import List, Optional, Tuple + +from .Transform import Transform + + +class SVD(Transform): + """Instance of Transform. Applies singular value decomposition to generate matrices""" + + def calculate( + self, + ordinates: Tuple[List[float], List[float], List[float]], + absolutes: Tuple[List[float], List[float], List[float]], + weights: List[float], + ) -> np.array: + """Calculates matrix with singular value decomposition and accompanying methods + Defaults to singular value decomposition constrained for 3D rotation/translation + """ + weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights) + weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights) + # generate weighted "covariance" matrix + H = self.get_covariance_matrix(absolutes, ordinates, weights) + # Singular value decomposition, then rotation matrix from L&R eigenvectors + # (the determinant guarantees a rotation, and not a reflection) + U, S, Vh = np.linalg.svd(H) + if self.valid(S): + R = self.get_rotation_matrix(U, Vh) + # now get translation using weighted centroids and R + T = self.get_translation_matrix(R, weighted_absolutes, weighted_ordinates) + return self.get_matrix(R, T, weighted_absolutes, weighted_ordinates) + print("Poorly conditioned or singular matrix, returning NaNs") + return np.nan * np.ones((4, 4)) + + def get_covariance_matrix( + self, + absolutes: Tuple[List[float], List[float], List[float]], + ordinates: Tuple[List[float], List[float], List[float]], + weights: List[float], + ) -> List[List[float]]: + """calculate covariance matrix with weighted absolutes/ordinates""" + weighted_ordinates = self.get_weighted_values(values=ordinates, weights=weights) + weighted_absolutes = self.get_weighted_values(values=absolutes, weights=weights) + # generate weighted "covariance" matrix + H = np.dot( + self.get_stacked_values( + values=ordinates, + weighted_values=weighted_ordinates, + ), + np.dot( + np.diag(weights), + self.get_stacked_values( + values=absolutes, + weighted_values=weighted_absolutes, + ).T, + ), + ) + return H + + def get_matrix( + self, + R: List[List[float]], + T: List[List[float]], + weighted_absolutes: Optional[ + Tuple[List[float], List[float], List[float]] + ] = None, + weighted_ordinates: Optional[ + Tuple[List[float], List[float], List[float]] + ] = None, + ) -> np.array: + """Returns matrix formatted for 3D rotation/translation + NOTE: weighted absolutes/ordinates are only used by RotationTranslationXY's child function + """ + return [ + [R[0, 0], R[0, 1], R[0, 2], T[0]], + [R[1, 0], R[1, 1], R[1, 2], T[1]], + [R[2, 0], R[2, 1], R[2, 2], T[2]], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_rotation_matrix( + self, U: List[List[float]], Vh: List[List[float]] + ) -> List[List[float]]: + """computes rotation matrix from products of singular value decomposition""" + return np.dot( + Vh.T, np.dot(np.diag([1, 1, np.linalg.det(np.dot(Vh.T, U.T))]), U.T) + ) + + def get_stacked_values( + self, + values: Tuple[List[float], List[float], List[float]], + weighted_values: Tuple[List[float], List[float], List[float]], + ) -> np.array: + """Supports intermediate mathematical steps by differencing and shaping values for SVD + + Attributes + ---------- + values: absolutes or ordinates + weighted_values: absolutes or ordinates with weights applied + ndims: number of rows desired in return array(case specific). Default set to 3 dimensions(XYZ/HEZ) + + Outputs + ------- + Stacked and differenced values from their weighted counterparts + """ + return np.vstack([[values[i] - weighted_values[i]] for i in range(self.ndims)]) + + def get_translation_matrix( + self, + R: List[List[float]], + weighted_absolutes: Tuple[List[float], List[float], List[float]], + weighted_ordinates: Tuple[List[float], List[float], List[float]], + ) -> List[List[float]]: + """computes translation matrix from rotation matrix and weighted absolutes/ordinates""" + return np.array([weighted_absolutes[i] for i in range(self.ndims)]) - np.dot( + R, [weighted_ordinates[i] for i in range(self.ndims)] + ) + + def get_weighted_values( + self, + values: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]], + ) -> Tuple[float, float, float]: + """Application of weights for SVD methods, which call for weighted averages""" + if weights is None: + weights = np.ones_like(values[0]) + return ( + np.average(values[0], weights=weights), + np.average(values[1], weights=weights), + np.average(values[2], weights=weights), + ) + + def valid(self, singular_values: List[float]) -> bool: + """validates whether or not a matrix can reliably transform the method's number of dimensions""" + if np.sum(singular_values) < self.ndims: + return False + return True diff --git a/geomagio/adjusted/transform/ShearYZ.py b/geomagio/adjusted/transform/ShearYZ.py new file mode 100644 index 0000000000000000000000000000000000000000..7dcc771c59549a77040c898032ea0d6fc52ca27a --- /dev/null +++ b/geomagio/adjusted/transform/ShearYZ.py @@ -0,0 +1,39 @@ +import numpy as np +from typing import List, Optional, Tuple + +from .LeastSq import LeastSq + + +class ShearYZ(LeastSq): + """Calculates affine using least squares, constrained to shear y and z, but not x.""" + + def get_matrix( + self, + matrix: List[List[float]], + absolutes: Optional[Tuple[List[float], List[float], List[float]]] = None, + ordinates: Optional[Tuple[List[float], List[float], List[float]]] = None, + weights: Optional[List[float]] = None, + ) -> np.array: + return [ + [1.0, 0.0, 0.0, 0.0], + [matrix[0], 1.0, 0.0, 0.0], + [matrix[1], matrix[2], 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_stacked_ordinates( + self, ordinates: Tuple[List[float], List[float], List[float]] + ) -> List[List[float]]: + # (reduces degrees of freedom by 13: + # - 2 for making x independent of y,z; + # - 1 for making y independent of z; + # - 3 for not scaling each axis + # - 4 for the last row of zeros and a one) + ord_stacked = np.zeros((3, len(ordinates[0]) * 3)) + ord_stacked[0, 0::3] = 1.0 + ord_stacked[1, 0::3] = ordinates[0] + ord_stacked[1, 1::3] = 1.0 + ord_stacked[2, 0::3] = ordinates[0] + ord_stacked[2, 1::3] = ordinates[1] + ord_stacked[2, 2::3] = 1.0 + return ord_stacked diff --git a/geomagio/adjusted/transform/Transform.py b/geomagio/adjusted/transform/Transform.py new file mode 100644 index 0000000000000000000000000000000000000000..0df271c9766491744fd55a429ee4c5c8ad7ae208 --- /dev/null +++ b/geomagio/adjusted/transform/Transform.py @@ -0,0 +1,174 @@ +import numpy as np +from pydantic import BaseModel +from typing import List, Optional, Tuple + +from ...residual.Reading import Reading, get_baselines, get_times + + +class Transform(BaseModel): + """Method for generating an affine matrix. + + Attributes + ---------- + acausal: if true, future readings are used in calculations + memory: Controls impact of measurements from the past + Defaults to infinite(equal weighting) + """ + + acausal: bool = False + memory: Optional[float] = None + ndims: int = 3 + + def calculate( + self, + ordinates: Tuple[List[float], List[float], List[float]], + absolutes: Tuple[List[float], List[float], List[float]], + weights: List[float], + ) -> np.array: + """Type skeleton inherited by any instance of Transform + + Attributes + ---------- + ordinates: H, E and Z ordinates + absolutes: X, Y and Z absolutes(NOTE: absolutes must be rotated from original H, E and Z values) + weights: time weights to apply during calculations of matrices + """ + # return identity matrix + return np.eye(4) + + def get_weights(self, readings: List[Reading], time: int = None) -> List[float]: + """ + Calculate time-dependent weights according to exponential decay. + + Inputs: + ------- + readings: list of valid readings + time: time weights are calculated for + + Output: + ------- + weights: array of vector distances/metrics + """ + + times = get_times(readings) + # convert to array of floats + times = np.asarray(times).astype(float) + + if time is None: + time = float(max(times)) + + baselines = get_baselines(readings) + + # if memory is actually infinite, return equal weights + if np.isinf(self.memory): + weights = np.ones(times.shape) + + # initialize weights + weights = np.zeros(times.shape) + + # calculate exponential decay time-dependent weights + weights[times <= time] = np.exp((times[times <= time] - time) / self.memory) + weights[times >= time] = np.exp((time - times[times >= time]) / self.memory) + + if not self.acausal: + weights[times > time] = 0.0 + + weights = filter_iqrs(multiseries=baselines, weights=weights) + + return weights + + +def filter_iqr( + series: List[float], threshold: int = 3.0, weights: List[int] = None +) -> List[int]: + """ + Identify "good" elements in series by calculating potentially weighted + 25%, 50% (median), and 75% quantiles of series, the number of 25%-50% + quantile ranges below, or 50%-75% quantile ranges above each value of + series falls from the median, and finally, setting elements of good to + True that fall within these multiples of quantile ranges. + + NOTE: NumPy has a percentile function, but it does not yet handle + weights. This algorithm was adapted from the PyPI + package wquantiles (https://pypi.org/project/wquantiles/) + + Inputs: + series: array of observations to filter + + Options: + threshold: threshold in fractional number of 25%-50% (50%-75%) + quantile ranges below (above) the median each element of + series may fall and still be considered "good" + Default set to 6. + weights: weights to assign to each element of series. Default set to 1. + + Output: + good: Boolean array where True values correspond to "good" data + + """ + + if weights is None: + weights = np.ones_like(series) + + # initialize good as all True for weights greater than 0 + good = (weights > 0).astype(bool) + if np.size(good) <= 1: + # if a singleton is passed, assume it is "good" + return good + + good_old = ~good + while not (good_old == good).all(): + good_old = good + + wq25 = weighted_quartile(series[good], weights[good], 0.25) + wq50 = weighted_quartile(series[good], weights[good], 0.50) + wq75 = weighted_quartile(series[good], weights[good], 0.75) + + # NOTE: it is necessary to include good on the RHS here + # to prevent oscillation between two equally likely + # "optimal" solutions; this is a common problem with + # expectation maximization algorithms + good = ( + good + & (series >= (wq50 - threshold * (wq50 - wq25))) + & (series <= (wq50 + threshold * (wq75 - wq50))) + ) + + return good + + +def filter_iqrs( + multiseries: List[List[float]], + weights: List[float], + threshold: float = 3.0, +) -> List[float]: + """Filters "bad" weights generated by unreliable readings""" + good = None + for series in multiseries: + filtered = filter_iqr(series, threshold=threshold, weights=weights) + if good is None: + good = filtered + else: + good = good & filtered + + return weights * good + + +def weighted_quartile(data: List[float], weights: List[float], quant: float) -> float: + """Get weighted quartile to determine statistically good/bad data + + Attributes + ---------- + data: filtered array of observations + weights: array of vector distances/metrics + quant: statistical percentile of input data + """ + # sort data and weights + ind_sorted = np.argsort(data) + sorted_data = data[ind_sorted] + sorted_weights = weights[ind_sorted] + # compute auxiliary arrays + Sn = np.cumsum(sorted_weights) + Pn = (Sn - 0.5 * sorted_weights) / Sn[-1] + # interpolate to weighted quantile + return np.interp(quant, Pn, sorted_data) diff --git a/geomagio/adjusted/transform/TranslateOrigins.py b/geomagio/adjusted/transform/TranslateOrigins.py new file mode 100644 index 0000000000000000000000000000000000000000..9ba7ae0c4337b4e766265329f1d48fccb46f36e8 --- /dev/null +++ b/geomagio/adjusted/transform/TranslateOrigins.py @@ -0,0 +1,61 @@ +import numpy as np +from typing import List, Optional, Tuple + +from .LeastSq import LeastSq + + +class TranslateOrigins(LeastSq): + """Calculates affine using using least squares, constrained to tanslate origins""" + + def get_matrix( + self, + matrix: List[List[float]], + absolutes: Optional[Tuple[List[float], List[float], List[float]]] = None, + ordinates: Optional[Tuple[List[float], List[float], List[float]]] = None, + weights: Optional[List[float]] = None, + ) -> np.array: + return [ + [1.0, 0.0, 0.0, matrix[0]], + [0.0, 1.0, 0.0, matrix[1]], + [0.0, 0.0, 1.0, matrix[2]], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_stacked_ordinates( + self, ordinates: Tuple[List[float], List[float], List[float]] + ) -> List[List[float]]: + ord_stacked = np.zeros((3, len(ordinates[0]) * 3)) + ord_stacked[0, 0::3] = 1.0 + ord_stacked[1, 1::3] = 1.0 + ord_stacked[2, 2::3] = 1.0 + return ord_stacked + + def get_stacked_values( + self, + absolutes: Tuple[List[float], List[float], List[float]], + ordinates: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]] = None, + ) -> Tuple[List[float], List[List[float]]]: + # LHS, or dependent variables + abs_stacked = self.get_stacked_absolutes(absolutes) + # subtract ords from abs to force simple translation + abs_stacked[0::3] = absolutes[0] - ordinates[0] + abs_stacked[1::3] = absolutes[1] - ordinates[1] + abs_stacked[2::3] = absolutes[2] - ordinates[2] + # RHS, or independent variables + ord_stacked = self.get_stacked_ordinates(ordinates) + return abs_stacked, ord_stacked + + def get_weighted_values( + self, + values: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]], + ) -> List[float]: + """Weights are applied after matrix creation steps, + requiring weights to be stacked similar to ordinates and absolutes""" + if weights is not None: + weights = np.sqrt(weights) + weights = np.vstack((weights, weights, weights)).T.ravel() + else: + weights = 1 + return values * weights diff --git a/geomagio/adjusted/transform/ZRotationHScale.py b/geomagio/adjusted/transform/ZRotationHScale.py new file mode 100644 index 0000000000000000000000000000000000000000..aa3901ef9eb89b6eda6e27b4dabed30167c200bb --- /dev/null +++ b/geomagio/adjusted/transform/ZRotationHScale.py @@ -0,0 +1,42 @@ +import numpy as np +from typing import List, Optional, Tuple + +from .LeastSq import LeastSq + + +class ZRotationHscale(LeastSq): + """Calculates affine using least squares, constrained to rotate about the Z axis + and apply uniform horizontal scaling.""" + + def get_matrix( + self, + matrix: List[List[float]], + absolutes: Optional[Tuple[List[float], List[float], List[float]]] = None, + ordinates: Optional[Tuple[List[float], List[float], List[float]]] = None, + weights: Optional[List[float]] = None, + ) -> np.array: + return [ + [matrix[0], matrix[1], 0.0, matrix[2]], + [-matrix[1], matrix[0], 0.0, matrix[3]], + [0.0, 0.0, matrix[4], matrix[5]], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_stacked_ordinates( + self, ordinates: Tuple[List[float], List[float], List[float]] + ) -> List[List[float]]: + # (reduces degrees of freedom by 10: + # - 2 for making x,y independent of z; + # - 2 for making z independent of x,y + # - 2 for not allowing shear in x,y; and + # - 4 for the last row of zeros and a one) + ord_stacked = np.zeros((6, len(ordinates[0]) * 3)) + ord_stacked[0, 0::3] = ordinates[0] + ord_stacked[0, 1::3] = ordinates[1] + ord_stacked[1, 0::3] = ordinates[1] + ord_stacked[1, 1::3] = -ordinates[0] + ord_stacked[2, 0::3] = 1.0 + ord_stacked[3, 1::3] = 1.0 + ord_stacked[4, 2::3] = ordinates[2] + ord_stacked[5, 2::3] = 1.0 + return ord_stacked diff --git a/geomagio/adjusted/transform/ZRotationHScaleZBaseline.py b/geomagio/adjusted/transform/ZRotationHScaleZBaseline.py new file mode 100644 index 0000000000000000000000000000000000000000..f9998589f1ac50c2f69caab66b9d65ad17ae65ea --- /dev/null +++ b/geomagio/adjusted/transform/ZRotationHScaleZBaseline.py @@ -0,0 +1,55 @@ +import numpy as np +from typing import List, Optional, Tuple + +from .LeastSq import LeastSq + + +class ZRotationHscaleZbaseline(LeastSq): + """Calculates affine using least squares, constrained to rotate about the Z axis, + apply a uniform horizontal scaling, and apply a baseline shift for the Z axis.""" + + def get_matrix( + self, + matrix: List[List[float]], + absolutes: Optional[Tuple[List[float], List[float], List[float]]] = None, + ordinates: Optional[Tuple[List[float], List[float], List[float]]] = None, + weights: Optional[List[float]] = None, + ) -> np.array: + return [ + [matrix[0], matrix[1], 0.0, 0.0], + [-matrix[1], matrix[0], 0.0, 0.0], + [0.0, 0.0, 1.0, matrix[2]], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_stacked_ordinates( + self, ordinates: Tuple[List[float], List[float], List[float]] + ) -> List[List[float]]: + # (reduces degrees of freedom by 13: + # - 2 for making x,y independent of z; + # - 2 for making z independent of x,y; + # - 2 for not allowing shear in x,y; + # - 2 for not allowing translation in x,y; + # - 1 for not allowing scaling in z; and + # - 4 for the last row of zeros and a one) + ord_stacked = np.zeros((3, len(ordinates[0]) * 3)) + ord_stacked[0, 0::3] = ordinates[0] + ord_stacked[0, 1::3] = ordinates[1] + ord_stacked[1, 0::3] = ordinates[1] + ord_stacked[1, 1::3] = -ordinates[0] + ord_stacked[2, 2::3] = 1.0 + return ord_stacked + + def get_stacked_values( + self, + absolutes: Tuple[List[float], List[float], List[float]], + ordinates: Tuple[List[float], List[float], List[float]], + weights: Optional[List[float]] = None, + ) -> Tuple[List[float], List[float]]: + # LHS, or dependent variables + abs_stacked = self.get_stacked_absolutes(absolutes) + # subtract z_o from z_a to force simple z translation + abs_stacked[2::3] = absolutes[2] - ordinates[2] + # RHS, or independent variables + ord_stacked = self.get_stacked_ordinates(ordinates) + return abs_stacked, ord_stacked diff --git a/geomagio/adjusted/transform/ZRotationShear.py b/geomagio/adjusted/transform/ZRotationShear.py new file mode 100644 index 0000000000000000000000000000000000000000..b58f4eb16f638127376e588a7c4c924985376a4e --- /dev/null +++ b/geomagio/adjusted/transform/ZRotationShear.py @@ -0,0 +1,40 @@ +import numpy as np +from typing import List, Optional, Tuple + +from .LeastSq import LeastSq + + +class ZRotationShear(LeastSq): + """Calculates affine using least squares, constrained to rotate about the Z axis""" + + def get_matrix( + self, + matrix: List[List[float]], + absolutes: Optional[Tuple[List[float], List[float], List[float]]] = None, + ordinates: Optional[Tuple[List[float], List[float], List[float]]] = None, + weights: Optional[List[float]] = None, + ) -> np.array: + return [ + [matrix[0], matrix[1], 0.0, matrix[2]], + [matrix[3], matrix[4], 0.0, matrix[5]], + [0.0, 0.0, matrix[6], matrix[7]], + [0.0, 0.0, 0.0, 1.0], + ] + + def get_stacked_ordinates( + self, ordinates: Tuple[List[float], List[float], List[float]] + ) -> List[List[float]]: + # (reduces degrees of freedom by 8: + # - 2 for making x,y independent of z; + # - 2 for making z independent of x,y + # - 4 for the last row of zeros and a one) + ord_stacked = np.zeros((8, len(ordinates[0]) * 3)) + ord_stacked[0, 0::3] = ordinates[0] + ord_stacked[1, 0::3] = ordinates[1] + ord_stacked[2, 0::3] = 1.0 + ord_stacked[3, 1::3] = ordinates[0] + ord_stacked[4, 1::3] = ordinates[1] + ord_stacked[5, 1::3] = 1.0 + ord_stacked[6, 2::3] = ordinates[2] + ord_stacked[7, 2::3] = 1.0 + return ord_stacked diff --git a/geomagio/adjusted/transform/__init__.py b/geomagio/adjusted/transform/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0fa99ab9aca8f355ff40cd866cc7f3b515e2a7f6 --- /dev/null +++ b/geomagio/adjusted/transform/__init__.py @@ -0,0 +1,26 @@ +from .LeastSq import LeastSq +from .QRFactorization import QRFactorization +from .Rescale3D import Rescale3D +from .RotationTranslationXY import RotationTranslationXY +from .ShearYZ import ShearYZ +from .Transform import Transform +from .TranslateOrigins import TranslateOrigins +from .SVD import SVD +from .ZRotationHScale import ZRotationHscale +from .ZRotationHScaleZBaseline import ZRotationHscaleZbaseline +from .ZRotationShear import ZRotationShear + +__all__ = [ + "LeastSq", + "QRFactorization", + "Rescale3D", + "RotationTranslation3D", + "RotationTranslationXY", + "ShearYZ", + "Transform", + "TranslateOrigins", + "SVD", + "ZRotationHscale", + "ZRotationHscaleZbaseline", + "ZRotationShear", +] diff --git a/geomagio/algorithm/AdjustedAlgorithm.py b/geomagio/algorithm/AdjustedAlgorithm.py index 601bad2444246efeee5b0f81f4551f245e3a9406..fcbe2dcebb8e66e2d95e9235a7134e905f52eca6 100644 --- a/geomagio/algorithm/AdjustedAlgorithm.py +++ b/geomagio/algorithm/AdjustedAlgorithm.py @@ -1,23 +1,22 @@ -"""Algorithm that converts from one geomagnetic coordinate system to a - related geographic coordinate system, by using transformations generated - from absolute, baseline measurements. -""" -from __future__ import absolute_import +import sys -from .Algorithm import Algorithm import json import numpy as np from obspy.core import Stream, Stats -import sys + +from ..adjusted import AdjustedMatrix +from .Algorithm import Algorithm class AdjustedAlgorithm(Algorithm): - """Adjusted Data Algorithm""" + """Algorithm that converts from one geomagnetic coordinate system to a + related geographic coordinate system, by using transformations generated + from absolute, baseline measurements. + """ def __init__( self, - matrix=None, - pier_correction=None, + matrix: AdjustedMatrix = None, statefile=None, data_type=None, location=None, @@ -33,7 +32,6 @@ class AdjustedAlgorithm(Algorithm): ) # state variables self.matrix = matrix - self.pier_correction = pier_correction self.statefile = statefile self.data_type = data_type self.location = location @@ -47,23 +45,27 @@ class AdjustedAlgorithm(Algorithm): """ # Adjusted matrix defaults to identity matrix matrix_size = len([c for c in self.get_input_channels() if c != "F"]) + 1 - self.matrix = np.eye(matrix_size) - self.pier_correction = 0 + matrix = np.eye(matrix_size).tolist() if self.statefile is None: + self.matrix = AdjustedMatrix(matrix=matrix) return - data = None try: with open(self.statefile, "r") as f: data = f.read() data = json.loads(data) except IOError as err: - sys.stderr.write("I/O error {0}".format(err)) - if data is None or data == "": - return - for row in range(matrix_size): - for col in range(matrix_size): - self.matrix[row, col] = np.float64(data[f"M{row+1}{col+1}"]) - self.pier_correction = np.float64(data["PC"]) + raise FileNotFoundError("statefile not found") + if "pier_correction" in data: + self.matrix = AdjustedMatrix(**data) + elif "PC" in data: + # read data from legacy format + for row in range(matrix_size): + for col in range(matrix_size): + matrix[row][col] = np.float64(data[f"M{row+1}{col+1}"]) + pier_correction = np.float64(data["PC"]) + self.matrix = AdjustedMatrix(matrix=matrix, pier_correction=pier_correction) + else: + raise ValueError("pier correction not found in statefile") def save_state(self): """Save algorithm state to a file. @@ -71,14 +73,10 @@ class AdjustedAlgorithm(Algorithm): """ if self.statefile is None: return - data = {"PC": self.pier_correction} - length = len(self.matrix[0, :]) - for i in range(0, length): - for j in range(0, length): - key = "M" + str(i + 1) + str(j + 1) - data[key] = self.matrix[i, j] + json_string = self.matrix.json() + json_dict = json.loads(json_string) with open(self.statefile, "w") as f: - f.write(json.dumps(data)) + f.write(json.dumps(json_dict)) def create_trace(self, channel, stats, data): """Utility to create a new trace object. @@ -126,15 +124,11 @@ class AdjustedAlgorithm(Algorithm): out = None inchannels = self.get_input_channels() outchannels = self.get_output_channels() - raws = np.vstack( - [ - stream.select(channel=channel)[0].data - for channel in inchannels - if channel != "F" - ] - + [np.ones_like(stream[0].data)] + adjusted = self.matrix.process( + stream, + inchannels=inchannels, + outchannels=outchannels, ) - adjusted = np.matmul(self.matrix, raws) out = Stream( [ self.create_trace( @@ -142,12 +136,9 @@ class AdjustedAlgorithm(Algorithm): stream.select(channel=inchannels[i])[0].stats, adjusted[i], ) - for i in range(len(adjusted) - 1) + for i in range(len(outchannels)) ] ) - if "F" in inchannels and "F" in outchannels: - f = stream.select(channel="F")[0] - out += self.create_trace("F", f.stats, f.data + self.pier_correction) return out def can_produce_data(self, starttime, endtime, stream): diff --git a/geomagio/algorithm/FilterAlgorithm.py b/geomagio/algorithm/FilterAlgorithm.py index 99b7177bdafa51ec4541f24d12193cb498f92eae..da0f2df18f115f7be5d9e3d25c86147917a65e4a 100644 --- a/geomagio/algorithm/FilterAlgorithm.py +++ b/geomagio/algorithm/FilterAlgorithm.py @@ -14,25 +14,99 @@ from .. import TimeseriesUtility STEPS = [ { # 10 Hz to one second filter "name": "10Hz", + "data_interval": "second", + "data_interval_type": "1-second", "input_sample_period": 0.1, "output_sample_period": 1.0, "window": sps.firwin(123, 0.25, window="blackman", fs=10.0), + "type": "firfilter", + "filter_comments": [ + "Vector 1-second values are computed from 10 Hz values using a Blackman filter (123 taps, cutoff 0.25Hz) centered on the start of the second." + ], }, { # one second to one minute filter "name": "Intermagnet One Minute", + "data_interval": "minute", + "data_interval_type": "1-minute", "input_sample_period": 1.0, "output_sample_period": 60.0, "window": sps.get_window(window=("gaussian", 15.8734), Nx=91), + "type": "firfilter", + "filter_comments": [ + "Scalar and Vector 1-minute values are computed from 1 Hz values using an INTERMAGNET gaussian filter centered on the start of the minute (00:30-01:30)." + ], }, { # one minute to one hour filter "name": "One Hour", + "data_interval": "hour", + "data_interval_type": "1-hour (00-59)", "input_sample_period": 60.0, "output_sample_period": 3600.0, - "window": sps.windows.boxcar(91), + "window": sps.windows.boxcar(60), + "type": "average", + "filter_comments": [ + "Scalar and Vector 1-hour values are computed from average of 1-minute values in the hour (00-59)", + ], + }, + { # one minute to one day filter + "name": "One Day", + "data_interval": "day", + "data_interval_type": "1-day (00:00-23:59)", + "input_sample_period": 60.0, + "output_sample_period": 86400, + "window": sps.windows.boxcar(1440), + "type": "average", + "filter_comments": [ + "Scalar and Vector 1-day values are computed from average of 1-minute values in the day (00:00-23:59)", + ], }, ] +def get_nearest_time(step, output_time, left=True): + interval_start = output_time - ( + output_time.timestamp % step["output_sample_period"] + ) + # shift interval right if needed + if interval_start != output_time and not left: + interval_start += step["output_sample_period"] + # position center of filter, data around interval + half_width = get_step_time_shift(step) + if step["type"] == "average": + filter_center = interval_start + half_width + data_start = interval_start + data_end = (interval_start + step["output_sample_period"]) - step[ + "input_sample_period" + ] + else: + filter_center = interval_start + data_start = filter_center - half_width + data_end = filter_center + half_width + return { + "time": filter_center, + "data_start": data_start, + "data_end": data_end, + } + + +def get_step_time_shift(step): + """Calculates the time shift generated in each filtering step + + Parameters + ---------- + step: dict + Dictionary object holding information about a given filter step + Returns + ------- + shift: float + Time shift value + """ + input_sample_period = step["input_sample_period"] + numtaps = len(step["window"]) + shift = input_sample_period * ((numtaps - 1) / 2) + return shift + + class FilterAlgorithm(Algorithm): """ Filter Algorithm that filters and downsamples data @@ -49,7 +123,7 @@ class FilterAlgorithm(Algorithm): outchannels=None, ): - Algorithm.__init__(self, inchannels=None, outchannels=None) + Algorithm.__init__(self, inchannels=inchannels, outchannels=outchannels) self.coeff_filename = coeff_filename self.filtertype = filtertype self.input_sample_period = input_sample_period @@ -58,7 +132,7 @@ class FilterAlgorithm(Algorithm): self.load_state() # ensure correctly aligned coefficients in each step self.steps = ( - self.steps and [self._prepare_step(step) for step in self.steps] or [] + self.steps and [self._validate_step(step) for step in self.steps] or [] ) def load_state(self): @@ -75,9 +149,19 @@ class FilterAlgorithm(Algorithm): self.steps = [ { "name": "name" in data and data["name"] or "custom", + "data_interval": TimeseriesUtility.get_interval_from_delta( + self.output_sample_period + ), + "data_interval_type": "filtered custom interval", + "filter_comments": "filter_comments" in data + and data["filter_comments"] + or [ + "Data produced by filter utilizing custom coefficients and intervals." + ], "input_sample_period": self.input_sample_period, "output_sample_period": self.output_sample_period, "window": data["window"], + "type": data["type"], } ] @@ -87,7 +171,7 @@ class FilterAlgorithm(Algorithm): """ if self.coeff_filename is None: return - data = {"window": list(self.window)} + data = {"window": list(self.window), "type": self.type} with open(self.coeff_filename, "w") as f: f.write(json.dumps(data)) @@ -105,22 +189,24 @@ class FilterAlgorithm(Algorithm): steps = [] for step in STEPS: - if self.input_sample_period <= step["input_sample_period"]: - if self.output_sample_period >= step["output_sample_period"]: - steps.append(step) + if ( + self.input_sample_period <= step["input_sample_period"] + and self.output_sample_period >= step["output_sample_period"] + ): + if ( + step["type"] == "average" + and step["output_sample_period"] != self.output_sample_period + ): + continue + steps.append(step) return steps - def _prepare_step(self, step) -> Dict: - window = step["window"] - if len(window) % 2 == 1: + def _validate_step(self, step): + """Verifies whether or not firfirlter steps have an odd number of coefficients""" + if step["type"] == "firfilter" and len(step["window"]) % 2 != 1: + raise ValueError("Firfilter requires an odd number of coefficients") + else: return step - sys.stderr.write("Even number of taps. Appending center coefficient.") - new_window = np.array(window) - i = len(window) // 2 - np.insert(new_window, i + 1, np.average(window[i : i + 2])) - new_step = dict(step) - new_step["window"] = new_window - return new_step def can_produce_data(self, starttime, endtime, stream): """Can Produce data @@ -202,39 +288,58 @@ class FilterAlgorithm(Algorithm): decimation = int(output_sample_period / input_sample_period) numtaps = len(window) window = window / sum(window) - # first output timestamp is in the center of the filter window - filter_time_shift = input_sample_period * (numtaps // 2) out = Stream() for trace in stream: - # data to filter - data = trace.data - starttime = trace.stats.starttime + filter_time_shift - # align with the output period - misalignment = starttime.timestamp % output_sample_period - if misalignment != 0: - # skip incomplete input - starttime = (starttime - misalignment) + output_sample_period - input_starttime = starttime - filter_time_shift - offset = int( - 1e-6 - + (input_starttime - trace.stats.starttime) / input_sample_period - ) - print( - f"Skipping {offset} input samples to align output", file=sys.stderr - ) - data = data[offset:] - # check there is still enough data for filter - if len(data) < numtaps: - continue + starttime, data = self.align_trace(step, trace) + # check that there is still enough data to filter + if len(data) < numtaps: + continue filtered = self.firfilter(data, window, decimation) stats = Stats(trace.stats) - stats.starttime = starttime stats.delta = output_sample_period + stats.data_interval = step["data_interval"] + stats.data_interval_type = step["data_interval_type"] + stats.filter_comments = step["filter_comments"] + stats.starttime = starttime stats.npts = len(filtered) trace_out = self.create_trace(stats.channel, stats, filtered) out += trace_out return out + def align_trace(self, step, trace): + """Aligns trace to handle trailing or missing values. + Parameters + ---------- + step: dict + Dictionary object holding information about a given filter step + trace: obspy.core.trace + trace holding data and stats(starttime/endtime) to manipulate in alignment + Returns + ------- + filter_start["time"]: UTCDateTime + shifted time for filtered output + data: numpy array + trimmed data if input trace is misaligned + """ + data = trace.data + start = trace.stats.starttime + filter_start = get_nearest_time(step=step, output_time=start, left=False) + while filter_start["data_start"] < start: + # filter needs more data, shift one output right + filter_start = get_nearest_time( + step=step, + output_time=filter_start["time"] + step["output_sample_period"], + left=False, + ) + + if start != filter_start["data_start"]: + offset = int( + 1e-6 + + (filter_start["data_start"] - start) / step["input_sample_period"] + ) + data = data[offset:] + return filter_start["time"], data + @staticmethod def firfilter(data, window, step, allowed_bad=0.1): """Run fir filter for a numpy array. @@ -304,12 +409,11 @@ class FilterAlgorithm(Algorithm): end of input required to generate requested output. """ steps = self.get_filter_steps() - # calculate start/end from step array - for step in steps: - half = len(step["window"]) // 2 - half_step = half * step["input_sample_period"] - start = start - half_step - end = end + half_step + # calculate start/end from inverted step array + for step in reversed(steps): + start_interval = get_nearest_time(step=step, output_time=start, left=False) + end_interval = get_nearest_time(step=step, output_time=end, left=True) + start, end = start_interval["data_start"], end_interval["data_end"] return (start, end) @classmethod diff --git a/geomagio/algorithm/SqDistAlgorithm.py b/geomagio/algorithm/SqDistAlgorithm.py index 4344505404d76f0891ee3a957f33c75807646fc9..4dc5087a230367e817f1dacaa4991d7523455d20 100644 --- a/geomagio/algorithm/SqDistAlgorithm.py +++ b/geomagio/algorithm/SqDistAlgorithm.py @@ -465,7 +465,7 @@ class SqDistAlgorithm(Algorithm): ts = np.linspace( np.max((-m, -3 * np.round(sig))), np.min((m, 3 * np.round(sig))), - np.int(np.round(np.min((2 * m, 6 * np.round(sig))) + 1)), + int(np.round(np.min((2 * m, 6 * np.round(sig))) + 1)), ) nts = ts.size weights = np.exp(-0.5 * (ts / sig) ** 2) diff --git a/geomagio/api/db/MetadataDatabaseFactory.py b/geomagio/api/db/MetadataDatabaseFactory.py new file mode 100644 index 0000000000000000000000000000000000000000..8782ea4bb679687a95a2d47363301f7014d32ac0 --- /dev/null +++ b/geomagio/api/db/MetadataDatabaseFactory.py @@ -0,0 +1,131 @@ +from datetime import datetime +from typing import List, Optional + +from databases import Database +from obspy import UTCDateTime +from sqlalchemy import or_ + +from ...metadata import Metadata, MetadataQuery +from .metadata_history_table import metadata_history +from .metadata_table import metadata + + +class MetadataDatabaseFactory(object): + def __init__(self, database: Database): + self.database = database + + async def create_metadata(self, meta: Metadata) -> Metadata: + query = metadata.insert() + meta.status = meta.status or "new" + values = meta.datetime_dict(exclude={"id", "metadata_id"}, exclude_none=True) + query = query.values(**values) + meta.id = await self.database.execute(query) + return meta + + async def get_metadata( + self, + params: MetadataQuery, + history: bool = False, + ) -> List[Metadata]: + table = metadata + if history: + table = metadata_history + query = table.select() + ( + id, + category, + starttime, + endtime, + created_after, + created_before, + network, + station, + channel, + location, + data_valid, + status, + ) = params.datetime_dict().values() + if id: + query = query.where(table.c.id == id) + if category: + query = query.where(table.c.category == category) + if network: + query = query.where(table.c.network == network) + if station: + query = query.where(table.c.station == station) + if channel: + query = query.where(table.c.channel.like(channel)) + if location: + query = query.where(table.c.location.like(location)) + if starttime: + query = query.where( + or_( + table.c.endtime == None, + table.c.endtime > starttime, + ) + ) + if endtime: + query = query.where( + or_( + table.c.starttime == None, + table.c.starttime < endtime, + ) + ) + if created_after: + query = query.where(table.c.created_time > created_after) + if created_before: + query = query.where(table.c.created_time < created_before) + if data_valid is not None: + query = query.where(table.c.data_valid == data_valid) + if status is not None: + query = query.where(table.c.status.in_(status)) + rows = await self.database.fetch_all(query) + return [Metadata(**row) for row in rows] + + async def get_metadata_by_id(self, id: int): + meta = await self.get_metadata(MetadataQuery(id=id)) + if len(meta) != 1: + raise ValueError(f"{len(meta)} records found") + return meta[0] + + async def get_metadata_history_by_id(self, id: int) -> Optional[Metadata]: + query = metadata_history.select() + query = query.where(metadata_history.c.id == id) + meta = await self.database.fetch_one(query) + if meta is None: + return meta + return Metadata(**meta) + + async def get_metadata_history_by_metadata_id( + self, metadata_id: int + ) -> List[Metadata]: + async with self.database.transaction() as transaction: + query = metadata_history.select() + query = query.where(metadata_history.c.metadata_id == metadata_id).order_by( + metadata_history.c.updated_time + ) + rows = await self.database.fetch_all(query) + metadata = [Metadata(**row) for row in rows] + current_metadata = await self.get_metadata_by_id(id=metadata_id) + metadata.append(current_metadata) + # return records in order of age(newest first) + metadata.reverse() + return metadata + + async def update_metadata(self, meta: Metadata, updated_by: str) -> Metadata: + async with self.database.transaction() as transaction: + # write current record to metadata history table + original_metadata = await self.get_metadata_by_id(id=meta.id) + original_metadata.metadata_id = original_metadata.id + values = original_metadata.datetime_dict(exclude={"id"}, exclude_none=True) + query = metadata_history.insert() + query = query.values(**values) + original_metadata.id = await self.database.execute(query) + # update record in metadata table + meta.updated_by = updated_by + meta.updated_time = UTCDateTime() + query = metadata.update().where(metadata.c.id == meta.id) + values = meta.datetime_dict(exclude={"id", "metadata_id"}) + query = query.values(**values) + await self.database.execute(query) + return await self.get_metadata_by_id(id=meta.id) diff --git a/geomagio/api/db/__init__.py b/geomagio/api/db/__init__.py index 50e414c7396b72a2aa4d0b7849ba42b801ab417e..dfb22968ecc34f0a19bcc60648f7efae772bf834 100644 --- a/geomagio/api/db/__init__.py +++ b/geomagio/api/db/__init__.py @@ -7,5 +7,10 @@ Modules outside the api should not access the database directly. """ from .common import database, sqlalchemy_metadata +from .MetadataDatabaseFactory import MetadataDatabaseFactory -__all__ = ["database", "sqlalchemy_metadata"] +__all__ = [ + "database", + "sqlalchemy_metadata", + "MetadataDatabaseFactory", +] diff --git a/geomagio/api/db/common.py b/geomagio/api/db/common.py index 317861787e2ce3c2691955d9b1344a16d5c768ae..d5646f1cc6825d445f2c6d63fd71b5df66dcd100 100644 --- a/geomagio/api/db/common.py +++ b/geomagio/api/db/common.py @@ -39,7 +39,9 @@ from sqlalchemy import MetaData # database connection -database = Database(os.getenv("DATABASE_URL", "sqlite:///./api_database.db")) +database_url = os.getenv("DATABASE_URL", None) +database_url = database_url or "sqlite:///./api_database.db" +database = Database(database_url) # metadata used to manage database schema sqlalchemy_metadata = MetaData() diff --git a/geomagio/api/db/create.py b/geomagio/api/db/create.py index 0ccc0a97a0d0ce5161a3850dfc0d8588ada2fdfd..531b79a9b00212624cfe1dbb566da6db08ab75aa 100644 --- a/geomagio/api/db/create.py +++ b/geomagio/api/db/create.py @@ -3,13 +3,15 @@ import sqlalchemy from .common import database, sqlalchemy_metadata # register models with sqlalchemy_metadata by importing +from .metadata_history_table import metadata_history from .metadata_table import metadata from .session_table import session def create_db(): """Create the database using sqlalchemy.""" - engine = sqlalchemy.create_engine(str(database.url)) + database_url = str(database.url).replace("mysql://", "mysql+pymysql://") + engine = sqlalchemy.create_engine(database_url) sqlalchemy_metadata.create_all(engine) diff --git a/geomagio/api/db/metadata_history_table.py b/geomagio/api/db/metadata_history_table.py new file mode 100644 index 0000000000000000000000000000000000000000..471869fe132d16a249ca31d283c0235a8e2d10d2 --- /dev/null +++ b/geomagio/api/db/metadata_history_table.py @@ -0,0 +1,18 @@ +from sqlalchemy import Column, ForeignKey, Integer + +from .common import sqlalchemy_metadata +from .metadata_table import metadata + +# create copy of original metadata table and add to sqlalchemy metadata +metadata_history = metadata.tometadata( + metadata=sqlalchemy_metadata, name="metadata_history" +) +metadata_history.indexes.clear() +metadata_history.append_column( + Column( + "metadata_id", + Integer, + ForeignKey("metadata.id"), + nullable=False, + ), +) diff --git a/geomagio/api/db/metadata_table.py b/geomagio/api/db/metadata_table.py index 9b1571a21c1abf54a7aed5e644e0974d7c001c99..6b5716db50b2e96b9102f0c16f61a021884e6bee 100644 --- a/geomagio/api/db/metadata_table.py +++ b/geomagio/api/db/metadata_table.py @@ -1,12 +1,7 @@ -from datetime import datetime -import enum - -from obspy import UTCDateTime -from sqlalchemy import or_, Boolean, Column, Index, Integer, JSON, String, Table, Text +from sqlalchemy import Boolean, Column, Index, Integer, JSON, String, Table, Text import sqlalchemy_utc -from ...metadata import Metadata, MetadataCategory -from .common import database, sqlalchemy_metadata +from .common import sqlalchemy_metadata """Metadata database model. @@ -26,9 +21,9 @@ metadata = Table( default=sqlalchemy_utc.utcnow(), index=True, ), - # reviewer - Column("reviewed_by", String(length=255), index=True, nullable=True), - Column("reviewed_time", sqlalchemy_utc.UtcDateTime, index=True, nullable=True), + # editor + Column("updated_by", String(length=255), index=True, nullable=True), + Column("updated_time", sqlalchemy_utc.UtcDateTime, index=True, nullable=True), # time range Column("starttime", sqlalchemy_utc.UtcDateTime, index=True, nullable=True), Column("endtime", sqlalchemy_utc.UtcDateTime, index=True, nullable=True), @@ -43,8 +38,8 @@ metadata = Table( Column("priority", Integer, default=1), # whether data is valid (primarily for flags) Column("data_valid", Boolean, default=True, index=True), - # whether metadata is valid (based on review) - Column("metadata_valid", Boolean, default=True, index=True), + # deletion status indicator + Column("status", String(length=255), nullable=True), # metadata json blob Column("metadata", JSON, nullable=True), # general comment @@ -65,8 +60,8 @@ metadata = Table( "starttime", "endtime", # valid - "metadata_valid", "data_valid", + "status", ), Index( "index_category_time", @@ -77,64 +72,3 @@ metadata = Table( "endtime", ), ) - - -async def create_metadata(meta: Metadata) -> Metadata: - query = metadata.insert() - values = meta.datetime_dict(exclude={"id"}, exclude_none=True) - query = query.values(**values) - metadata.id = await database.execute(query) - return metadata - - -async def delete_metadata(id: int) -> None: - query = metadata.delete().where(metadata.c.id == id) - await database.execute(query) - - -async def get_metadata( - *, # make all params keyword - id: int = None, - network: str = None, - station: str = None, - channel: str = None, - location: str = None, - category: MetadataCategory = None, - starttime: datetime = None, - endtime: datetime = None, - data_valid: bool = None, - metadata_valid: bool = None, -): - query = metadata.select() - if id is not None: - query = query.where(metadata.c.id == id) - if category: - query = query.where(metadata.c.category == category) - if network or station or channel or location: - query = ( - query.where(metadata.c.network.like(network or "%")) - .where(metadata.c.station.like(station or "%")) - .where(metadata.c.channel.like(channel or "%")) - .where(metadata.c.location.like(location or "%")) - ) - if starttime: - query = query.where( - or_(metadata.c.endtime == None, metadata.c.endtime > starttime) - ) - if endtime: - query = query.where( - or_(metadata.c.starttime == None, metadata.c.starttime < endtime) - ) - if data_valid is not None: - query = query.where(metadata.c.data_valid == data_valid) - if metadata_valid is not None: - query = query.where(metadata.c.metadata_valid == metadata_valid) - rows = await database.fetch_all(query) - return [Metadata(**row) for row in rows] - - -async def update_metadata(meta: Metadata) -> None: - query = metadata.update().where(metadata.c.id == meta.id) - values = meta.datetime_dict(exclude={"id"}) - query = query.values(**values) - await database.execute(query) diff --git a/geomagio/api/db/session_table.py b/geomagio/api/db/session_table.py index f5a3f89e4045b56b8dc7673c64f4014b04a77847..61f4b589a0217b5c6ade5f729f8deddd23a33a17 100644 --- a/geomagio/api/db/session_table.py +++ b/geomagio/api/db/session_table.py @@ -1,6 +1,4 @@ from datetime import datetime, timedelta, timezone -import json -from typing import Dict, Optional import sqlalchemy import sqlalchemy_utc diff --git a/geomagio/api/secure/SessionMiddleware.py b/geomagio/api/secure/SessionMiddleware.py index 04c621c18d6058340ca6aaa40f0334e883d10aaf..3bf0ea8e07cf2f5fe89a434b2a12615e84ad60a4 100644 --- a/geomagio/api/secure/SessionMiddleware.py +++ b/geomagio/api/secure/SessionMiddleware.py @@ -1,10 +1,9 @@ -import base64 import json from typing import Callable, Dict, Mapping import uuid from cryptography.fernet import Fernet -from starlette.datastructures import MutableHeaders, Secret +from starlette.datastructures import MutableHeaders from starlette.requests import HTTPConnection from starlette.types import ASGIApp, Message, Receive, Scope, Send diff --git a/geomagio/api/secure/app.py b/geomagio/api/secure/app.py index 753b9e3bca9ff2cdfe4cda7fb6bd50d882b3f00c..79e3f8c6a66a79fe96afdecda79f816c6cc03e11 100644 --- a/geomagio/api/secure/app.py +++ b/geomagio/api/secure/app.py @@ -11,7 +11,11 @@ from .metadata import router as metadata_router from .SessionMiddleware import SessionMiddleware -app = FastAPI(root_path="/ws/secure") +app = FastAPI( + description="Web service for interaction with operational metadata records", + root_path="/ws/secure", + title="Geomagnetism Metadata Web Service", +) # NOTE: database used for sessions is started by ..app.app, # which mounts this application at /ws/secure @@ -52,4 +56,5 @@ async def index(request: Request, user: User = Depends(current_user)): </body> </html>""", media_type="text/html", + headers={"Cache-control": "no-cache"}, ) diff --git a/geomagio/api/secure/login.py b/geomagio/api/secure/login.py index c3407d9317c5f6d4fad6f6a0f62c0b2b23d1df40..9bab1d8d7877449a8dff11bef03f3fe94539641c 100644 --- a/geomagio/api/secure/login.py +++ b/geomagio/api/secure/login.py @@ -39,11 +39,16 @@ from typing import Callable, List, Optional from authlib.integrations.starlette_client import OAuth from fastapi import APIRouter, Depends, HTTPException +import httpx from pydantic import BaseModel from starlette.requests import Request from starlette.responses import RedirectResponse +GITLAB_HOST = os.getenv("GITLAB_HOST", "code.usgs.gov") +GITLAB_API_URL = os.getenv("GITLAB_API_URL", f"https://{GITLAB_HOST}/api/v4") + + class User(BaseModel): """Information about a logged in user.""" @@ -56,15 +61,46 @@ class User(BaseModel): async def current_user(request: Request) -> Optional[User]: - """Get logged in user, or None if not logged in. + """Get user information from gitlab access token or session(if currently logged in). + Returns none if access token is not vald or user is not logged in. Usage example: user: Optional[User] = Depends(current_user) """ + user = None if "user" in request.session: - return User(**request.session["user"]) - return None + user = User(**request.session["user"]) + elif "Authorization" in request.headers: + user = await get_gitlab_user(token=request.headers["Authorization"]) + if user is not None: + request.session["user"] = user.dict() + return user + + +async def get_gitlab_user(token: str, url: str = GITLAB_API_URL) -> Optional[User]: + header = {"PRIVATE-TOKEN": token} + # request user information from gitlab api with access token + + try: + # use httpx/async so this doesn't block other requests + async with httpx.AsyncClient() as client: + userinfo_response = await client.get(f"{url}/user", headers=header) + userinfo = userinfo_response.json() + user = User( + email=userinfo["email"], + sub=userinfo["id"], + name=userinfo["name"], + nickname=userinfo["username"], + picture=userinfo["avatar_url"], + ) + # use valid token to retrieve user's groups + groups_response = await client.get(f"{url}/groups", headers=header) + user.groups = [g["full_path"] for g in groups_response.json()] + return user + except Exception: + logging.exception(f"Unable to get gitlab user") + return None def require_user( @@ -105,17 +141,20 @@ oauth.register( name="openid", client_id=os.getenv("OPENID_CLIENT_ID"), client_secret=os.getenv("OPENID_CLIENT_SECRET"), - server_metadata_url=os.getenv("OPENID_METADATA_URL"), + server_metadata_url=os.getenv( + "OPENID_METADATA_URL", f"https://{GITLAB_HOST}/.well-known/openid-configuration" + ), client_kwargs={"scope": "openid email profile"}, ) # routes for login/logout router = APIRouter() -@router.get("/authorize") +@router.get( + "/authorize", + description="Authorize callback after authenticating using OpenID", +) async def authorize(request: Request): - """Authorize callback after authenticating using OpenID""" - # finish login token = await oauth.openid.authorize_access_token(request) @@ -133,10 +172,15 @@ async def authorize(request: Request): ) -@router.get("/login") +@router.get( + "/login", + description="Redirect to OpenID provider.", +) async def login(request: Request): - """Redirect to OpenID provider.""" redirect_uri = request.url_for("authorize") + if "127.0.0.1" not in redirect_uri: + # 127.0.0.1 used for local dev, all others use https + redirect_uri = redirect_uri.replace("http://", "https://") # save original location if "Referer" in request.headers: request.session["after_authorize_redirect"] = request.headers["Referer"] @@ -144,9 +188,11 @@ async def login(request: Request): return await oauth.openid.authorize_redirect(request, redirect_uri) -@router.get("/logout") +@router.get( + "/logout", + description="Clear session and redirect to index page.", +) async def logout(request: Request): - """Clear session and redirect to index page.""" request.session.pop("token", None) request.session.pop("user", None) return RedirectResponse( @@ -158,7 +204,9 @@ async def logout(request: Request): ) -@router.get("/user") +@router.get( + "/user", + description="Get currently logged in user.", +) async def user(request: Request, user: User = Depends(require_user())) -> User: - """Get currently logged in user.""" return user diff --git a/geomagio/api/secure/metadata.py b/geomagio/api/secure/metadata.py index fb0dfc4cfeed3981e07d37f501062975c5055420..e5acb0849bc3f08cb00b3e6a8ed092adcfd4b6bd 100644 --- a/geomagio/api/secure/metadata.py +++ b/geomagio/api/secure/metadata.py @@ -16,76 +16,135 @@ Configuration: import os from typing import List -from fastapi import APIRouter, Body, Depends, Request, Response +from fastapi import APIRouter, Body, Depends, Request, Response, Query from obspy import UTCDateTime -from ...metadata import Metadata, MetadataCategory -from ..db import metadata_table -from .login import require_user, User -from .MetadataQuery import MetadataQuery +from ...metadata import Metadata, MetadataCategory, MetadataQuery from ... import pydantic_utcdatetime +from ..db.common import database +from ..db import MetadataDatabaseFactory +from .login import require_user, User # routes for login/logout router = APIRouter() -@router.post("/metadata", response_model=Metadata) -async def create_metadata( - request: Request, - metadata: Metadata, - user: User = Depends(require_user()), -): - metadata = await metadata_table.create_metadata(metadata) - return Response(metadata, status_code=201, media_type="application/json") - - -@router.delete("/metadata/{id}") -async def delete_metadata( - id: int, user: User = Depends(require_user(os.getenv("ADMIN_GROUP", "admin"))) -): - await metadata_table.delete_metadata(id) - - -@router.get("/metadata", response_model=List[Metadata]) -async def get_metadata( +def get_metadata_query( category: MetadataCategory = None, starttime: UTCDateTime = None, endtime: UTCDateTime = None, + created_after: UTCDateTime = None, + created_before: UTCDateTime = None, network: str = None, station: str = None, channel: str = None, location: str = None, data_valid: bool = None, - metadata_valid: bool = True, -): - query = MetadataQuery( + status: List[str] = Query(None), +) -> MetadataQuery: + return MetadataQuery( category=category, starttime=starttime, endtime=endtime, + created_after=created_after, + created_before=created_before, network=network, station=station, channel=channel, location=location, data_valid=data_valid, - metadata_valid=metadata_valid, + status=status, + ) + + +@router.post( + "/metadata", + description="Save metadata in database", + response_model=Metadata, +) +async def create_metadata( + request: Request, + metadata: Metadata, + user: User = Depends(require_user()), +): + metadata = await MetadataDatabaseFactory(database=database).create_metadata( + meta=metadata ) - metas = await metadata_table.get_metadata(**query.datetime_dict(exclude={"id"})) + return Response(metadata.json(), status_code=201, media_type="application/json") + + +@router.get( + "/metadata", + description="Search metadata by query parameters", + name="Request metadata", + response_model=List[Metadata], +) +async def get_metadata(query: MetadataQuery = Depends(get_metadata_query)): + metas = await MetadataDatabaseFactory(database=database).get_metadata(params=query) return metas -@router.get("/metadata/{id}", response_model=Metadata) +@router.get( + "/metadata/history", + description="Search historical metadata by query parameters", + response_model=List[Metadata], +) +async def get_metadata_history(query: MetadataQuery = Depends(get_metadata_query)): + metas = await MetadataDatabaseFactory(database=database).get_metadata( + params=query, history=True + ) + return metas + + +@router.get( + "/metadata/{id}", + description="Search metadata by database id", + response_model=Metadata, +) async def get_metadata_by_id(id: int): - meta = await metadata_table.get_metadata(id=id) - if len(meta) != 1: + return await MetadataDatabaseFactory(database=database).get_metadata_by_id(id=id) + + +@router.get( + "/metadata/{metadata_id}/history", + description="Search metadata version history by database id", + response_model=List[Metadata], +) +async def get_metadata_history_by_metadata_id( + metadata_id: int, +): + return await MetadataDatabaseFactory( + database=database + ).get_metadata_history_by_metadata_id( + metadata_id=metadata_id, + ) + + +@router.get( + "/metadata/history/{id}", + description="Search historical metadata by database id", + response_model=Metadata, +) +async def get_metadata_history_by_id(id: int): + metadata = await MetadataDatabaseFactory( + database=database + ).get_metadata_history_by_id(id=id) + if metadata is None: return Response(status_code=404) - else: - return meta[0] + return metadata -@router.put("/metadata/{id}") +@router.put( + "/metadata/{id}", + description="Edit metadata from older version", + response_model=Metadata, +) async def update_metadata( id: int, metadata: Metadata = Body(...), user: User = Depends(require_user([os.getenv("REVIEWER_GROUP", "reviewer")])), ): - await metadata_table.update_metadata(metadata) + return await MetadataDatabaseFactory(database=database).update_metadata( + meta=metadata, + updated_by=user.nickname, + ) diff --git a/geomagio/api/ws/DataApiQuery.py b/geomagio/api/ws/DataApiQuery.py index 5d7cb3b2eabbf3867681bff082c4a6ed7d09cd86..d955b7882a12988697232f45e72bc33dae66353d 100644 --- a/geomagio/api/ws/DataApiQuery.py +++ b/geomagio/api/ws/DataApiQuery.py @@ -1,42 +1,20 @@ import datetime import enum -from typing import Any, Dict, List, Optional, Union +import os +from typing import Dict, List, Union from obspy import UTCDateTime from pydantic import BaseModel, root_validator, validator from ... import pydantic_utcdatetime -from .Element import ELEMENTS, ELEMENT_INDEX +from .Element import ELEMENTS +from .Observatory import OBSERVATORY_INDEX + DEFAULT_ELEMENTS = ["X", "Y", "Z", "F"] REQUEST_LIMIT = 345600 VALID_ELEMENTS = [e.id for e in ELEMENTS] -VALID_OBSERVATORIES = [ - "BDT", - "BOU", - "BRT", - "BRW", - "BSL", - "CMO", - "CMT", - "DED", - "DHT", - "FDT", - "FRD", - "FRN", - "GUA", - "HON", - "NEW", - "SHU", - "SIT", - "SJG", - "SJT", - "TST", - "TUC", - "USGS", -] - class DataType(str, enum.Enum): VARIATION = "variation" @@ -44,6 +22,10 @@ class DataType(str, enum.Enum): QUASI_DEFINITIVE = "quasi-definitive" DEFINITIVE = "definitive" + @classmethod + def values(cls) -> List[str]: + return [t.value for t in cls] + class OutputFormat(str, enum.Enum): IAGA2002 = "iaga2002" @@ -71,10 +53,10 @@ class DataApiQuery(BaseModel): def validate_data_type( cls, data_type: Union[DataType, str] ) -> Union[DataType, str]: - if data_type not in DataType and len(data_type) != 2: + if data_type not in DataType.values() and len(data_type) != 2: raise ValueError( f"Bad data type value '{data_type}'." - f" Valid values are: {', '.join(list(DataType))}" + f" Valid values are: {', '.join(DataType.values())}" ) return data_type @@ -94,10 +76,10 @@ class DataApiQuery(BaseModel): @validator("id") def validate_id(cls, id: str) -> str: - if id not in VALID_OBSERVATORIES: + if id not in OBSERVATORY_INDEX: raise ValueError( f"Bad observatory id '{id}'." - f" Valid values are: {', '.join(VALID_OBSERVATORIES)}." + f" Valid values are: {', '.join(sorted(OBSERVATORY_INDEX.keys()))}." ) return id diff --git a/geomagio/api/ws/Element.py b/geomagio/api/ws/Element.py index 952feae9024d7fb3f42d00e5a5fa01166018773d..b0df40d1da3f54d4c7d5d3d734bde9a18e6e80d4 100644 --- a/geomagio/api/ws/Element.py +++ b/geomagio/api/ws/Element.py @@ -10,6 +10,9 @@ class Element(BaseModel): ELEMENTS = [ + Element(id="U", name="North Component(miniseed)", units="nT"), + Element(id="V", name="East Component(miniseed)", units="nT"), + Element(id="W", name="Vertical Component(miniseed)", units="nT"), Element(id="H", name="North Component", units="nT"), Element(id="E", name="East Component", units="nT"), Element(id="X", name="Geographic North Magnitude", units="nT"), diff --git a/geomagio/api/ws/Observatory.py b/geomagio/api/ws/Observatory.py index ebd10da2f51d5bad8c5d14ef1eb2d56d162c18ed..fe857c9acfed34b9fbe96da7b2ad1b85fc9b3589 100644 --- a/geomagio/api/ws/Observatory.py +++ b/geomagio/api/ws/Observatory.py @@ -51,6 +51,24 @@ class Observatory(BaseModel): sensor_orientation = "HDZF" return sensor_orientation + def geojson(self) -> Dict: + return { + "type": "Feature", + "id": self.id, + "properties": { + "name": self.name, + "agency": self.agency, + "agency_name": self.agency_name, + "sensor_orientation": self.sensor_orientation, + "sensor_sampling_rate": 0.01, + "declination_base": self.declination_base, + }, + "geometry": { + "type": "Point", + "coordinates": [self.longitude, self.latitude, self.elevation], + }, + } + OBSERVATORIES = [ Observatory( @@ -107,6 +125,15 @@ OBSERVATORIES = [ agency="USGS", declination_base=215772, ), + Observatory( + id="BXX", + elevation=1682, + latitude=40.137, + longitude=254.763, + name="Boulder Test(Coil Building)", + agency="USGS", + declination_base=5527, + ), Observatory( id="CMO", elevation=197, @@ -153,7 +180,7 @@ OBSERVATORIES = [ declination_base=209690, ), Observatory( - id="FRT", + id="FDT", elevation=69, latitude=38.205, longitude=282.627, diff --git a/geomagio/api/ws/algorithms.py b/geomagio/api/ws/algorithms.py index 0e2d51cb9fe9bb1546fc7bbea2cb77d09472d09a..6bf055c33ca2503897bbbbe0c8e264b373e78f75 100644 --- a/geomagio/api/ws/algorithms.py +++ b/geomagio/api/ws/algorithms.py @@ -1,8 +1,13 @@ -from fastapi import APIRouter, Depends +from os import name +from fastapi import APIRouter, Depends, HTTPException from starlette.responses import Response from ... import TimeseriesFactory from ...algorithm import DbDtAlgorithm +from ...residual import ( + calculate, + Reading, +) from .DataApiQuery import DataApiQuery from .data import format_timeseries, get_data_factory, get_data_query, get_timeseries @@ -10,12 +15,16 @@ from .data import format_timeseries, get_data_factory, get_data_query, get_times router = APIRouter() -@router.get("/algorithms/dbdt/") +@router.get( + "/algorithms/dbdt/", + description="First order derivative at requested interval", + name="Dbdt Algorithm", +) def get_dbdt( query: DataApiQuery = Depends(get_data_query), - data_factory: TimeseriesFactory = Depends(get_data_factory), ) -> Response: - dbdt = DbDtAlgorithm() + dbdt = DbDtAlgorithm(period=query.sampling_period) + data_factory = get_data_factory(query=query) # read data raw = get_timeseries(data_factory, query) # run dbdt @@ -25,3 +34,16 @@ def get_dbdt( return format_timeseries( timeseries=timeseries, format=query.format, elements=elements ) + + +@router.post( + "/algorithms/residual", + description="Caclulates new absolutes and baselines from reading\n\n" + + "Returns posted reading object with updated values", + response_model=Reading, +) +def calculate_residual(reading: Reading, adjust_reference: bool = True): + try: + return calculate(reading=reading, adjust_reference=adjust_reference) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) diff --git a/geomagio/api/ws/app.py b/geomagio/api/ws/app.py index 9431fc44756063c5258b4052fc3dc4f0c35e2186..fdf9cc57177e963d450866c244fda4bb369d903c 100644 --- a/geomagio/api/ws/app.py +++ b/geomagio/api/ws/app.py @@ -1,13 +1,11 @@ import os -from typing import Dict, Union from fastapi import FastAPI, Request, Response from fastapi.exceptions import RequestValidationError -from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, PlainTextResponse, RedirectResponse from obspy import UTCDateTime -from . import algorithms, data, elements, observatories +from . import algorithms, data, elements, metadata, observatories ERROR_CODE_MESSAGES = { @@ -20,16 +18,38 @@ ERROR_CODE_MESSAGES = { 503: "Service Unavailable", } +METADATA_ENDPOINT = bool(os.getenv("METADATA_ENDPOINT", False)) VERSION = os.getenv("GEOMAG_VERSION", "version") -app = FastAPI(docs_url="/docs", root_path="/ws") +app = FastAPI( + description="Web service for data access and observatory/element information\n\n" + + "Supports realtime processing via algorithms", + docs_url="/docs", + root_path="/ws", + title="Geomagnetism Data Web Service", +) app.include_router(algorithms.router) app.include_router(data.router) app.include_router(elements.router) app.include_router(observatories.router) +if METADATA_ENDPOINT: + app.include_router(metadata.router) + + +@app.middleware("http") +async def add_headers(request: Request, call_next): + response = await call_next(request) + response.headers[ + "Access-Control-Allow-Headers" + ] = "accept, origin, authorization, content-type" + response.headers["Access-Control-Allow-Methods"] = "*" + response.headers["Access-Control-Allow-Origin"] = "*" + response.headers["Cache-Control"] = "max-age=60" + return response + @app.get("/", include_in_schema=False) async def redirect_to_docs(): @@ -62,13 +82,22 @@ def format_error( status_code: int, exception: str, format: str, request: Request ) -> Response: """Assign error_body value based on error format.""" + # These urls are embedded in responses + # and app usually runs behind reverse proxy + url = str(request.url) + usage = f"http://{request.headers['host']}/ws/docs" + if "x-forwarded-proto" in request.headers: + proto = f"{request.headers['x-forwarded-proto']}:" + url = url.replace("http:", proto) + usage = usage.replace("http:", proto) + # serve error if format == "json": - return json_error(status_code, exception, request.url) + return json_error(code=status_code, error=exception, url=url, usage=usage) else: - return text_error(status_code, exception, request.url) + return text_error(code=status_code, error=exception, url=url, usage=usage) -def json_error(code: int, error: Exception, url: str) -> Response: +def json_error(code: int, error: Exception, url: str, usage: str) -> Response: """Format json error message. Returns @@ -84,7 +113,8 @@ def json_error(code: int, error: Exception, url: str) -> Response: "status": code, "error": str(error), "generated": f"{UTCDateTime().isoformat()}Z", - "url": str(url), + "url": url, + "usage": usage, "version": VERSION, }, }, @@ -92,7 +122,7 @@ def json_error(code: int, error: Exception, url: str) -> Response: ) -def text_error(code: int, error: Exception, url: str) -> Response: +def text_error(code: int, error: Exception, url: str, usage: str = "") -> Response: """Format error message as plain text Returns @@ -104,7 +134,7 @@ def text_error(code: int, error: Exception, url: str) -> Response: {error} -Usage details are available from +Usage details are available from {usage} Request: {url} diff --git a/geomagio/api/ws/data.py b/geomagio/api/ws/data.py index 053474c1d3834931d81d5d1e18dee1041575cbf5..24f16b226acc8aa1e44913c622a202cf63b8c55b 100644 --- a/geomagio/api/ws/data.py +++ b/geomagio/api/ws/data.py @@ -1,12 +1,12 @@ import os -from typing import Any, Dict, List, Union +from typing import List, Union from fastapi import APIRouter, Depends, Query from obspy import UTCDateTime, Stream from starlette.responses import Response -from ... import TimeseriesFactory, TimeseriesUtility -from ...edge import EdgeFactory +from ... import DerivedTimeseriesFactory, TimeseriesFactory, TimeseriesUtility +from ...edge import EdgeFactory, MiniSeedFactory from ...iaga2002 import IAGA2002Writer from ...imfjson import IMFJSONWriter from .DataApiQuery import ( @@ -18,7 +18,9 @@ from .DataApiQuery import ( ) -def get_data_factory() -> TimeseriesFactory: +def get_data_factory( + query: DataApiQuery, +) -> TimeseriesFactory: """Reads environment variable to determine the factory to be used Returns @@ -26,13 +28,23 @@ def get_data_factory() -> TimeseriesFactory: data_factory Edge or miniseed factory object """ - data_type = os.getenv("DATA_TYPE", "edge") - data_host = os.getenv("DATA_HOST", "cwbpub.cr.usgs.gov") - data_port = int(os.getenv("DATA_PORT", "2060")) - if data_type == "edge": - return EdgeFactory(host=data_host, port=data_port) + host = os.getenv("DATA_HOST", "cwbpub.cr.usgs.gov") + sampling_period = query.sampling_period + if sampling_period in [ + SamplingPeriod.TEN_HERTZ, + SamplingPeriod.HOUR, + SamplingPeriod.DAY, + ]: + factory = MiniSeedFactory( + host=host, port=int(os.getenv("DATA_MINISEED_PORT", "2061")) + ) + elif sampling_period in [SamplingPeriod.SECOND, SamplingPeriod.MINUTE]: + factory = EdgeFactory( + host=host, port=int(os.getenv("DATA_EARTHWORM_PORT", "2060")) + ) else: return None + return DerivedTimeseriesFactory(factory) def get_data_query( @@ -147,11 +159,16 @@ def get_timeseries(data_factory: TimeseriesFactory, query: DataApiQuery) -> Stre router = APIRouter() -@router.get("/data/") +@router.get( + "/data/", + name="Request data", + description="Returns timeseries depending on query parameters\n\n" + + "Limited to 345600 data points", +) def get_data( query: DataApiQuery = Depends(get_data_query), - data_factory: TimeseriesFactory = Depends(get_data_factory), ) -> Response: + data_factory = get_data_factory(query=query) # read data timeseries = get_timeseries(data_factory, query) # output response diff --git a/geomagio/api/ws/elements.py b/geomagio/api/ws/elements.py index 11a6c95d7ccf7b5fb00b5e58ff9a83df6e0cb78d..06da476a84d61ae94861472582113bbcc52bb5b7 100644 --- a/geomagio/api/ws/elements.py +++ b/geomagio/api/ws/elements.py @@ -8,17 +8,23 @@ from .Element import ELEMENTS router = APIRouter() -@router.get("/elements/") +@router.get( + "/elements/", + description="Information regarding available geomagnetic elements", +) def get_elements() -> Dict: - return { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "id": e.id, - "properties": {"name": e.name, "units": e.units}, - "geometry": None, - } - for e in ELEMENTS - ], - } + features = [] + for e in ELEMENTS: + feature = { + "type": "Feature", + "id": e.id, + "properties": { + "name": e.name, + "units": e.units, + }, + "geometry": None, + } + if e.abbreviation: + feature["properties"]["abbreviation"] = e.abbreviation + features.append(feature) + return {"type": "FeatureCollection", "features": features} diff --git a/geomagio/api/ws/metadata.py b/geomagio/api/ws/metadata.py new file mode 100644 index 0000000000000000000000000000000000000000..1775569a434dda86c04a731494ea0060db1980d0 --- /dev/null +++ b/geomagio/api/ws/metadata.py @@ -0,0 +1,43 @@ +from typing import List + +from fastapi import APIRouter, Query +from obspy import UTCDateTime + +from ...metadata import Metadata, MetadataCategory, MetadataQuery +from ..db.common import database +from ..db import MetadataDatabaseFactory + +router = APIRouter() + + +@router.get( + "/metadata", + description="Search metadata records with query parameters(excludes id and metadata id)", + response_model=List[Metadata], +) +async def get_metadata( + category: MetadataCategory = None, + starttime: UTCDateTime = None, + endtime: UTCDateTime = None, + network: str = None, + station: str = None, + channel: str = None, + location: str = None, + data_valid: bool = None, + status: List[str] = Query(None), +): + query = MetadataQuery( + category=category, + starttime=starttime, + endtime=endtime, + network=network, + station=station, + channel=channel, + location=location, + data_valid=data_valid, + status=status, + ) + metas = await MetadataDatabaseFactory(database=database).get_metadata( + **query.datetime_dict(exclude={"id", "metadata_id"}) + ) + return metas diff --git a/geomagio/api/ws/observatories.py b/geomagio/api/ws/observatories.py index 9e9d19416209dc28ad854d132ae66c6dc9de6fbf..1a8c797c258952ba8793f12a903ffb70b93b6abf 100644 --- a/geomagio/api/ws/observatories.py +++ b/geomagio/api/ws/observatories.py @@ -1,34 +1,30 @@ from typing import Dict -from fastapi import APIRouter +from fastapi import APIRouter, Response -from .Observatory import OBSERVATORIES +from .Observatory import OBSERVATORIES, OBSERVATORY_INDEX router = APIRouter() -@router.get("/observatories/") +@router.get( + "/observatories/", + description="Information regarding available geomagnetic observatories", +) def get_observatories() -> Dict: return { "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "id": o.id, - "properties": { - "name": o.name, - "agency": o.agency, - "agency_name": o.agency_name, - "sensor_orientation": o.sensor_orientation, - "sensor_sampling_rate": 0.01, - "declination_base": o.declination_base, - }, - "geometry": { - "type": "Point", - "coordinates": [o.longitude, o.latitude, o.elevation], - }, - } - for o in OBSERVATORIES - ], + "features": [o.geojson() for o in OBSERVATORIES], } + + +@router.get( + "/observatories/{id}", + description="Search observatories by 3-letter observatory code", +) +async def get_observatory_by_id(id: str) -> Dict: + try: + return OBSERVATORY_INDEX[id].geojson() + except KeyError: + return Response(status_code=404) diff --git a/geomagio/edge/EdgeFactory.py b/geomagio/edge/EdgeFactory.py index a22514650586aaaad0da69ac12cd0365e0f1d405..c25955fe141ab4114ed5c28b4a95d32ff1cfe2ed 100644 --- a/geomagio/edge/EdgeFactory.py +++ b/geomagio/edge/EdgeFactory.py @@ -22,6 +22,7 @@ from ..TimeseriesFactory import TimeseriesFactory from ..TimeseriesFactoryException import TimeseriesFactoryException from ..ObservatoryMetadata import ObservatoryMetadata from .RawInputClient import RawInputClient +from .LegacySNCL import LegacySNCL class EdgeFactory(TimeseriesFactory): @@ -90,7 +91,6 @@ class EdgeFactory(TimeseriesFactory): ): TimeseriesFactory.__init__(self, observatory, channels, type, interval) self.client = earthworm.Client(host, port) - self.observatoryMetadata = observatoryMetadata or ObservatoryMetadata() self.tag = tag self.locationCode = locationCode @@ -110,6 +110,7 @@ class EdgeFactory(TimeseriesFactory): channels=None, type=None, interval=None, + add_empty_channels: bool = True, ): """Get timeseries data @@ -127,6 +128,8 @@ class EdgeFactory(TimeseriesFactory): data type. interval: {'day', 'hour', 'minute', 'second', 'tenhertz'} data interval. + add_empty_channels + if True, returns channels without data as empty traces Returns ------- @@ -158,8 +161,16 @@ class EdgeFactory(TimeseriesFactory): timeseries = obspy.core.Stream() for channel in channels: data = self._get_timeseries( - starttime, endtime, observatory, channel, type, interval + starttime, + endtime, + observatory, + channel, + type, + interval, + add_empty_channels, ) + if len(data) == 0: + continue timeseries += data finally: # restore stdout @@ -277,187 +288,16 @@ class EdgeFactory(TimeseriesFactory): trace.data = numpy.ma.masked_invalid(trace.data) return stream - def _get_edge_channel(self, observatory, channel, type, interval): - """get edge channel. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F, X, Y, G} or - any appropriate edge channel, ie MSD, MGD, HGD. - type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {minute, second} - - Returns - ------- - edge_channel - {MVH, MVE, MVD, MGD etc} - """ - edge_interval_code = self._get_interval_code(interval) - edge_channel = None - - # If form is chan.loc, return chan (left) portion. - # Allows specific chan/loc selection. - if channel.find(".") >= 0: - tmplist = channel.split(".") - return tmplist[0].strip() - - if channel == "D": - edge_channel = edge_interval_code + "VD" - elif channel == "E": - edge_channel = edge_interval_code + "VE" - elif channel == "F": - edge_channel = edge_interval_code + "SF" - elif channel == "H": - edge_channel = edge_interval_code + "VH" - elif channel == "Z": - edge_channel = edge_interval_code + "VZ" - elif channel == "G": - edge_channel = edge_interval_code + "SG" - elif channel == "X": - edge_channel = edge_interval_code + "VX" - elif channel == "Y": - edge_channel = edge_interval_code + "VY" - elif channel == "E-E": - edge_channel = edge_interval_code + "QE" - elif channel == "E-N": - edge_channel = edge_interval_code + "QN" - elif channel == "DIST": - edge_channel = edge_interval_code + "DT" - elif channel == "DST": - edge_channel = edge_interval_code + "GD" - elif channel == "SQ": - edge_channel = edge_interval_code + "SQ" - elif channel == "SV": - edge_channel = edge_interval_code + "SV" - else: - edge_channel = channel - return edge_channel - - def _get_edge_location(self, observatory, channel, type, interval): - """get edge location. - - The edge location code is currently determined by the type - passed in. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F} - type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {minute, second} - - Returns - ------- - location - returns an edge location code - """ - location = None - - # If form is chan.loc, return loc (right) portion - # Allows specific chan/loc selection. - if channel.find(".") >= 0: - tmplist = channel.split(".") - return tmplist[1].strip() - - if self.locationCode is not None: - location = self.locationCode - else: - if type == "variation" or type == "reported": - location = "R0" - elif type == "adjusted" or type == "provisional": - location = "A0" - elif type == "quasi-definitive": - location = "Q0" - elif type == "definitive": - location = "D0" - return location - - def _get_edge_network(self, observatory, channel, type, interval): - """get edge network code. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F} - type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {minute, second} - - Returns - ------- - network - always NT - """ - return "NT" - - def _get_edge_station(self, observatory, channel, type, interval): - """get edge station. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F} - type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {minute, second} - - Returns - ------- - station - the observatory is returned as the station - """ - return observatory - - def _get_interval_code(self, interval): - """get edge interval code. - - Converts the metadata interval string, into an edge single character - edge code. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F} - type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {minute, second} - - Returns - ------- - interval type - """ - interval_code = None - if interval == "day": - interval_code = "D" - elif interval == "hour": - interval_code = "H" - elif interval == "minute": - interval_code = "M" - elif interval == "second": - interval_code = "S" - else: - raise TimeseriesFactoryException('Unexpected interval "%s"' % interval) - return interval_code - - def _get_timeseries(self, starttime, endtime, observatory, channel, type, interval): + def _get_timeseries( + self, + starttime, + endtime, + observatory, + channel, + type, + interval, + add_empty_channels: bool = True, + ): """get timeseries data for a single channel. Parameters @@ -474,19 +314,29 @@ class EdgeFactory(TimeseriesFactory): data type {definitive, quasi-definitive, variation} interval : str interval length {minute, second} + add_empty_channels + if True, returns channels without data as empty traces Returns ------- obspy.core.trace timeseries trace of the requested channel data """ - station = self._get_edge_station(observatory, channel, type, interval) - location = self._get_edge_location(observatory, channel, type, interval) - network = self._get_edge_network(observatory, channel, type, interval) - edge_channel = self._get_edge_channel(observatory, channel, type, interval) + sncl = LegacySNCL.get_sncl( + station=observatory, + data_type=type, + interval=interval, + element=channel, + location=self.locationCode, + ) try: data = self.client.get_waveforms( - network, station, location, edge_channel, starttime, endtime + sncl.network, + sncl.station, + sncl.location, + sncl.channel, + starttime, + endtime, ) except TypeError: # get_waveforms() fails if no data is returned from Edge @@ -496,17 +346,16 @@ class EdgeFactory(TimeseriesFactory): for trace in data: trace.data = trace.data.astype("i4") data.merge() - if data.count() == 0: - data += TimeseriesUtility.create_empty_trace( - starttime, - endtime, - observatory, - channel, - type, - interval, - network, - station, - location, + if data.count() == 0 and add_empty_channels: + data += self._get_empty_trace( + starttime=starttime, + endtime=endtime, + observatory=observatory, + channel=channel, + data_type=type, + interval=interval, + network=sncl.network, + location=sncl.location, ) self._set_metadata(data, observatory, channel, type, interval) return data @@ -574,10 +423,13 @@ class EdgeFactory(TimeseriesFactory): ----- RawInputClient seems to only work when sockets are """ - station = self._get_edge_station(observatory, channel, type, interval) - location = self._get_edge_location(observatory, channel, type, interval) - network = self._get_edge_network(observatory, channel, type, interval) - edge_channel = self._get_edge_channel(observatory, channel, type, interval) + sncl = LegacySNCL.get_sncl( + station=observatory, + data_type=type, + interval=interval, + element=channel, + location=self.locationCode, + ) now = obspy.core.UTCDateTime(datetime.utcnow()) if ((now - endtime) > 864000) and (self.cwbport > 0): @@ -588,7 +440,13 @@ class EdgeFactory(TimeseriesFactory): port = self.write_port ric = RawInputClient( - self.tag, host, port, station, edge_channel, location, network + self.tag, + host, + port, + sncl.station, + sncl.channel, + sncl.location, + sncl.network, ) stream = self._convert_stream_to_masked(timeseries=timeseries, channel=channel) @@ -610,20 +468,26 @@ class EdgeFactory(TimeseriesFactory): ric.forceout() ric.close() - def _set_metadata(self, stream, observatory, channel, type, interval): + def _set_metadata( + self, + stream: obspy.core.Stream, + observatory: str, + channel: str, + type: str, + interval: str, + ): """set metadata for a given stream/channel Parameters ---------- - observatory : str + observatory observatory code - channel : str + channel edge channel code {MVH, MVE, MVD, ...} - type : str + type data type {definitive, quasi-definitive, variation} - interval : str + interval interval length {minute, second} """ - for trace in stream: self.observatoryMetadata.set_metadata( trace.stats, observatory, channel, type, interval diff --git a/geomagio/edge/LegacySNCL.py b/geomagio/edge/LegacySNCL.py new file mode 100644 index 0000000000000000000000000000000000000000..f80b438a602ce15ec0dc552ff10c7345701c0222 --- /dev/null +++ b/geomagio/edge/LegacySNCL.py @@ -0,0 +1,139 @@ +from typing import Optional + +from .SNCL import SNCL, _get_location_start + +ELEMENT_CONVERSIONS = { + # e-field + "E-E": "QE", + "E-N": "QN", + # derived indicies + "SQ": "SQ", + "SV": "SV", + "DIST": "DT", + "DST": "GD", +} +CHANNEL_CONVERSIONS = { + ELEMENT_CONVERSIONS[key]: key for key in ELEMENT_CONVERSIONS.keys() +} + + +class LegacySNCL(SNCL): + @classmethod + def get_sncl( + cls, + data_type: str, + element: str, + interval: str, + station: str, + network: str = "NT", + location: Optional[str] = None, + ) -> "LegacySNCL": + return LegacySNCL( + station=station, + network=network, + channel=get_channel(element=element, interval=interval), + location=location or get_location(element=element, data_type=data_type), + ) + + @property + def element(self) -> str: + return _check_predefined_element(channel=self.channel) or _get_element( + channel=self.channel, location=self.location + ) + + @property + def interval(self) -> str: + channel_start = self.channel[0] + if channel_start == "S": + return "second" + elif channel_start == "M": + return "minute" + elif channel_start == "H": + return "hour" + elif channel_start == "D": + return "day" + raise ValueError(f"Unexcepted interval code: {channel_start}") + + +def get_channel(element: str, interval: str) -> str: + return _check_predefined_channel(element=element, interval=interval) or ( + _get_channel_start(interval=interval) + _get_channel_end(element=element) + ) + + +def get_location(element: str, data_type: str) -> str: + if len(data_type) == 2: + return data_type + return _get_location_start(data_type=data_type) + _get_location_end(element=element) + + +def _check_predefined_element(channel: str) -> Optional[str]: + channel_end = channel[1:] + if channel_end in CHANNEL_CONVERSIONS: + return CHANNEL_CONVERSIONS[channel_end] + return None + + +def _get_channel_start(interval: str) -> str: + if interval == "second": + return "S" + elif interval == "minute": + return "M" + elif interval == "hour": + return "H" + elif interval == "day": + return "D" + raise ValueError(f" Unexcepted interval: {interval}") + + +def _get_element(channel: str, location: str) -> str: + """Translates channel/location to element""" + element_start = channel[2] + channel = channel + channel_middle = channel[1] + location_end = location[1] + if channel_middle in ["Q", "E"]: + element_end = "_Volt" + elif channel_middle == "Y": + element_end = "_Bin" + elif channel_middle == "K": + element_end = "_Temp" + elif location_end == "1": + element_end = "_Sat" + else: + element_end = "" + return element_start + element_end + + +def _check_predefined_channel(element: str, interval: str) -> Optional[str]: + if element in ELEMENT_CONVERSIONS: + return _get_channel_start(interval=interval) + ELEMENT_CONVERSIONS[element] + elif len(element) == 3: + return element + # chan.loc format + elif "." in element: + channel = element.split(".")[0] + return channel.strip() + else: + return None + + +def _get_channel_end(element: str) -> str: + channel_middle = "V" + if "_Volt" in element: + channel_middle = "E" + elif "_Bin" in element: + channel_middle = "Y" + elif "_Temp" in element: + channel_middle = "K" + elif element in ["F", "G"]: + channel_middle = "S" + channel_end = element.split("_")[0] + return channel_middle + channel_end + + +def _get_location_end(element: str) -> str: + """Translates element suffix to end of location code""" + if "_Sat" in element: + return "1" + return "0" diff --git a/geomagio/edge/MiniSeedFactory.py b/geomagio/edge/MiniSeedFactory.py index 99c3b9eec31e63a125131b6c0f65933220b13822..94103caee23b037e93eb6d0dfc967a92e8951721 100644 --- a/geomagio/edge/MiniSeedFactory.py +++ b/geomagio/edge/MiniSeedFactory.py @@ -23,6 +23,7 @@ from ..TimeseriesFactory import TimeseriesFactory from ..TimeseriesFactoryException import TimeseriesFactoryException from ..ObservatoryMetadata import ObservatoryMetadata from .MiniSeedInputClient import MiniSeedInputClient +from .SNCL import SNCL class MiniSeedFactory(TimeseriesFactory): @@ -67,7 +68,7 @@ class MiniSeedFactory(TimeseriesFactory): self, host="cwbpub.cr.usgs.gov", port=2061, - write_port=7981, + write_port=7974, observatory=None, channels=None, type=None, @@ -96,6 +97,7 @@ class MiniSeedFactory(TimeseriesFactory): channels=None, type=None, interval=None, + add_empty_channels: bool = True, ): """Get timeseries data @@ -113,6 +115,8 @@ class MiniSeedFactory(TimeseriesFactory): data type. interval: {'day', 'hour', 'minute', 'second', 'tenhertz'} data interval. + add_empty_channels + if True, returns channels without data as empty traces Returns ------- @@ -149,8 +153,16 @@ class MiniSeedFactory(TimeseriesFactory): ) else: data = self._get_timeseries( - starttime, endtime, observatory, channel, type, interval + starttime, + endtime, + observatory, + channel, + type, + interval, + add_empty_channels, ) + if len(data) == 0: + continue timeseries += data finally: # restore stdout @@ -295,213 +307,16 @@ class MiniSeedFactory(TimeseriesFactory): trace.data = numpy.ma.masked_invalid(trace.data) return stream - def _get_edge_channel(self, observatory, channel, type, interval): - """get edge channel. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F, X, Y, G} or - any appropriate edge channel, ie MSD, MGD, HGD. - type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {'day', 'hour', 'minute', 'second', 'tenhertz'} - - Returns - ------- - edge_channel - {MVH, MVE, MVD, MGD etc} - """ - edge_interval_code = self._get_interval_code(interval) - edge_channel = None - - # If form is chan.loc, return chan (left) portion. - # Allows specific chan/loc selection. - if channel.find(".") >= 0: - tmplist = channel.split(".") - return tmplist[0].strip() - - # see if channel name uses _ for ELEMENT_SUFFIX - element = None - suffix = None - if channel.find("_") >= 0: - element, suffix = channel.split("_") - - # 10Hz should be bin/volt - if interval == "tenhertz": - middle = None - if suffix == "Bin": - middle = "Y" - elif suffix == "Volt": - middle = "E" - elif suffix is not None: - raise TimeseriesFactoryException( - 'bad channel suffix "%s", wanted "Bin" or "Volt"' % suffix - ) - # check for expected channels - if element in ("U", "V", "W") and middle is not None: - return edge_interval_code + middle + element - else: - # unknown, assume advanced user - return channel - - if suffix is not None: - if suffix == "Dist" or suffix == "SQ" or suffix == "SV" or suffix == "DT": - # these suffixes modify location code, but use element channel - channel = element - else: - raise TimeseriesFactoryException( - 'bad channel suffix "%s", wanted "Dist", "SQ", or "SV"' % suffix - ) - if channel in ("D", "F", "G", "H", "U", "V", "W", "X", "Y", "Z"): - # normal elements - edge_channel = edge_interval_code + "F" + channel - elif channel == "E-E": - edge_channel = edge_interval_code + "QE" - elif channel == "E-N": - edge_channel = edge_interval_code + "QN" - elif channel == "Dst4": - edge_channel = edge_interval_code + "X4" - elif channel == "Dst3": - edge_channel = edge_interval_code + "X3" - else: - edge_channel = channel - return edge_channel - - def _get_edge_location(self, observatory, channel, data_type, interval): - """get edge location. - - The edge location code is currently determined by the type - passed in. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F} - data_type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {'day', 'hour', 'minute', 'second', 'tenhertz'} - - Returns - ------- - location - returns an edge location code - """ - # If form is chan.loc, return loc (right) portion - # Allows specific chan/loc selection. - if channel.find(".") >= 0: - tmplist = channel.split(".") - return tmplist[1].strip() - # factory override - if self.locationCode is not None: - return self.locationCode - # determine prefix - location_prefix = "R" - if data_type == "variation" or data_type == "reported": - location_prefix = "R" - elif data_type == "adjusted" or data_type == "provisional": - location_prefix = "A" - elif data_type == "quasi-definitive": - location_prefix = "Q" - elif data_type == "definitive": - location_prefix = "D" - # determine suffix - location_suffix = "0" - if channel.find("_") >= 0: - _, suffix = channel.split("_") - if suffix == "Dist": - location_suffix = "D" - elif suffix == "SQ": - location_suffix = "Q" - elif suffix == "SV": - location_suffix = "V" - elif suffix == "DT": - location_suffix = "R" - elif suffix not in ("Bin", "Volt"): - raise TimeseriesFactoryException( - 'bad channel suffix "%s", wanted "Dist", "SQ", or "SV"' % suffix - ) - return location_prefix + location_suffix - - def _get_edge_network(self, observatory, channel, type, interval): - """get edge network code. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F} - type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {'day', 'hour', 'minute', 'second', 'tenhertz'} - - Returns - ------- - network - always NT - """ - return "NT" - - def _get_edge_station(self, observatory, channel, type, interval): - """get edge station. - - Parameters - ---------- - observatory : str - observatory code - channel : str - single character channel {H, E, D, Z, F} - type : str - data type {definitive, quasi-definitive, variation} - interval : str - interval length {'day', 'hour', 'minute', 'second', 'tenhertz'} - - Returns - ------- - station - the observatory is returned as the station - """ - return observatory - - def _get_interval_code(self, interval): - """get edge interval code. - - Converts the metadata interval string, into an edge single character - edge code. - - Parameters - ---------- - interval : str - interval length {'day', 'hour', 'minute', 'second', 'tenhertz'} - - Returns - ------- - interval type - """ - interval_code = None - if interval == "day": - interval_code = "P" - elif interval == "hour": - interval_code = "R" - elif interval == "minute": - interval_code = "U" - elif interval == "second": - interval_code = "L" - elif interval == "tenhertz": - interval_code = "B" - else: - raise TimeseriesFactoryException('Unexpected interval "%s"' % interval) - return interval_code - - def _get_timeseries(self, starttime, endtime, observatory, channel, type, interval): + def _get_timeseries( + self, + starttime, + endtime, + observatory, + channel, + type, + interval, + add_empty_channels: bool = True, + ): """get timeseries data for a single channel. Parameters @@ -518,31 +333,35 @@ class MiniSeedFactory(TimeseriesFactory): data type {definitive, quasi-definitive, variation} interval : str interval length {'day', 'hour', 'minute', 'second', 'tenhertz'} + add_empty_channels + if True, returns channels without data as empty traces Returns ------- obspy.core.trace timeseries trace of the requested channel data """ - station = self._get_edge_station(observatory, channel, type, interval) - location = self._get_edge_location(observatory, channel, type, interval) - network = self._get_edge_network(observatory, channel, type, interval) - edge_channel = self._get_edge_channel(observatory, channel, type, interval) + sncl = SNCL.get_sncl( + station=observatory, + data_type=type, + interval=interval, + element=channel, + location=self.locationCode, + ) data = self.client.get_waveforms( - network, station, location, edge_channel, starttime, endtime + sncl.network, sncl.station, sncl.location, sncl.channel, starttime, endtime ) data.merge() - if data.count() == 0: - data += TimeseriesUtility.create_empty_trace( - starttime, - endtime, - observatory, - channel, - type, - interval, - network, - station, - location, + if data.count() == 0 and add_empty_channels: + data += self._get_empty_trace( + starttime=starttime, + endtime=endtime, + observatory=observatory, + channel=channel, + data_type=type, + interval=interval, + network=sncl.network, + location=sncl.location, ) self._set_metadata(data, observatory, channel, type, interval) return data @@ -666,32 +485,41 @@ class MiniSeedFactory(TimeseriesFactory): to_write = to_write.split() to_write = TimeseriesUtility.unmask_stream(to_write) # relabel channels from internal to edge conventions - station = self._get_edge_station(observatory, channel, type, interval) - location = self._get_edge_location(observatory, channel, type, interval) - network = self._get_edge_network(observatory, channel, type, interval) - edge_channel = self._get_edge_channel(observatory, channel, type, interval) + sncl = SNCL.get_sncl( + station=observatory, + data_type=type, + interval=interval, + element=channel, + location=self.locationCode, + ) for trace in to_write: - trace.stats.station = station - trace.stats.location = location - trace.stats.network = network - trace.stats.channel = edge_channel + trace.stats.station = sncl.station + trace.stats.location = sncl.location + trace.stats.network = sncl.network + trace.stats.channel = sncl.channel # finally, send to edge self.write_client.send(to_write) - def _set_metadata(self, stream, observatory, channel, type, interval): + def _set_metadata( + self, + stream: obspy.core.Stream, + observatory: str, + channel: str, + type: str, + interval: str, + ): """set metadata for a given stream/channel Parameters ---------- - observatory : str + observatory observatory code - channel : str + channel edge channel code {MVH, MVE, MVD, ...} - type : str + type data type {definitive, quasi-definitive, variation} - interval : str - interval length {'day', 'hour', 'minute', 'second', 'tenhertz'} + interval + interval length {minute, second} """ - for trace in stream: self.observatoryMetadata.set_metadata( trace.stats, observatory, channel, type, interval diff --git a/geomagio/edge/MiniSeedInputClient.py b/geomagio/edge/MiniSeedInputClient.py index 906c37e32e2e4bd821c4696234c2dbce87b31394..a25d31ce89741d8483aa9a1bf55220b1d2c3358e 100644 --- a/geomagio/edge/MiniSeedInputClient.py +++ b/geomagio/edge/MiniSeedInputClient.py @@ -2,6 +2,11 @@ from __future__ import absolute_import, print_function import io import socket import sys +from typing import BinaryIO + +from obspy.core import Stream + +from ..TimeseriesUtility import encode_stream, split_stream class MiniSeedInputClient(object): @@ -16,11 +21,14 @@ class MiniSeedInputClient(object): MiniSeedServer hostname port: int MiniSeedServer port + encoding: str + Floating point precision for output data """ - def __init__(self, host, port=2061): + def __init__(self, host, port=2061, encoding="float32"): self.host = host self.port = port + self.encoding = encoding self.socket = None def close(self): @@ -48,7 +56,7 @@ class MiniSeedInputClient(object): attempts += 1 try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(self.host, self.port) + s.connect((self.host, self.port)) break except socket.error as e: if attempts >= max_attempts: @@ -63,14 +71,45 @@ class MiniSeedInputClient(object): Parameters ---------- - stream: obspy.core.Stream + stream: Stream stream with trace(s) to send. """ # connect if needed if self.socket is None: self.connect() - # convert stream to miniseed buf = io.BytesIO() - stream.write(buf, format="MSEED") + self._format_miniseed(stream=stream, buf=buf) # send data - self.socket.sendall(buf) + self.socket.sendall(buf.getvalue()) + + def _format_miniseed(self, stream: Stream, buf: BinaryIO) -> io.BytesIO: + """Processes and writes stream to buffer as miniseed + + Parameters: + ----------- + stream: Stream + stream with data to write + buf: BinaryIO + memory buffer for output data + """ + processed = self._pre_process(stream=stream) + for trace in processed: + # convert stream to miniseed + trace.write(buf, format="MSEED", reclen=512) + + def _pre_process(self, stream: Stream) -> Stream: + """Encodes and splits streams at daily intervals + + Paramters: + ---------- + stream: Stream + stream of input data + + Returns: + -------- + stream: Stream + list of encoded trace split at daily intervals + """ + stream = encode_stream(stream=stream, encoding=self.encoding) + stream = split_stream(stream=stream, size=86400) + return stream diff --git a/geomagio/edge/RawInputClient.py b/geomagio/edge/RawInputClient.py index 20c29acfd8845cd53ec7f2259e0f9e95f3587dd5..e6483c7fa5859990f23cbbf84811a937b172ef76 100644 --- a/geomagio/edge/RawInputClient.py +++ b/geomagio/edge/RawInputClient.py @@ -6,6 +6,8 @@ import struct import sys from datetime import datetime from ..TimeseriesFactoryException import TimeseriesFactoryException +from ..TimeseriesUtility import round_usecs +import logging from obspy.core import UTCDateTime from time import sleep @@ -160,7 +162,7 @@ class RawInputClient: PARAMETERS ---------- - interval: {'daily', 'hourly', 'minute', 'second'} + interval: {'day', 'hour', 'minute', 'second'} data interval. trace: obspy.core.trace @@ -181,11 +183,11 @@ class RawInputClient: nsamp = DAYMINUTES timeoffset = 60 samplerate = 1.0 / 60 - elif interval == "hourly": + elif interval == "hour": nsamp = MAXINPUTSIZE timeoffset = 3600 samplerate = 1.0 / 3600 - elif interval == "daily": + elif interval == "day": nsamp = MAXINPUTSIZE timeoffset = 86400 samplerate = 1.0 / 86400 @@ -414,7 +416,7 @@ class RawInputClient: """ PARAMETERS ---------- - time: UTCDateTime + input_time: UTCDateTime time of the first sample RETURNS @@ -425,11 +427,20 @@ class RawInputClient: secs: int usecs: int """ + + # check for presence of residual microseconds + if (time.microsecond / 1000).is_integer() == False: + # create warning message when rounding is necessary + logging.warning( + "residual microsecond values encountered, rounding to nearest millisecond" + ) + # round residual microsecond values + time = round_usecs(time) + yr = time.year doy = time.datetime.timetuple().tm_yday secs = time.hour * 3600 + time.minute * 60 + time.second usecs = time.microsecond - return (yr, doy, secs, usecs) def _open_socket(self): diff --git a/geomagio/edge/SNCL.py b/geomagio/edge/SNCL.py new file mode 100644 index 0000000000000000000000000000000000000000..e137b2b163e5f5b44688774b2dd9b62a2c000468 --- /dev/null +++ b/geomagio/edge/SNCL.py @@ -0,0 +1,204 @@ +from typing import Dict, Optional + +from pydantic import BaseModel + +ELEMENT_CONVERSIONS = { + # e-field + "E-E": "QE", + "E-N": "QN", + # derived indicies + "Dst3": "X3", + "Dst4": "X4", +} + +CHANNEL_CONVERSIONS = { + ELEMENT_CONVERSIONS[key]: key for key in ELEMENT_CONVERSIONS.keys() +} + + +class SNCL(BaseModel): + station: str + network: str = "NT" + channel: str + location: str + + @classmethod + def get_sncl( + cls, + data_type: str, + element: str, + interval: str, + station: str, + network: str = "NT", + location: Optional[str] = None, + ) -> "SNCL": + return SNCL( + station=station, + network=network, + channel=get_channel( + element=element, interval=interval, data_type=data_type + ), + location=location or get_location(element=element, data_type=data_type), + ) + + def parse_sncl(self) -> Dict: + return { + "station": self.station, + "network": self.network, + "data_type": self.data_type, + "element": self.element, + "interval": self.interval, + } + + @property + def data_type(self) -> str: + """Translates beginning of location code to data type""" + location_start = self.location[0] + if location_start == "R": + return "variation" + elif location_start == "A": + return "adjusted" + elif location_start == "Q": + return "quasi-definitive" + elif location_start == "D": + return "definitive" + raise ValueError(f"Unexpected location start: {location_start}") + + @property + def element(self) -> str: + return _check_predefined_element(channel=self.channel) or _get_element( + channel=self.channel, location=self.location + ) + + @property + def interval(self) -> str: + """Translates beginning of channel to interval""" + channel_start = self.channel[0] + if channel_start == "B": + return "tenhertz" + elif channel_start == "L": + return "second" + elif channel_start == "U": + return "minute" + elif channel_start == "R": + return "hour" + elif channel_start == "P": + return "day" + raise ValueError(f"Unexcepted interval code: {channel_start}") + + +def get_channel(element: str, interval: str, data_type: str) -> str: + return _check_predefined_channel(element=element, interval=interval) or ( + _get_channel_start(interval=interval) + + _get_channel_end(element=element, data_type=data_type) + ) + + +def get_location(element: str, data_type: str) -> str: + if len(data_type) == 2: + return data_type + return _get_location_start(data_type=data_type) + _get_location_end(element=element) + + +def _check_predefined_element(channel: str) -> Optional[str]: + channel_end = channel[1:] + if channel_end in CHANNEL_CONVERSIONS: + return CHANNEL_CONVERSIONS[channel_end] + return None + + +def _get_channel_start(interval: str) -> str: + if interval == "tenhertz": + return "B" + if interval == "second": + return "L" + elif interval == "minute": + return "U" + elif interval == "hour": + return "R" + elif interval == "day": + return "P" + raise ValueError(f" Unexcepted interval: {interval}") + + +def _get_element(channel: str, location: str) -> str: + """Translates channel/location to element""" + element_start = channel[2] + channel = channel + channel_middle = channel[1] + location_end = location[1] + if channel_middle == "E": + element_end = "_Volt" + elif channel_middle == "Y": + element_end = "_Bin" + elif channel_middle == "K": + element_end = "_Temp" + elif location_end == "1": + element_end = "_Sat" + elif location_end == "D": + element_end = "_Dist" + elif location_end == "Q": + element_end = "_SQ" + elif location_end == "V": + element_end = "_SV" + else: + element_end = "" + return element_start + element_end + + +def _check_predefined_channel(element: str, interval: str) -> Optional[str]: + if element in ELEMENT_CONVERSIONS: + return _get_channel_start(interval=interval) + ELEMENT_CONVERSIONS[element] + elif len(element) == 3: + return element + # chan.loc format + elif "." in element: + channel = element.split(".")[0] + return channel.strip() + else: + return None + + +def _get_channel_end(element: str, data_type: str) -> str: + channel_middle = "F" + if "_Volt" in element: + channel_middle = "E" + elif "_Bin" in element: + channel_middle = "Y" + elif "_Temp" in element: + channel_middle = "K" + channel_end = element.split("_")[0] + if data_type == "variation": + if channel_end == "H": + channel_end = "U" + elif channel_end == "E": + channel_end = "V" + elif channel_end == "Z": + channel_end = "W" + return channel_middle + channel_end + + +def _get_location_start(data_type: str) -> str: + """Translates data type to beginning of location code""" + if data_type == "variation": + return "R" + elif data_type == "adjusted": + return "A" + elif data_type == "quasi-definitive": + return "Q" + elif data_type == "definitive": + return "D" + raise ValueError(f"Unexpected data type: {data_type}") + + +def _get_location_end(element: str) -> str: + """Translates element suffix to end of location code""" + if "_Sat" in element: + return "1" + if "_Dist" in element: + return "D" + if "_SQ" in element: + return "Q" + if "_SV" in element: + return "V" + return "0" diff --git a/geomagio/edge/__init__.py b/geomagio/edge/__init__.py index 93888daa3356a5d4f8b5d3fed1034772106e6f13..45b3d49c3ac29b3dd213d0f83cf1017ea5d95549 100644 --- a/geomagio/edge/__init__.py +++ b/geomagio/edge/__init__.py @@ -5,6 +5,17 @@ from __future__ import absolute_import from .EdgeFactory import EdgeFactory from .LocationCode import LocationCode from .MiniSeedFactory import MiniSeedFactory +from .MiniSeedInputClient import MiniSeedInputClient from .RawInputClient import RawInputClient +from .SNCL import SNCL +from .LegacySNCL import LegacySNCL -__all__ = ["EdgeFactory", "LocationCode", "MiniSeedFactory", "RawInputClient"] +__all__ = [ + "EdgeFactory", + "LocationCode", + "MiniSeedFactory", + "MiniSeedInputClient", + "RawInputClient", + "LegacySNCL", + "SNCL", +] diff --git a/geomagio/edge/sncl.py b/geomagio/edge/sncl.py deleted file mode 100644 index 5bfa4ff2ee186cd832a70623fd6b8604ceee3aea..0000000000000000000000000000000000000000 --- a/geomagio/edge/sncl.py +++ /dev/null @@ -1,258 +0,0 @@ -"""SNCL utilities. - -Station -Network -Channel -Location -""" - -# components that map directly to channel suffixes -CHANNEL_FROM_COMPONENT = { - # e-field - "E-E": "QY", - "E-N": "QX", - "E-U": "QU", - "E-V": "QV", - # derived indicies - "AE": "XA", - "DST3": "X3", - "DST": "X4", - "K": "XK", -} -# reverse lookup of component from channel -COMPONENT_FROM_CHANNEL = dict((v, k) for (k, v) in CHANNEL_FROM_COMPONENT.iteritems()) - - -class SNCLException(Exception): - pass - - -def get_scnl( - observatory, - component=None, - channel=None, - data_type="variation", - interval="second", - location=None, - network="NT", -): - """Generate a SNCL code from data attributes. - - Parameters - ---------- - observatory : str - observatory code. - component : str - geomag component name. - channel : str - default None. - use a specific channel code, instead of generating. - data_type : str - default 'variation' - 'variation', 'adjusted', 'quasi-definitive', or 'definitive'. - interval: str|float - default 'second' - 'tenhertz', 'second', 'minute', 'hour', 'day', - or equivalent interval in seconds - location : str - default None - use a specific location code, instead of generating. - network : str - default 'NT' - network `observatory` is a part of. - - Raises - ------ - SNCLException : when unable to generate a SNCL - - Returns - ------- - dict : dictionary containing the following keys - 'station' : observatory code - 'network' : network code - 'channel' : channel code - 'location' : location code - """ - # use explicit channel/location if specified - channel = channel or __get_channel(component, interval) - location = location or __get_location(component, data_type) - return { - "station": observatory, - "network": network, - "channel": channel, - "location": location, - } - - -def parse_sncl(sncl): - """Parse a SNCL code into data attributes. - - Parameters - ---------- - sncl : dict - dictionary object with the following keys - 'station' : observatory code - 'network' : network code - 'channel' : channel code - 'location' : location code - - Raises - ------ - SNCLException : when unable to parse a SNCL - - Returns - ------- - dict : dictionary containing the following keys - 'observatory' : observatory code - 'network' : network code - 'component' : geomag component name - 'data_type' : geomag data type (e.g. 'variation') - 'interval' : data interval in seconds (e.g. 1) - """ - network = sncl["network"] - station = sncl["station"] - channel = sncl["channel"] - location = sncl["location"] - return { - "observatory": station, - "network": network, - "component": __parse_component(channel, location), - "data_type": __parse_data_type(location), - "interval": __parse_interval(channel), - } - - -def __get_channel(component, interval): - channel_start = __get_channel_start(interval) - # check for direct component mappings - if component in CHANNEL_FROM_COMPONENT: - channel_end = CHANNEL_FROM_COMPONENT[component] - else: - channel_end = __get_channel_end(component) - return channel_start + channel_end - - -def __get_channel_start(interval): - if interval == "tenhertz" or interval == 0.1: - return "B" - if interval == "second" or interval == 1: - return "L" - if interval == "minute" or interval == 60: - return "U" - if interval == "hour" or interval == 3600: - return "R" - if interval == "day" or interval == 86400: - return "P" - raise SNCLException("Unexpected interval {}".format(interval)) - - -def __get_channel_end(component): - # default to engineering units - channel_middle = "F" - # check for suffix that may override - component_parts = component.split("-") - channel_end = component_parts[0] - if len(component_parts) > 1: - component_suffix = component_parts[1] - if component_suffix == "-Bin": - channel_middle = "Y" - elif component_suffix == "-Temp": - channel_middle = "K" - elif component_suffix == "-Volt": - channel_middle = "E" - else: - raise SNCLException("Unexpected component {}".format(component)) - return channel_middle + channel_end - - -def __get_location(component, data_type): - location_start = __get_location_start(data_type) - location_end = __get_location_end(component) - return location_start + location_end - - -def __get_location_start(data_type): - if data_type == "variation": - return "R" - elif data_type == "adjusted": - return "A" - elif data_type == "quasi-definitive": - return "Q" - elif data_type == "definitive": - return "D" - raise SNCLException("Unexpected data type {}".format(data_type)) - - -def __get_location_end(component): - if component.endswith("-Sat"): - return "1" - if component.endswith("-Dist"): - return "D" - if component.endswith("-SQ"): - return "Q" - if component.endswith("-SV"): - return "V" - return "0" - - -def __parse_component(channel, location): - channel_end = channel[1:] - if channel_end in COMPONENT_FROM_CHANNEL: - return COMPONENT_FROM_CHANNEL[channel_end] - channel_middle = channel[1] - component = channel[2] - component_end = "" - if channel_middle == "E": - component_end = "-Volt" - elif channel_middle == "K": - component_end = "-Temp" - elif channel_middle == "Y": - component_end = "-Bin" - elif channel_middle == "F": - component_end = __parse_component_end(location) - else: - raise SNCLException("Unexpected channel middle {}".format(channel)) - return component + component_end - - -def __parse_component_end(location): - location_end = location[1] - if location_end == "0": - return "" - if location_end == "1": - return "-Sat" - if location_end == "D": - return "-Dist" - if location_end == "Q": - return "-SQ" - if location_end == "V": - return "-SV" - raise SNCLException("Unexpected location end {}".format(location_end)) - - -def __parse_data_type(location): - location_start = location[0] - if location_start == "R": - return "variation" - if location_start == "A": - return "adjusted" - if location_start == "Q": - return "quasi-definitive" - if location_start == "D": - return "definitive" - raise SNCLException("Unexpected location start {}".format(location_start)) - - -def __parse_interval(channel): - channel_start = channel[0] - if channel_start == "B": - return 0.1 - if channel_start == "L": - return 1 - if channel_start == "U": - return 60 - if channel_start == "R": - return 3600 - if channel_start == "P": - return 86400 - raise SNCLException("Unexpected channel {}".format(channel)) diff --git a/geomagio/iaga2002/IAGA2002Writer.py b/geomagio/iaga2002/IAGA2002Writer.py index 2e9bbdfdd35cfc15c92273d25265b9bb4151c660..bd215ef98deae916d2cbdf83610b25b4d5d1adc2 100644 --- a/geomagio/iaga2002/IAGA2002Writer.py +++ b/geomagio/iaga2002/IAGA2002Writer.py @@ -117,7 +117,11 @@ class IAGA2002Writer(object): if ( "declination_base" in stats and stats.declination_base is not None - and (stats.data_type == "variation" or stats.data_type == "reported") + and ( + stats.data_type == "variation" + or stats.data_type == "reported" + or stats.data_type[0] == "R" + ) ): comments.append( "DECBAS {:<8d}" diff --git a/geomagio/iaga2002/StreamIAGA2002Factory.py b/geomagio/iaga2002/StreamIAGA2002Factory.py index 62f6f871c99b61aad9a4ce903f63a99cdbce0d66..eb1625f62c57b309c0f5063e520cb09a437b44d6 100644 --- a/geomagio/iaga2002/StreamIAGA2002Factory.py +++ b/geomagio/iaga2002/StreamIAGA2002Factory.py @@ -31,6 +31,7 @@ class StreamIAGA2002Factory(IAGA2002Factory): channels=None, type=None, interval=None, + add_empty_channels: bool = True, ): """Implements get_timeseries diff --git a/geomagio/imfv122/StreamIMFV122Factory.py b/geomagio/imfv122/StreamIMFV122Factory.py index b868d8dd20318e1587403dda00b6f751876ad1cd..4a9575fc9dba04c986b54e76014477c0e6a0c7ad 100644 --- a/geomagio/imfv122/StreamIMFV122Factory.py +++ b/geomagio/imfv122/StreamIMFV122Factory.py @@ -31,6 +31,7 @@ class StreamIMFV122Factory(IMFV122Factory): channels=None, type=None, interval=None, + add_empty_channels: bool = True, ): """Implements get_timeseries diff --git a/geomagio/imfv283/GOESIMFV283Factory.py b/geomagio/imfv283/GOESIMFV283Factory.py index 29bda1d9b9f912b481ba3b0f8ad9cc0bc5569d4f..195e8710505ea21bd631ec8fdacbb252dfd63947 100644 --- a/geomagio/imfv283/GOESIMFV283Factory.py +++ b/geomagio/imfv283/GOESIMFV283Factory.py @@ -67,6 +67,7 @@ class GOESIMFV283Factory(IMFV283Factory): channels=None, type=None, interval=None, + add_empty_channels: bool = True, ): """Implements get_timeseries diff --git a/geomagio/imfv283/IMFV283Parser.py b/geomagio/imfv283/IMFV283Parser.py index ae23d5e72734b13cbee5010021872520d01f4f88..b9ae702709bd703a8ce0d56aa39ab964161b7f8e 100644 --- a/geomagio/imfv283/IMFV283Parser.py +++ b/geomagio/imfv283/IMFV283Parser.py @@ -232,9 +232,10 @@ class IMFV283Parser(object): """ header = {} - # day of year and minute of day are combined into 3 bytes - header["day"] = data[0] + 0x100 * (data[1] & 0xF) - header["minute"] = (data[2] & 0xFF) * 0x10 + data[1] / 0x10 + # day of year and minute of day are each 12 bits: + # input bytes AB CD EF => day = DAB, minute = EFC + header["day"] = ((data[1] & 0xF) << 8) + data[0] + header["minute"] = ((data[2] & 0xFF) << 4) + ((data[1] & 0xF0) >> 4) # offset values for each channel are in bytes 3,4,5,6 respectively. header["offset"] = data[3:7] diff --git a/geomagio/imfv283/StreamIMFV283Factory.py b/geomagio/imfv283/StreamIMFV283Factory.py index aff52877f6bbe3948acb82fddd31326148fb6854..402f0e2c3199ceb58ddcb9223815e045f582edf8 100644 --- a/geomagio/imfv283/StreamIMFV283Factory.py +++ b/geomagio/imfv283/StreamIMFV283Factory.py @@ -31,6 +31,7 @@ class StreamIMFV283Factory(IMFV283Factory): channels=None, type=None, interval=None, + add_empty_channels: bool = True, ): """Implements get_timeseries diff --git a/geomagio/metadata/Metadata.md b/geomagio/metadata/Metadata.md new file mode 100644 index 0000000000000000000000000000000000000000..558d7a5dc1aaded4a5a75f099857cb29a1b5b483 --- /dev/null +++ b/geomagio/metadata/Metadata.md @@ -0,0 +1,62 @@ +# Metadata Webservice Model Parameters + +## Metadata id: +* **id**: Database id + +* **metadata_id**: Indicates old version of metadata when set + +## User information: + +* **created_by**: user who created record + +* **created_time**: time record was created + +* **updated_by**: user that last changed record(empty when new) + +* **updated_time**: time of last record change + +# Status: +* **status**: 1 of 4 record statuses + * **new**: when a new record is created + * **updated**: when a new record is edited by an approved user + * **reviewed**: after a record is reviewed by an approved user + * **deleted**: archive a record that is invalid or incomplete + +* **review_comment**: Comments from reviewer about why record is valid/invalid + +# Details: +* **category**: 1 of 5 metadata types + * **reading**: field measurements and residual method calculations + + https://code.usgs.gov/ghsc/geomag/geomag-algorithms/-/blob/master/geomagio/residual/Reading.py + + * **adjusted-matrix**: adjusted matrices and performance metrics for use by the adjusted algorithm + + https://code.usgs.gov/ghsc/geomag/geomag-algorithms/-/blob/master/geomagio/adjusted/AdjustedMatrix.py + + * **flag**: indicators of observatory outages or data issues(spikes, gaps, offsets, etc.) + + * **observatory**: Information related to an observatory(agency name, station id, sensor orientation, etc.) + + https://code.usgs.gov/ghsc/geomag/geomag-algorithms/-/blob/master/geomagio/api/ws/Observatory.py + + * **instrument**: holds information(serial numbers, volt/bin constants, etc.) pertaining to theodolites or magnetometers + +* **comment**: Comments from users entering or editing metadata(non-review related) + +* **data_valid**: Whether referenced data is valid/invalid during a given time range + +* **metadata**: Information specific to each metadata category + +* **priority**: If multiple records reference the same time, higher priority takes precedence + +* **starttime/endtime**: time that metadata applies to(instrument/observatory/adjusted-matrix) or time metadata was collected at(flag/reading) + +# Reference Data: +* **network**: Defaults to NT(geomagnetism edge network) + +* **station**: Station/observatory ID + +* **channel**: 3-Letter edge channel. When null, applies to entire station + +* **location**: 2-Letter edge location code. When null, applies to entire station \ No newline at end of file diff --git a/geomagio/metadata/Metadata.py b/geomagio/metadata/Metadata.py index ce2ad5211ac07c5023c4821396a8abe1ed1fd4f5..7c073317776baef7d8a08280aaeeaaa5989936f3 100644 --- a/geomagio/metadata/Metadata.py +++ b/geomagio/metadata/Metadata.py @@ -48,12 +48,14 @@ class Metadata(BaseModel): # database id id: int = None + # metadata history id referencing database id + metadata_id: int = None # author created_by: str = None created_time: UTCDateTime = None - # reviewer - reviewed_by: str = None - reviewed_time: UTCDateTime = None + # editor + updated_by: str = None + updated_time: UTCDateTime = None # time range starttime: UTCDateTime = None endtime: UTCDateTime = None @@ -68,18 +70,18 @@ class Metadata(BaseModel): priority: int = 1 # whether data is valid (primarily for flags) data_valid: bool = True - # whether metadata is valid (based on review) - metadata_valid: bool = True # metadata json blob metadata: Dict = None # general comment comment: str = None # review specific comment review_comment: str = None + # metadata status indicator + status: str = None def datetime_dict(self, **kwargs): values = self.dict(**kwargs) - for key in ["created_time", "reviewed_time", "starttime", "endtime"]: + for key in ["created_time", "updated_time", "starttime", "endtime"]: if key in values and values[key] is not None: values[key] = values[key].datetime.replace(tzinfo=timezone.utc) return values diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/metadata/MetadataFactory.py new file mode 100644 index 0000000000000000000000000000000000000000..ec3485624093e04eadf51a56e123834534b0feb2 --- /dev/null +++ b/geomagio/metadata/MetadataFactory.py @@ -0,0 +1,82 @@ +import os +import requests +from typing import List + +from obspy import UTCDateTime +from pydantic import parse_obj_as + +from .Metadata import Metadata +from .MetadataQuery import MetadataQuery + + +GEOMAG_API_HOST = os.getenv("GEOMAG_API_HOST", "geomag.usgs.gov") +GEOMAG_API_URL = f"https://{GEOMAG_API_HOST}/ws/secure/metadata" +if "127.0.0.1" in GEOMAG_API_URL: + GEOMAG_API_URL = GEOMAG_API_URL.replace("https://", "http://") + + +class MetadataFactory(object): + def __init__( + self, + url: str = GEOMAG_API_URL, + token: str = os.getenv("GITLAB_API_TOKEN"), + ): + self.url = url + self.token = token + + def _get_headers(self): + return ( + {"Authorization": self.token, "content-type": "application/json"} + if self.token + else None + ) + + def get_metadata(self, query: MetadataQuery) -> List[Metadata]: + if query.id: + metadata = [self.get_metadata_by_id(id=query.id)] + else: + response = requests.get( + url=self.url, + headers=self._get_headers(), + params=parse_params(query=query), + ) + metadata = parse_obj_as(List[Metadata], response.json()) + return metadata + + def get_metadata_by_id(self, id: int) -> Metadata: + response = requests.get( + url=f"{self.url}/{id}", + headers=self._get_headers(), + ) + return Metadata(**response.json()) + + def create_metadata(self, metadata: Metadata) -> Metadata: + response = requests.post( + url=self.url, + data=metadata.json(), + headers=self._get_headers(), + ) + return Metadata(**response.json()) + + def update_metadata(self, metadata: Metadata) -> Metadata: + response = requests.put( + url=f"{self.url}/{metadata.id}", + data=metadata.json(), + headers=self._get_headers(), + ) + return Metadata(**response.json()) + + +def parse_params(query: MetadataQuery) -> str: + query = query.dict(exclude_none=True) + args = {} + for key in query.keys(): + element = query[key] + # convert times to strings + if isinstance(element, UTCDateTime): + element = element.isoformat() + # get string value of metadata category + if key == "category": + element = element.value + args[key] = element + return args diff --git a/geomagio/api/secure/MetadataQuery.py b/geomagio/metadata/MetadataQuery.py similarity index 58% rename from geomagio/api/secure/MetadataQuery.py rename to geomagio/metadata/MetadataQuery.py index 629ad014907dbf7d35c76acd4781caad98ea184e..2b873795e05dbc2ce70255e854d34f7886f468e8 100644 --- a/geomagio/api/secure/MetadataQuery.py +++ b/geomagio/metadata/MetadataQuery.py @@ -1,10 +1,11 @@ from datetime import timezone from obspy import UTCDateTime -from pydantic import BaseModel, validator +from pydantic import BaseModel +from typing import List, Optional -from ...metadata import MetadataCategory -from ... import pydantic_utcdatetime +from .. import pydantic_utcdatetime +from .MetadataCategory import MetadataCategory class MetadataQuery(BaseModel): @@ -12,20 +13,18 @@ class MetadataQuery(BaseModel): category: MetadataCategory = None starttime: UTCDateTime = None endtime: UTCDateTime = None + created_after: UTCDateTime = None + created_before: UTCDateTime = None network: str = None station: str = None channel: str = None location: str = None - data_valid: bool = None - metadata_valid: bool = True + data_valid: Optional[bool] = None + status: Optional[List[str]] = None def datetime_dict(self, **kwargs): values = self.dict(**kwargs) - for key in ["starttime", "endtime"]: + for key in ["starttime", "endtime", "created_after", "created_before"]: if key in values and values[key] is not None: values[key] = values[key].datetime.replace(tzinfo=timezone.utc) return values - - @validator("starttime") - def set_default_starttime(cls, starttime: UTCDateTime = None) -> UTCDateTime: - return starttime or UTCDateTime() - 30 * 86400 diff --git a/geomagio/metadata/__init__.py b/geomagio/metadata/__init__.py index 7502db0243cac6af13d38c8386c3dd292ae386c2..4a348bb4793b409f7cb9afb1be8261731341922a 100644 --- a/geomagio/metadata/__init__.py +++ b/geomagio/metadata/__init__.py @@ -1,5 +1,13 @@ from .Metadata import Metadata from .MetadataCategory import MetadataCategory +from .MetadataFactory import MetadataFactory, GEOMAG_API_URL +from .MetadataQuery import MetadataQuery -__all__ = ["Metadata", "MetadataCategory"] +__all__ = [ + "GEOMAG_API_URL", + "Metadata", + "MetadataCategory", + "MetadataFactory", + "MetadataQuery", +] diff --git a/geomagio/metadata/main.py b/geomagio/metadata/main.py new file mode 100644 index 0000000000000000000000000000000000000000..cf1cd9adca76d198c839739beae0d80bae37d836 --- /dev/null +++ b/geomagio/metadata/main.py @@ -0,0 +1,173 @@ +import sys +import json +import os +from typing import Dict, List, Optional + +from obspy import UTCDateTime +import typer + +from .Metadata import Metadata +from .MetadataCategory import MetadataCategory +from .MetadataFactory import MetadataFactory +from .MetadataQuery import MetadataQuery + + +GEOMAG_API_HOST = os.getenv("GEOMAG_API_HOST", "geomag.usgs.gov") +GEOMAG_API_URL = f"https://{GEOMAG_API_HOST}/ws/secure/metadata" +if "127.0.0.1" in GEOMAG_API_URL: + GEOMAG_API_URL = GEOMAG_API_URL.replace("https://", "http://") + + +ENVIRONMENT_VARIABLE_HELP = """Environment variables: + + GITLAB_API_TOKEN + + (Required) Personal access token with "read_api" scope. Create at + https://code.usgs.gov/profile/personal_access_tokens + + GEOMAG_API_HOST + + Default "geomag.usgs.gov" + + REQUESTS_CA_BUNDLE + + Use custom certificate bundle + """ + + +app = typer.Typer( + help=f""" + Command line interface for Metadata API + + {ENVIRONMENT_VARIABLE_HELP} + """ +) + + +def load_metadata(input_file: str) -> Optional[Dict]: + if input_file is None: + return None + if input_file == "-": + data = json.loads(sys.stdin.read()) + return data + with open(input_file, "r") as file: + data = json.load(file) + return data + + +def main(): + """Command line interface for Metadata API. + + Registered as "geomag-metadata" console script in setup.py. + """ + app() + + +@app.command( + help=f""" + Create new metadata. + + {ENVIRONMENT_VARIABLE_HELP} + """ +) +def create( + category: MetadataCategory = None, + channel: str = None, + created_after: str = None, + created_before: str = None, + data_valid: bool = True, + endtime: str = None, + id: int = None, + input_file: str = None, + location: str = None, + network: str = None, + starttime: str = None, + station: str = None, + status: str = None, + url: str = GEOMAG_API_URL, + wrap: bool = True, +): + input_metadata = load_metadata(input_file=input_file) + if not wrap: + metadata = Metadata(**input_metadata) + else: + metadata = Metadata( + category=category, + channel=channel, + created_after=UTCDateTime(created_after) if created_after else None, + created_before=UTCDateTime(created_before) if created_before else None, + data_valid=data_valid, + endtime=UTCDateTime(endtime) if endtime else None, + id=id, + location=location, + metadata=input_metadata, + network=network, + starttime=UTCDateTime(starttime) if starttime else None, + station=station, + status=status or "new", + ) + metadata = MetadataFactory(url=url).create_metadata(metadata=metadata) + print(metadata.json()) + + +@app.command( + help=f""" + Search existing metadata. + + {ENVIRONMENT_VARIABLE_HELP} + """ +) +def get( + category: Optional[MetadataCategory] = None, + channel: Optional[str] = None, + created_after: Optional[str] = None, + created_before: Optional[str] = None, + data_valid: Optional[bool] = None, + endtime: Optional[str] = None, + getone: bool = False, + id: Optional[int] = None, + location: Optional[str] = None, + network: Optional[str] = None, + status: Optional[List[str]] = typer.Argument(None), + starttime: Optional[str] = None, + station: Optional[str] = None, + url: str = GEOMAG_API_URL, +): + query = MetadataQuery( + category=category, + channel=channel, + created_after=UTCDateTime(created_after) if created_after else None, + created_before=UTCDateTime(created_before) if created_before else None, + data_valid=data_valid, + endtime=UTCDateTime(endtime) if endtime else None, + id=id, + location=location, + network=network, + starttime=UTCDateTime(starttime) if starttime else None, + station=station, + status=status, + ) + metadata = MetadataFactory(url=url).get_metadata(query=query) + if getone: + if len(metadata) != 1: + raise ValueError(f"{len(metadata)} matching records") + print(metadata[0].json()) + else: + print("[" + ",\n".join([m.json() for m in metadata]) + "]") + + +@app.command( + help=f""" + Update an existing metadata. + + {ENVIRONMENT_VARIABLE_HELP} + """ +) +def update( + input_file: str, + url: str = GEOMAG_API_URL, +): + metadata_dict = load_metadata(input_file=input_file) + metadata = Metadata(**metadata_dict) + response = MetadataFactory(url=url).update_metadata(metadata=metadata) + print(response.json()) diff --git a/geomagio/pcdcp/PCDCPParser.py b/geomagio/pcdcp/PCDCPParser.py index f3108c10d57df0d675e0044a7f6eae595b82e9c9..176b389412730f2b0650b3c2208b65a514be9f50 100644 --- a/geomagio/pcdcp/PCDCPParser.py +++ b/geomagio/pcdcp/PCDCPParser.py @@ -4,9 +4,9 @@ import numpy # values that represent missing data points in PCDCP -NINES = numpy.int("9999999") -NINES_RAW = numpy.int("99999990") -NINES_DEG = numpy.int("9999") +NINES = int("9999999") +NINES_RAW = int("99999990") +NINES_DEG = int("9999") class PCDCPParser(object): diff --git a/geomagio/processing/__init__.py b/geomagio/processing/__init__.py index 8bc3dc0a0ca0bf717c909d7466b7a4f986c77d84..77f23254b3968e623266998c5045bcfe76c9e083 100644 --- a/geomagio/processing/__init__.py +++ b/geomagio/processing/__init__.py @@ -1,3 +1,21 @@ +"""Package with near-real time processing configurations. + +Note that these implementations are subject to change, +and should be considered less stable than other packages in the library. """ -Required file for initializing entrypoints within processing workflow -""" +from .factory import get_edge_factory, get_miniseed_factory +from .derived import adjusted, average, sqdist_minute +from .obsrio import obsrio_minute, obsrio_second, obsrio_temperatures, obsrio_tenhertz + + +__all__ = [ + "adjusted", + "average", + "get_edge_factory", + "get_miniseed_factory", + "obsrio_minute", + "obsrio_second", + "obsrio_temperatures", + "obsrio_tenhertz", + "sqdist_minute", +] diff --git a/geomagio/processing/affine_matrix.py b/geomagio/processing/affine_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..51c8746a5f43c75485ddb0d64570dc6893518766 --- /dev/null +++ b/geomagio/processing/affine_matrix.py @@ -0,0 +1,96 @@ +from enum import Enum + +from obspy import UTCDateTime +from typing import Optional +import typer + +from ..adjusted.Affine import Affine +from ..residual import Reading, SpreadsheetSummaryFactory, WebAbsolutesFactory +from ..metadata import ( + GEOMAG_API_URL, + Metadata, + MetadataCategory, + MetadataFactory, + MetadataQuery, +) + + +class InputFactory(str, Enum): + METADATA = "metadata" + SPREADSHEET = "spreadsheet" + WEBABSOLUTES = "webabsolutes" + + +def main(): + typer.run(generate_matrix) + + +def generate_matrix( + observatory: str, + starttime: str, + endtime: str, + readings_starttime: str, + readings_endtime: str, + input_factory: InputFactory = InputFactory.WEBABSOLUTES, + metadata_url: str = GEOMAG_API_URL, + output_file: Optional[str] = None, + output_metadata: bool = False, + spreadsheet_directory: Optional[str] = None, + webabsolutes_url: Optional[ + str + ] = "https://geomag.usgs.gov/baselines/observation.json.php", + quiet: bool = False, +): + if input_factory == InputFactory.METADATA: + metadata = MetadataFactory(url=metadata_url).get_metadata( + query=MetadataQuery( + station=observatory, + starttime=readings_starttime, + endtime=readings_endtime, + category=MetadataCategory.READING, + data_valid=True, + ) + ) + readings = [Reading(**m.metadata) for m in metadata] + elif input_factory == InputFactory.SPREADSHEET: + readings = SpreadsheetSummaryFactory( + base_directory=spreadsheet_directory + ).get_readings( + observatory=observatory, + starttime=UTCDateTime(readings_starttime), + endtime=UTCDateTime(readings_endtime), + ) + elif input_factory == InputFactory.WEBABSOLUTES: + readings = WebAbsolutesFactory(url=webabsolutes_url).get_readings( + observatory=observatory, + starttime=UTCDateTime(readings_starttime), + endtime=UTCDateTime(readings_endtime), + ) + # calculate one affine matrix between starttime and endtime + result = Affine( + observatory=observatory, + starttime=UTCDateTime(starttime), + endtime=UTCDateTime(endtime), + update_interval=None, + ).calculate(readings=readings)[0] + + if output_metadata: + MetadataFactory(url=metadata_url).create_metadata( + metadata=Metadata( + station=observatory, + created_by="generate_matrix", + metadata=result.dict(), + starttime=result.starttime, + endtime=result.endtime, + network="NT", + category=MetadataCategory.ADJUSTED_MATRIX, + comment=f"calculated from {readings_starttime} to {readings_endtime}", + ) + ) + + if output_file: + with open(output_file, "w") as file: + file.write(result.json()) + + if not quiet: + print(result.json(indent=2)) diff --git a/geomagio/processing/derived.py b/geomagio/processing/derived.py new file mode 100644 index 0000000000000000000000000000000000000000..04161aaadd37ec687cb423fe56dacda0e236d202 --- /dev/null +++ b/geomagio/processing/derived.py @@ -0,0 +1,159 @@ +from typing import List, Optional + +import numpy + +from ..algorithm import ( + AdjustedAlgorithm, + AverageAlgorithm, + SqDistAlgorithm, +) +from ..Controller import Controller, get_realtime_interval +from ..TimeseriesFactory import TimeseriesFactory +from .factory import get_edge_factory + + +def adjusted( + observatory: str, + input_factory: Optional[TimeseriesFactory] = None, + interval: str = "second", + output_factory: Optional[TimeseriesFactory] = None, + matrix: Optional[numpy.ndarray] = None, + pier_correction: Optional[float] = None, + statefile: Optional[str] = None, + realtime_interval: int = 600, +): + """Run Adjusted algorithm. + + Parameters + ---------- + observatory: observatory to calculate + input_factory: where to read, should be configured with data_type + interval: data interval + output_factory: where to write, should be configured with data_type + matrix: adjusted matrix + pier_correction: adjusted pier correction + statefile: adjusted statefile + realtime_interval: window in seconds + + Uses update_limit=10. + """ + if not statefile and (not matrix or not pier_correction): + raise ValueError("Either statefile or matrix and pier_correction are required.") + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + algorithm=AdjustedAlgorithm( + matrix=matrix, + pier_correction=pier_correction, + statefile=statefile, + data_type="adjusted", + location="A0", + ), + inputFactory=input_factory or get_edge_factory(data_type="variation"), + inputInterval=interval, + outputFactory=output_factory or get_edge_factory(data_type="adjusted"), + outputInterval=interval, + ) + controller.run_as_update( + observatory=(observatory,), + output_observatory=(observatory,), + starttime=starttime, + endtime=endtime, + input_channels=("H", "E", "Z", "F"), + output_channels=("X", "Y", "Z", "F"), + realtime=realtime_interval, + update_limit=10, + ) + + +def average( + observatories: List[str], + input_channel: str, + input_factory: Optional[TimeseriesFactory] = None, + interval: str = "second", + output_channel: str = None, + output_factory: Optional[TimeseriesFactory] = None, + output_observatory: str = "USGS", + realtime_interval: int = 600, +): + """Run Average algorithm. + + Parameters + ---------- + observatories: input observatories to calculate + input_channel: channel from multiple observatories to average + input_factory: where to read, should be configured with data_type and interval + interval: data interval + output_channel: channel to write (defaults to input_channel). + output_factory: where to write, should be configured with data_type and interval + output_observatory: observatory where output is written + realtime_interval: window in seconds + + Uses update_limit=10. + """ + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + algorithm=AverageAlgorithm(observatories=observatories, channel=output_channel), + inputFactory=input_factory or get_edge_factory(), + inputInterval=interval, + outputFactory=output_factory or get_edge_factory(), + outputInterval=interval, + ) + controller.run_as_update( + observatory=observatories, + output_observatory=(output_observatory,), + starttime=starttime, + endtime=endtime, + output_channels=(output_channel or input_channel,), + realtime=realtime_interval, + update_limit=10, + ) + + +def sqdist_minute( + observatory: str, + statefile: str, + input_factory: Optional[TimeseriesFactory] = None, + output_factory: Optional[TimeseriesFactory] = None, + realtime_interval: int = 1800, +): + """Run SqDist algorithm. + + Only supports "minute" interval. + + Parameters + ---------- + observatory: observatory to calculate + statefile: sqdist statefile must already exist + input_factory: where to read, should be configured with data_type and interval + output_factory: where to write, should be configured with data_type and interval + realtime_interval: window in seconds + """ + if not statefile: + raise ValueError("Statefile is required.") + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + algorithm=SqDistAlgorithm( + alpha=2.3148e-5, + gamma=3.3333e-2, + m=1440, + mag=True, + smooth=180, + statefile=statefile, + ), + inputFactory=input_factory or get_edge_factory(interval="minute"), + inputInterval="minute", + outputFactory=output_factory or get_edge_factory(interval="minute"), + outputInterval="minute", + ) + # sqdist is stateful, use run + controller.run( + observatory=(observatory,), + output_observatory=(observatory,), + starttime=starttime, + endtime=endtime, + input_channels=("X", "Y", "Z", "F"), + output_channels=("MDT", "MSQ", "MSV"), + realtime=realtime_interval, + rename_output_channel=(("H_Dist", "MDT"), ("H_SQ", "MSQ"), ("H_SV", "MSV")), + update_limit=10, + ) diff --git a/geomagio/processing/factory.py b/geomagio/processing/factory.py new file mode 100644 index 0000000000000000000000000000000000000000..6e741d469110eedcf02993273ffb12ca02b01cc6 --- /dev/null +++ b/geomagio/processing/factory.py @@ -0,0 +1,22 @@ +import os + +from ..TimeseriesFactory import TimeseriesFactory +from ..edge import EdgeFactory, MiniSeedFactory + + +def get_edge_factory( + data_type="variation", + host=os.getenv("EDGE_HOST", "127.0.0.1"), + interval="second", + **kwargs +) -> TimeseriesFactory: + return EdgeFactory(host=host, interval=interval, type=data_type, **kwargs) + + +def get_miniseed_factory( + data_type="variation", + host=os.getenv("EDGE_HOST", "127.0.0.1"), + interval="second", + **kwargs +) -> TimeseriesFactory: + return MiniSeedFactory(host=host, interval=interval, type=data_type, **kwargs) diff --git a/geomagio/processing/magproc.py b/geomagio/processing/magproc.py index 14f2fc744950131ffdf61717fc55a18fe4ac87b2..748dc91a5cd6ccebca8355972850e5f1d1f4a78e 100644 --- a/geomagio/processing/magproc.py +++ b/geomagio/processing/magproc.py @@ -1,7 +1,7 @@ from datetime import datetime import os import sys -from typing import List, Tuple +from typing import List from dateutil.relativedelta import relativedelta from obspy.core import UTCDateTime, Stream @@ -146,7 +146,7 @@ def write_temperature_data( endtime=endtime, timeseries=timeseries_temperature, observatory=observatory, - interval="hourly", + interval="hour", channels=["UK1", "UK2", "UK3", "UK4"], template=template, temperatures=True, diff --git a/geomagio/processing/make_cal.py b/geomagio/processing/make_cal.py new file mode 100755 index 0000000000000000000000000000000000000000..c0d48381ccab3f05b2bfee3ae7f3ffdd69f46412 --- /dev/null +++ b/geomagio/processing/make_cal.py @@ -0,0 +1,66 @@ +#! /usr/bin/env python + +""" +Usage: + python make_cal.py OBSERVATORY YEAR +""" +from __future__ import print_function + +from obspy import UTCDateTime +import typer + +from .magproc import write_cal_file + + +def main(): + typer.run(make_cal) + + +def make_cal(observatory: str, year: int): + write_cal_file( + starttime=UTCDateTime(f"{year}-01-01"), + endtime=UTCDateTime(f"{year+1}-01-01"), + observatory=observatory, + template="file://./{OBSERVATORY}{YEAR}WebAbsMaster.cal", + ) + + +""" +CAL format example: +- ordered by date +- within date, order by H, then D, then Z component +- component values order by start time +- D component values in minutes. + + +--2015 03 30 (H) +2140-2143 c 175.0 12531.3 +2152-2156 c 174.9 12533.3 +2205-2210 c 174.8 12533.1 +2220-2223 c 174.9 12520.7 +--2015 03 30 (D) +2133-2137 c 1128.3 1118.5 +2145-2149 c 1128.4 1116.4 +2159-2203 c 1128.3 1113.1 +2212-2216 c 1128.4 1113.5 +--2015 03 30 (Z) +2140-2143 c -52.9 55403.4 +2152-2156 c -52.8 55403.8 +2205-2210 c -52.8 55404.0 +2220-2223 c -52.8 55410.5 +--2015 07 27 (H) +2146-2151 c 173.5 12542.5 +2204-2210 c 173.8 12542.5 +2225-2229 c 173.8 12547.2 +2240-2246 c 173.6 12538.7 +--2015 07 27 (D) +2137-2142 c 1127.8 1109.2 +2154-2158 c 1128.3 1106.3 +2213-2220 c 1128.0 1106.3 +2232-2237 c 1128.3 1104.7 +--2015 07 27 (Z) +2146-2151 c -53.9 55382.7 +2204-2210 c -54.0 55382.5 +2225-2229 c -54.1 55383.7 +2240-2246 c -54.1 55389.0 +""" diff --git a/bin/monitor.py b/geomagio/processing/monitor.py similarity index 81% rename from bin/monitor.py rename to geomagio/processing/monitor.py index ad57d77a103778e582ca877b9a5d9a9a36d54cee..78b4cbb86471bff6e5ed854386647ff3823264e3 100755 --- a/bin/monitor.py +++ b/geomagio/processing/monitor.py @@ -1,21 +1,14 @@ #! /usr/bin/env python """Monitor """ -from os import path import sys - -# ensure geomag is on the path before importing -try: - import geomagio # noqa (tells linter to ignore this line.) -except ImportError: - script_dir = path.dirname(path.abspath(__file__)) - sys.path.append(path.normpath(path.join(script_dir, ".."))) - +from typing import List import argparse import sys + from obspy.core import UTCDateTime -import geomagio.TimeseriesUtility as TimeseriesUtility -import geomagio.edge as edge + +from .. import TimeseriesUtility, edge def calculate_warning_threshold(warning_threshold, interval): @@ -183,28 +176,35 @@ def print_html_header(starttime, endtime, title): ) -def print_observatories(args): +def print_observatories( + starttime: UTCDateTime, + endtime: UTCDateTime, + observatories: List[str], + edge_host: str, + channels: List[str], + data_type: str, + gaps_only: bool, + intervals: List[str], + location_code: str, + warning_threshold: int, +): """Print all the observatories - Parameters - --------- - args: dictionary - Holds all the command line arguments. See parse_args Returns ------- Boolean: if a warning was issued. """ - intervals = args.intervals - channels = args.channels - starttime = args.starttime - endtime = args.endtime - host = args.edge_host + intervals = intervals + channels = channels + starttime = starttime + endtime = endtime + host = edge_host table_header = get_table_header() warning_issued = False table_end = "</tbody>\n" + "</table>\n" - for observatory in args.observatories: + for observatory in observatories: summary_table = "" gap_details = "" print_it = False @@ -215,23 +215,21 @@ def print_observatories(args): host=host, port=2060, observatory=observatory, - type=args.type, + type=data_type, channels=channels, - locationCode=args.locationcode, + locationCode=location_code, interval=interval, ) timeseries = factory.get_timeseries(starttime=starttime, endtime=endtime) gaps = TimeseriesUtility.get_stream_gaps(timeseries) - if args.gaps_only and not has_gaps(gaps): + if gaps_only and not has_gaps(gaps): continue else: print_it = True warning = "" - warning_threshold = calculate_warning_threshold( - args.warning_threshold, interval - ) + warning_threshold = calculate_warning_threshold(warning_threshold, interval) summary_table += "<tr>" summary_table += '<td style="text-align:center;">' @@ -279,7 +277,38 @@ def print_observatories(args): return warning_issued -def main(args): +def generate_report( + starttime: UTCDateTime, + endtime: UTCDateTime, + observatories: List[str], + edge_host: str = "127.0.0.1", + channels: List[str] = ["H", "E", "Z", "F"], + data_type: str = "variation", + gaps_only: bool = True, + intervals: List[str] = ("minute",), + location_code: str = "R0", + title: str = "", + warning_threshold: int = 60, +): + print_html_header(starttime=starttime, endtime=endtime, title=title) + warning_issued = print_observatories( + starttime=starttime, + endtime=endtime, + observatories=observatories, + edge_host=edge_host, + channels=channels, + data_type=data_type, + gaps_only=gaps_only, + intervals=intervals, + location_code=location_code, + warning_threshold=warning_threshold, + ) + print("</body>\n" + "</html>\n") + + sys.exit(warning_issued) + + +def main(args=None): """command line tool for building geomag monitoring reports Inputs @@ -291,12 +320,21 @@ def main(args): parses command line options using argparse Output is in HTML. """ - print_html_header(args.starttime, args.endtime, args.title) - - warning_issued = print_observatories(args) - print("</body>\n" + "</html>\n") - - sys.exit(warning_issued) + if args is None: + args = parse_args(sys.argv[1:]) + generate_report( + starttime=args.starttime, + endtime=args.endtime, + observatories=args.observatories, + edge_host=args.edge_host, + channels=args.channels, + data_type=args.type, + gaps_only=args.gaps_only, + intervals=args.intervals, + location_code=args.locationcode, + title=args.title, + warning_threshold=args.warning_threshold, + ) def parse_args(args): @@ -329,7 +367,12 @@ def parse_args(args): default=None, help="UTC date YYYY-MM-DD HH:MM:SS", ) - parser.add_argument("--edge-host", required=True, help="IP/URL for edge connection") + parser.add_argument( + "--edge-host", + required=False, + default="127.0.0.1", + help="IP/URL for edge connection", + ) parser.add_argument( "--observatories", required=True, @@ -374,5 +417,4 @@ def parse_args(args): if __name__ == "__main__": - args = parse_args(sys.argv[1:]) - main(args) + main() diff --git a/geomagio/processing/obsrio.py b/geomagio/processing/obsrio.py new file mode 100644 index 0000000000000000000000000000000000000000..3a6182037bb2fef08dcda3a60edc3541df96827b --- /dev/null +++ b/geomagio/processing/obsrio.py @@ -0,0 +1,341 @@ +from typing import Optional + +import typer + +from ..algorithm import Algorithm, FilterAlgorithm +from ..Controller import ( + Controller, + get_realtime_interval, +) +from ..TimeseriesFactory import TimeseriesFactory +from .factory import get_edge_factory, get_miniseed_factory + + +def main(): + typer.run(obsrio_filter) + + +def obsrio_filter( + interval: str, + observatory: str, + input_factory: Optional[str] = None, + host: str = "127.0.0.1", + port: str = 2061, + output_factory: Optional[str] = None, + output_port: int = typer.Option( + 2061, help="Port where output factory writes data." + ), + output_read_port: int = typer.Option( + 2061, help="Port where output factory reads data" + ), + realtime_interval: int = 600, + update_limit: int = 10, +): + if interval == "realtime": + filter_realtime( + observatory=observatory, + input_factory=input_factory, + host=host, + port=port, + output_factory=output_factory, + output_port=output_port, + output_read_port=output_read_port, + realtime_interval=realtime_interval, + update_limit=update_limit, + ) + elif interval in ["hour", "day"]: + input_factory = get_edge_factory(host=host, port=port) + output_factory = get_miniseed_factory( + host=host, port=output_read_port, write_port=output_port + ) + if interval == "hour": + obsrio_hour( + observatory=observatory, + input_factory=input_factory, + output_factory=output_factory, + realtime_interval=realtime_interval, + update_limit=update_limit, + ) + elif interval == "day": + obsrio_day( + observatory=observatory, + input_factory=input_factory, + output_factory=output_factory, + realtime_interval=realtime_interval, + update_limit=update_limit, + ) + else: + raise ValueError("Invalid interval") + + +def filter_realtime( + observatory: str, + input_factory: Optional[str] = None, + host: str = "127.0.0.1", + port: str = 2061, + output_factory: Optional[str] = None, + output_port: int = typer.Option( + 2061, help="Port where output factory writes data." + ), + output_read_port: int = typer.Option( + 2061, help="Port where output factory reads data" + ), + realtime_interval: int = 600, + update_limit: int = 10, +): + """Filter 10Hz miniseed, 1 second, one minute, and temperature data. + Defaults set for realtime processing; can also be implemented to update legacy data""" + if input_factory == "miniseed": + input_factory = get_miniseed_factory(host=host, port=port) + elif input_factory == "edge": + input_factory = get_edge_factory(host=host, port=port) + if output_factory == "miniseed": + output_factory = get_miniseed_factory( + host=host, port=output_read_port, write_port=output_port + ) + elif output_factory == "edge": + output_factory = get_edge_factory( + host=host, port=output_read_port, write_port=output_port + ) + + obsrio_tenhertz( + observatory=observatory, + input_factory=input_factory, + output_factory=output_factory, + realtime_interval=realtime_interval, + update_limit=update_limit, + ) + obsrio_second( + observatory=observatory, + input_factory=input_factory, + output_factory=output_factory, + realtime_interval=realtime_interval, + update_limit=update_limit, + ) + obsrio_minute( + observatory=observatory, + input_factory=input_factory, + output_factory=output_factory, + realtime_interval=realtime_interval, + update_limit=update_limit, + ) + obsrio_temperatures( + observatory=observatory, + input_factory=input_factory, + output_factory=output_factory, + realtime_interval=realtime_interval, + update_limit=update_limit, + ) + + +def obsrio_day( + observatory: str, + input_factory: Optional[TimeseriesFactory] = None, + output_factory: Optional[TimeseriesFactory] = None, + realtime_interval: int = 86400, + update_limit: int = 7, +): + """Filter 1 second edge H,E,Z,F to 1 day miniseed U,V,W,F.""" + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + inputFactory=input_factory or get_edge_factory(), + inputInterval="minute", + outputFactory=output_factory or get_miniseed_factory(), + outputInterval="day", + ) + renames = {"H": "U", "E": "V", "Z": "W", "F": "F"} + for input_channel in renames.keys(): + output_channel = renames[input_channel] + controller.run_as_update( + algorithm=FilterAlgorithm( + input_sample_period=60.0, + output_sample_period=86400.0, + inchannels=(input_channel,), + outchannels=(output_channel,), + ), + observatory=(observatory,), + output_observatory=(observatory,), + starttime=starttime, + endtime=endtime, + input_channels=(input_channel,), + output_channels=(output_channel,), + realtime=realtime_interval, + rename_output_channel=((input_channel, output_channel),), + update_limit=update_limit, + ) + + +def obsrio_hour( + observatory: str, + input_factory: Optional[TimeseriesFactory] = None, + output_factory: Optional[TimeseriesFactory] = None, + realtime_interval: int = 600, + update_limit: int = 10, +): + """Filter 1 minute edge H,E,Z,F to 1 hour miniseed U,V,W,F.""" + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + inputFactory=input_factory or get_edge_factory(), + inputInterval="minute", + outputFactory=output_factory or get_miniseed_factory(), + outputInterval="hour", + ) + renames = {"H": "U", "E": "V", "Z": "W", "F": "F"} + for input_channel in renames.keys(): + output_channel = renames[input_channel] + controller.run_as_update( + algorithm=FilterAlgorithm( + input_sample_period=60.0, + output_sample_period=3600.0, + inchannels=(input_channel,), + outchannels=(output_channel,), + ), + observatory=(observatory,), + output_observatory=(observatory,), + starttime=starttime, + endtime=endtime, + input_channels=(input_channel,), + output_channels=(output_channel,), + realtime=realtime_interval, + rename_output_channel=((input_channel, output_channel),), + update_limit=update_limit, + ) + + +def obsrio_minute( + observatory: str, + input_factory: Optional[TimeseriesFactory] = None, + output_factory: Optional[TimeseriesFactory] = None, + realtime_interval: int = 600, + update_limit: int = 10, +): + """Filter 1Hz legacy H,E,Z,F to 1 minute legacy. + + Should be called after obsrio_second() and obsrio_tenhertz(), + which populate 1Hz legacy H,E,Z,F. + """ + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + inputFactory=input_factory or get_edge_factory(), + inputInterval="second", + outputFactory=output_factory or get_edge_factory(), + outputInterval="minute", + ) + for channel in ["H", "E", "Z", "F"]: + controller.run_as_update( + algorithm=FilterAlgorithm( + input_sample_period=1, + output_sample_period=60, + inchannels=(channel,), + outchannels=(channel,), + ), + observatory=(observatory,), + output_observatory=(observatory,), + starttime=starttime, + endtime=endtime, + input_channels=(channel,), + output_channels=(channel,), + realtime=realtime_interval, + update_limit=update_limit, + ) + + +def obsrio_second( + observatory: str, + input_factory: Optional[TimeseriesFactory] = None, + output_factory: Optional[TimeseriesFactory] = None, + realtime_interval: int = 600, + update_limit: int = 10, +): + """Copy 1Hz miniseed F to 1Hz legacy F.""" + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + algorithm=Algorithm(inchannels=("F",), outchannels=("F",)), + inputFactory=input_factory or get_miniseed_factory(), + outputFactory=output_factory or get_edge_factory(), + ) + controller.run_as_update( + observatory=(observatory,), + output_observatory=(observatory,), + starttime=starttime, + endtime=endtime, + input_channels=("F",), + output_channels=("F",), + realtime=realtime_interval, + update_limit=update_limit, + ) + + +def obsrio_temperatures( + observatory: str, + input_factory: Optional[TimeseriesFactory] = None, + output_factory: Optional[TimeseriesFactory] = None, + realtime_interval: int = 600, + update_limit: int = 10, +): + """Filter temperatures 1Hz miniseed (LK1-4) to 1 minute legacy (UK1-4).""" + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + inputFactory=input_factory or get_miniseed_factory(), + inputInterval="second", + outputFactory=output_factory or get_edge_factory(), + outputInterval="minute", + ) + renames = {"LK1": "UK1", "LK2": "UK2", "LK3": "UK3", "LK4": "UK4"} + for input_channel in renames.keys(): + output_channel = renames[input_channel] + controller.run_as_update( + algorithm=FilterAlgorithm( + input_sample_period=1, + output_sample_period=60, + inchannels=(input_channel,), + outchannels=(output_channel,), + ), + observatory=(observatory,), + output_observatory=(observatory,), + starttime=starttime, + endtime=endtime, + input_channels=(input_channel,), + output_channels=(output_channel,), + realtime=realtime_interval, + rename_output_channel=((input_channel, output_channel),), + update_limit=update_limit, + ) + + +def obsrio_tenhertz( + observatory: str, + input_factory: Optional[TimeseriesFactory] = None, + output_factory: Optional[TimeseriesFactory] = None, + realtime_interval: int = 600, + update_limit: int = 10, +): + """Filter 10Hz miniseed U,V,W to 1Hz legacy H,E,Z.""" + starttime, endtime = get_realtime_interval(realtime_interval) + controller = Controller( + inputFactory=input_factory + or get_miniseed_factory(convert_channels=("U", "V", "W")), + inputInterval="tenhertz", + outputFactory=output_factory or get_edge_factory(), + outputInterval="second", + ) + renames = {"U": "H", "V": "E", "W": "Z"} + for input_channel in renames.keys(): + output_channel = renames[input_channel] + controller.run_as_update( + algorithm=FilterAlgorithm( + input_sample_period=0.1, + output_sample_period=1, + inchannels=(input_channel,), + outchannels=(output_channel,), + ), + observatory=(observatory,), + output_observatory=(observatory,), + starttime=starttime, + endtime=endtime, + input_channels=(input_channel,), + output_channels=(output_channel,), + realtime=realtime_interval, + rename_output_channel=((input_channel, output_channel),), + update_limit=update_limit, + ) diff --git a/geomagio/pydantic_utcdatetime.py b/geomagio/pydantic_utcdatetime.py index 31a2bb39b79ffe6c45d5ec4d81508f8d0b16bf14..52ce67e262cf5d10c9698e88583e1089cdadd68e 100644 --- a/geomagio/pydantic_utcdatetime.py +++ b/geomagio/pydantic_utcdatetime.py @@ -42,7 +42,7 @@ class UTCDateTimeError(PydanticValueError): def format_utcdatetime(o: UTCDateTime) -> str: - return o.isoformat() + return o.strftime("%Y-%m-%dT%H:%M:%S.%fZ") def parse_utcdatetime( diff --git a/geomagio/residual/Calculation.py b/geomagio/residual/Calculation.py index 4001c5f1115f55e81396290440a44a203babe7b3..fe1ab1c1ed48706ddb343a0282daec9842c69fe7 100644 --- a/geomagio/residual/Calculation.py +++ b/geomagio/residual/Calculation.py @@ -2,10 +2,8 @@ from typing import List, Tuple from typing_extensions import Literal import numpy as np -from pydantic import BaseModel from .Absolute import Absolute -from .Angle import from_dms, to_dms from .MeasurementType import ( MeasurementType as mt, DECLINATION_TYPES, @@ -13,6 +11,7 @@ from .MeasurementType import ( MARK_TYPES, ) from .Measurement import AverageMeasurement, Measurement, average_measurement +from .Diagnostics import Diagnostics from .Reading import Reading @@ -29,9 +28,13 @@ def calculate(reading: Reading, adjust_reference: bool = True) -> Reading: NOTE: rest of reading object is shallow copy. """ # reference measurement, used to adjust absolutes - reference = reading[mt.WEST_DOWN][0] + missing_types = reading.get_missing_measurement_types() + if len(missing_types) != 0: + missing_types = ", ".join(t.value for t in missing_types) + raise ValueError(f"Missing {missing_types} measurements in input reading") + reference = adjust_reference and reading[mt.WEST_DOWN][0] or None # calculate inclination - inclination, f, mean = calculate_I( + inclination, f, i_mean = calculate_I( hemisphere=reading.hemisphere, measurements=reading.measurements ) corrected_f = f + reading.pier_correction @@ -39,14 +42,19 @@ def calculate(reading: Reading, adjust_reference: bool = True) -> Reading: absoluteH, absoluteZ = calculate_HZ_absolutes( corrected_f=corrected_f, inclination=inclination, - mean=mean, - reference=adjust_reference and reference or None, + mean=i_mean, + reference=reference, ) - absoluteD = calculate_D_absolute( + absoluteD, meridian = calculate_D_absolute( azimuth=reading.azimuth, h_baseline=absoluteH.baseline, measurements=reading.measurements, - reference=adjust_reference and reference or None, + reference=reference, + ) + # populate diagnostics object with averaged measurements + diagnostics = Diagnostics( + inclination=inclination, + meridian=meridian, ) # calculate scale scale_value = None @@ -61,8 +69,9 @@ def calculate(reading: Reading, adjust_reference: bool = True) -> Reading: calculated = Reading( absolutes=[absoluteD, absoluteH, absoluteZ], scale_value=scale_value, + diagnostics=diagnostics, # copy other attributes - **reading.dict(exclude={"absolutes", "scale_value"}), + **reading.dict(exclude={"absolutes", "scale_value", "diagnostics"}), ) return calculated @@ -72,7 +81,7 @@ def calculate_D_absolute( azimuth: float, h_baseline: float, reference: Measurement, -) -> Absolute: +) -> Tuple[Absolute, Diagnostics]: """Calculate D absolute. Parameters @@ -89,14 +98,14 @@ def calculate_D_absolute( mean = average_measurement(measurements, DECLINATION_TYPES) reference = reference or mean # average mark - average_mark = average_measurement(measurements, MARK_TYPES).angle + average_mark = average_measurement(measurements, MARK_TYPES) # adjust based on which is larger mark_up = average_measurement(measurements, [mt.FIRST_MARK_UP]).angle mark_down = average_measurement(measurements, [mt.FIRST_MARK_DOWN]).angle if mark_up < mark_down: - average_mark += 90 + average_mark.angle += 90 else: - average_mark -= 90 + average_mark.angle -= 90 # declination measurements declination_measurements = [ average_measurement(measurements, [t]) for t in DECLINATION_TYPES @@ -118,16 +127,20 @@ def calculate_D_absolute( shift = -180 # add subtract average mark angle from average meridian angle and add # azimuth to get the declination baseline - d_b = (meridian - average_mark) + azimuth + shift + d_b = (meridian - average_mark.angle) + azimuth + shift # calculate absolute d_abs = d_b + np.degrees(np.arctan(reference.e / (reference.h + h_baseline))) - return Absolute( - element="D", - absolute=d_abs, - baseline=d_b, - shift=shift, - starttime=mean.time, - endtime=mean.endtime, + + return ( + Absolute( + element="D", + absolute=d_abs, + baseline=d_b, + shift=shift, + starttime=mean.time, + endtime=mean.endtime, + ), + meridian, ) diff --git a/geomagio/residual/Diagnostics.py b/geomagio/residual/Diagnostics.py new file mode 100644 index 0000000000000000000000000000000000000000..fd9dac1c9e515e1e1033f5fb8735272ee15e1d57 --- /dev/null +++ b/geomagio/residual/Diagnostics.py @@ -0,0 +1,17 @@ +from typing import Optional + +from .Measurement import Measurement +from pydantic import BaseModel + + +class Diagnostics(BaseModel): + """Computed diagnostics during calculation. + + Attributes + ---------- + inclination: Average of inclination measurements + meridian: Calculated meridian value + """ + + inclination: float + meridian: float diff --git a/geomagio/residual/Reading.py b/geomagio/residual/Reading.py index 098cef7667040d74c7d51e1e1153a4c72b4714a3..437da4992fd3bac827861ff3340c3743b530c143 100644 --- a/geomagio/residual/Reading.py +++ b/geomagio/residual/Reading.py @@ -1,15 +1,21 @@ -import collections -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Tuple from typing_extensions import Literal -from obspy import Stream +import numpy as np +from obspy import Stream, UTCDateTime from pydantic import BaseModel from .. import TimeseriesUtility from ..TimeseriesFactory import TimeseriesFactory from .Absolute import Absolute -from .Measurement import AverageMeasurement, Measurement, average_measurement -from .MeasurementType import MeasurementType +from .Measurement import Measurement, average_measurement +from .Diagnostics import Diagnostics +from .MeasurementType import ( + MeasurementType, + DECLINATION_TYPES, + INCLINATION_TYPES, + MARK_TYPES, +) class Reading(BaseModel): @@ -33,6 +39,7 @@ class Reading(BaseModel): metadata: Dict = {} pier_correction: float = 0 scale_value: float = None + diagnostics: Diagnostics = None def __getitem__(self, measurement_type: MeasurementType): """Provide access to measurements by type. @@ -41,6 +48,21 @@ class Reading(BaseModel): """ return [m for m in self.measurements if m.measurement_type == measurement_type] + def get_absolute( + self, + element: str, + ) -> Optional[Absolute]: + for absolute in self.absolutes: + if absolute.element == element: + return absolute + return None + + def get_missing_measurement_types(self) -> List[str]: + measurement_types = [m.measurement_type for m in self.measurements] + all_types = DECLINATION_TYPES + INCLINATION_TYPES + MARK_TYPES + missing = [t for t in all_types if t not in measurement_types] + return missing + def load_ordinates( self, observatory: str, @@ -97,3 +119,77 @@ class Reading(BaseModel): time=measurement.time, default=default_existing and measurement.f or None, ) + + @property + def time(self) -> Optional[UTCDateTime]: + h = self.get_absolute("H") + if h: + return h.endtime + return None + + @property + def valid(self) -> bool: + """ensures that readings used in calculations have been marked as valid + + Attributes + ---------- + readings: list containing valid and invalid readings + """ + if ( + self.get_absolute("D").valid == True + and self.get_absolute("H").valid == True + and self.get_absolute("Z").valid == True + ): + return True + + +def get_absolutes( + readings: List[Reading], +) -> Tuple[List[float], List[float], List[float]]: + """Get H, D and Z absolutes""" + h_abs = np.array([reading.get_absolute("H").absolute for reading in readings]) + d_abs = np.array([reading.get_absolute("D").absolute for reading in readings]) + z_abs = np.array([reading.get_absolute("Z").absolute for reading in readings]) + + return (h_abs, d_abs, z_abs) + + +def get_absolutes_xyz( + readings: List[Reading], +) -> Tuple[List[float], List[float], List[float]]: + """Get X, Y and Z absolutes from H, D and Z baselines""" + h_abs, d_abs, z_abs = get_absolutes(readings) + # convert from cylindrical to Cartesian coordinates + x_a = h_abs * np.cos(np.radians(d_abs)) + y_a = h_abs * np.sin(np.radians(d_abs)) + z_a = z_abs + return (x_a, y_a, z_a) + + +def get_baselines( + readings: List[Reading], +) -> Tuple[List[float], List[float], List[float]]: + """Get H, D and Z baselines""" + h_bas = np.array([reading.get_absolute("H").baseline for reading in readings]) + d_bas = np.array([reading.get_absolute("D").baseline for reading in readings]) + z_bas = np.array([reading.get_absolute("Z").baseline for reading in readings]) + return (h_bas, d_bas, z_bas) + + +def get_ordinates( + readings: List[Reading], +) -> Tuple[List[float], List[float], List[float]]: + """Calculates ordinates from absolutes and baselines""" + h_abs, d_abs, z_abs = get_absolutes(readings) + h_bas, d_bas, z_bas = get_baselines(readings) + # recreate ordinate variometer measurements from absolutes and baselines + h_ord = h_abs - h_bas + d_ord = d_abs - d_bas + z_ord = z_abs - z_bas + e_ord = h_abs * np.radians(d_ord) + h_ord = np.sqrt(h_ord ** 2 - e_ord ** 2) + return (h_ord, e_ord, z_ord) + + +def get_times(readings: List[UTCDateTime]): + return np.array([reading.get_absolute("H").endtime for reading in readings]) diff --git a/geomagio/residual/SpreadsheetAbsolutesFactory.py b/geomagio/residual/SpreadsheetAbsolutesFactory.py index d53b772bd0cf6b67006c8d43884e5da9d03a6e60..1f50f794faff107c7663f9c7db9ba947749a1c1b 100644 --- a/geomagio/residual/SpreadsheetAbsolutesFactory.py +++ b/geomagio/residual/SpreadsheetAbsolutesFactory.py @@ -6,6 +6,13 @@ from obspy.core import UTCDateTime import openpyxl from .Absolute import Absolute +from .Calculation import ( + DECLINATION_TYPES, + MARK_TYPES, + INCLINATION_TYPES, + average_measurement, +) +from .Diagnostics import Diagnostics from .Measurement import Measurement from .MeasurementType import MeasurementType as mt from .Reading import Reading @@ -259,7 +266,10 @@ class SpreadsheetAbsolutesFactory(object): for filename in filenames: if start_filename <= filename < end_filename: readings.append( - self.parse_spreadsheet(os.path.join(dirpath, filename)) + self.parse_spreadsheet( + path=os.path.join(dirpath, filename), + include_measurements=include_measurements, + ) ) return readings @@ -296,6 +306,7 @@ class SpreadsheetAbsolutesFactory(object): metadata=metadata, pier_correction=metadata["pier_correction"], scale_value=numpy.degrees(metadata["scale_value"]), + diagnostics=self._parse_diagnostics(calculation_sheet), ) def _parse_absolutes( @@ -399,6 +410,18 @@ class SpreadsheetAbsolutesFactory(object): "precision": measurement_sheet["H8"].value, } + def _parse_diagnostics( + self, + sheet: openpyxl.worksheet, + ) -> Diagnostics: + """ + Gather diagnostics from list of measurements + """ + return Diagnostics( + inclination=sheet["H40"].value, + meridian=sheet["E36"].value, + ) + def convert_precision(angle, precision="DMS"): """ diff --git a/geomagio/residual/SpreadsheetSummaryFactory.py b/geomagio/residual/SpreadsheetSummaryFactory.py new file mode 100644 index 0000000000000000000000000000000000000000..7a1f5082c11e88c3fd26f5637eb3251e25f049e9 --- /dev/null +++ b/geomagio/residual/SpreadsheetSummaryFactory.py @@ -0,0 +1,141 @@ +import os + +from obspy import UTCDateTime +import openpyxl +from typing import List + +from .Absolute import Absolute +from . import Angle +from .Reading import Reading +from .SpreadsheetAbsolutesFactory import parse_relative_time + + +class SpreadsheetSummaryFactory(object): + """Read absolutes from summary spreadsheets""" + + def __init__(self, base_directory: str): + self.base_directory = base_directory + + def get_readings( + self, observatory: str, starttime: UTCDateTime, endtime: UTCDateTime + ) -> List[Reading]: + """Gathers readings from factory's base directory + + Attributes + ---------- + observatory: 3-letter observatory code + starttime: beginning date of readings + endtime: end date of readings + """ + readings = [] + start_filename = f"{observatory}{starttime.datetime:%Y%j%H%M}.xlsm" + end_filename = f"{observatory}{endtime.datetime:%Y%j%H%M}.xlsm" + for year in range(starttime.year, endtime.year + 1): + observatory_directory = os.path.join( + self.base_directory, observatory, f"{year}" + ) + for (dirpath, _, filenames) in os.walk(observatory_directory): + filenames.sort() + for filename in filenames: + if start_filename <= filename < end_filename: + rs = self.parse_spreadsheet( + os.path.join(dirpath, filename), + ) + for r in rs: + readings.append(r) + return readings + + def parse_spreadsheet(self, path: str) -> List[Reading]: + sheet = openpyxl.load_workbook(path, data_only=True)["Sheet1"] + readings = self._parse_readings(sheet, path) + return readings + + def _parse_metadata(self, sheet: openpyxl.worksheet) -> dict: + """gather metadata from spreadsheet + + Attributes + ---------- + sheet: excel sheet containing residual summary values + observatory: 3-letter observatory code + """ + date = sheet["I1"].value + date = f"{date.year}{date.month:02}{date.day:02}" + return { + "station": sheet["D49"].value[0:3], + "pier_correction": sheet["C5"].value, + "instrument": sheet["B3"].value, + "date": date, + "observer": sheet["I10"].value, + } + + def _parse_readings(self, sheet: openpyxl.worksheet, path: str) -> List[Reading]: + """get list of readings from spreadsheet + + Attributes + ---------- + sheet: excel sheet containing residual summary values + path: spreadsheet's filepath + + Outputs + ------- + List of valid readings from spreadsheet. + If all readings are valid, 4 readings are returned + """ + metadata = self._parse_metadata(sheet) + date = sheet["I1"].value + base_date = f"{date.year}{date.month:02}{date.day:02}" + readings = [] + for d_n in range(10, 14): + h_n = d_n + 14 + v_n = d_n + 28 + absolutes = [ + Absolute( + element="D", + absolute=Angle.from_dms( + degrees=sheet[f"C{d_n}"].value, minutes=sheet[f"D{d_n}"].value + ), + baseline=sheet[f"H{d_n}"].value / 60, + starttime=parse_relative_time( + base_date, "{0:04d}".format(sheet[f"B{v_n}"].value) + ), + endtime=parse_relative_time( + base_date, "{0:04d}".format(sheet[f"B{d_n}"].value) + ), + ), + Absolute( + element="H", + absolute=sheet[f"D{h_n}"].value, + baseline=sheet[f"H{h_n}"].value, + starttime=parse_relative_time( + base_date, "{0:04d}".format(sheet[f"B{v_n}"].value) + ), + endtime=parse_relative_time( + base_date, "{0:04d}".format(sheet[f"B{h_n}"].value) + ), + ), + Absolute( + element="Z", + absolute=sheet[f"D{v_n}"].value, + baseline=sheet[f"H{v_n}"].value, + starttime=parse_relative_time( + base_date, "{0:04d}".format(sheet[f"B{v_n}"].value) + ), + endtime=parse_relative_time( + base_date, "{0:04d}".format(sheet[f"B{v_n}"].value) + ), + ), + ] + valid = [ + sheet[f"J{d_n}"].value, + sheet[f"J{h_n}"].value, + sheet[f"J{d_n}"].value, + ] + if valid == [None, None, None]: + readings.append( + Reading( + metadata=metadata, + absolutes=absolutes, + pier_correction=metadata["pier_correction"], + ), + ) + return readings diff --git a/geomagio/residual/WebAbsolutesFactory.py b/geomagio/residual/WebAbsolutesFactory.py index 56998fb976fb0b1e465f50116cb76b57d0677518..25e29943bd5c966e5ebf151f303d8a7c10b00fb7 100644 --- a/geomagio/residual/WebAbsolutesFactory.py +++ b/geomagio/residual/WebAbsolutesFactory.py @@ -35,14 +35,15 @@ class WebAbsolutesFactory(object): } ) with urllib.request.urlopen(f"{self.url}?{args}") as data: - return self.parse_json(data) + return self.parse_json(data, observatory) - def parse_json(self, jsonstr: IO[str]) -> List[Reading]: + def parse_json(self, jsonstr: IO[str], observatory: str) -> List[Reading]: """Parse readings from the web absolutes JSON format.""" readings = [] response = json.load(jsonstr) for data in response["data"]: metadata = self._parse_metadata(data) + metadata["station"] = observatory readings.extend( [self._parse_reading(metadata, r) for r in data["readings"]] ) diff --git a/geomagio/residual/__init__.py b/geomagio/residual/__init__.py index 59e03297abe1d279223f1a63e34a096e1322ebf6..ec2de5e8adb9e2855ba2675a368f5a358f525154 100644 --- a/geomagio/residual/__init__.py +++ b/geomagio/residual/__init__.py @@ -20,13 +20,15 @@ from .MeasurementType import ( ) from .Reading import Reading from .SpreadsheetAbsolutesFactory import SpreadsheetAbsolutesFactory +from .SpreadsheetSummaryFactory import SpreadsheetSummaryFactory from .WebAbsolutesFactory import WebAbsolutesFactory __all__ = [ "Absolute", "Angle", "AverageMeasurement", - "average_measurement" "CalFileFactory", + "average_measurement", + "CalFileFactory", "calculate", "calculate_D_absolute", "calculate_HZ_absolutes", @@ -39,5 +41,6 @@ __all__ = [ "MeasurementType", "Reading", "SpreadsheetAbsolutesFactory", + "SpreadsheetSummaryFactory", "WebAbsolutesFactory", ] diff --git a/geomagio/temperature/TEMPWriter.py b/geomagio/temperature/TEMPWriter.py index 630f452da57250244bae62d10cd4799f7d54df7a..485321836650a7fb85a0ef78073ff3afa4223a9b 100644 --- a/geomagio/temperature/TEMPWriter.py +++ b/geomagio/temperature/TEMPWriter.py @@ -11,7 +11,7 @@ from obspy.core import Stream class TEMPWriter(object): """TEMP writer.""" - def __init__(self, empty_value=numpy.int("9999")): + def __init__(self, empty_value=int("9999")): self.empty_value = empty_value def write(self, out, timeseries, channels): diff --git a/geomagio/vbf/VBFWriter.py b/geomagio/vbf/VBFWriter.py index bfb18f5e6a20bc7e5a6fd57375a0185644a420a8..6ce2dd6472c1cade2b46e516028d9b689f0dfaa9 100644 --- a/geomagio/vbf/VBFWriter.py +++ b/geomagio/vbf/VBFWriter.py @@ -11,7 +11,7 @@ from obspy.core import Stream class VBFWriter(object): """VBF writer.""" - def __init__(self, empty_value=numpy.int("9999999")): + def __init__(self, empty_value=int("9999999")): self.empty_value = empty_value def write(self, out, timeseries, channels): diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000000000000000000000000000000000000..98e4f9c44effe479ed38c66ba922e7bcc672916f --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000000000000000000000000000000000000..b97f668a5479e7efb41273f413522bc358e3b83e --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,67 @@ +import os +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from geomagio.api.db.common import sqlalchemy_metadata + +config = context.config +fileConfig(config.config_file_name) +database_url = os.getenv("DATABASE_URL", None) +database_url = database_url or "sqlite:///./api_database.db" +database_url = database_url.replace("mysql://", "mysql+pymysql://") +database_url = database_url.replace("%", "%%") +config.set_main_option("sqlalchemy.url", database_url) +target_metadata = sqlalchemy_metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000000000000000000000000000000000000..2c0156303a8df3ffdc9de87765bf801bf6bea4a5 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/2280fe551e60_initialize_database.py b/migrations/versions/2280fe551e60_initialize_database.py new file mode 100644 index 0000000000000000000000000000000000000000..01c9d26b4d26d90cf685e0c244245030ab5dd920 --- /dev/null +++ b/migrations/versions/2280fe551e60_initialize_database.py @@ -0,0 +1,41 @@ +"""initialize database + +Revision ID: 2280fe551e60 +Revises: +Create Date: 2021-04-22 13:06:28.852803 + +""" +from alembic import op + +from geomagio.api.db.create import create_db + + +# revision identifiers, used by Alembic. +revision = "2280fe551e60" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + create_db() + + +def downgrade(): + # ### start Alembic commands ### + op.drop_table("metadata_history") + op.drop_index(op.f("ix_session_updated"), table_name="session") + op.drop_index(op.f("ix_session_session_id"), table_name="session") + op.drop_table("session") + op.drop_index(op.f("ix_metadata_updated_time"), table_name="metadata") + op.drop_index(op.f("ix_metadata_updated_by"), table_name="metadata") + op.drop_index(op.f("ix_metadata_starttime"), table_name="metadata") + op.drop_index(op.f("ix_metadata_metadata_valid"), table_name="metadata") + op.drop_index(op.f("ix_metadata_endtime"), table_name="metadata") + op.drop_index(op.f("ix_metadata_data_valid"), table_name="metadata") + op.drop_index(op.f("ix_metadata_created_time"), table_name="metadata") + op.drop_index(op.f("ix_metadata_created_by"), table_name="metadata") + op.drop_index("index_station_metadata", table_name="metadata") + op.drop_index("index_category_time", table_name="metadata") + op.drop_table("metadata") + # ### end Alembic commands ### diff --git a/migrations/versions/4324b87e0b3c_drop_metadata_valid_column.py b/migrations/versions/4324b87e0b3c_drop_metadata_valid_column.py new file mode 100644 index 0000000000000000000000000000000000000000..4bf200c01657d8352aa574f9ef8d52ea9434c4ee --- /dev/null +++ b/migrations/versions/4324b87e0b3c_drop_metadata_valid_column.py @@ -0,0 +1,85 @@ +"""drop metadata_valid column + +Revision ID: 4324b87e0b3c +Revises: 2280fe551e60 +Create Date: 2021-07-14 15:48:32.154022 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = "4324b87e0b3c" +down_revision = "2280fe551e60" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index("ix_metadata_metadata_valid", table_name="metadata") + op.drop_index("index_station_metadata", table_name="metadata") + op.create_index( + "index_station_metadata", + "metadata", + [ + "network", + "station", + "channel", + "location", + "category", + "starttime", + "endtime", + "data_valid", + "status", + ], + unique=False, + ) + op.drop_column("metadata", "metadata_valid") + op.drop_column("metadata_history", "metadata_valid") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "metadata_history", + sa.Column( + "metadata_valid", + mysql.TINYINT(display_width=1), + autoincrement=False, + nullable=True, + ), + ) + op.add_column( + "metadata", + sa.Column( + "metadata_valid", + mysql.TINYINT(display_width=1), + autoincrement=False, + nullable=True, + ), + ) + op.drop_index("index_station_metadata", table_name="metadata") + op.create_index( + "index_station_metadata", + "metadata", + [ + "network", + "station", + "channel", + "location", + "category", + "starttime", + "endtime", + "metadata_valid", + "data_valid", + "status", + ], + unique=False, + ) + op.create_index( + "ix_metadata_metadata_valid", "metadata", ["metadata_valid"], unique=False + ) + # ### end Alembic commands ### diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000000000000000000000000000000000000..f8b1844b64aa325804c11149e915ab8c63c3c4fb --- /dev/null +++ b/mypy.ini @@ -0,0 +1 @@ +[mypy] diff --git a/package.json b/package.json deleted file mode 100644 index b3842fced150d3439c9ea9665c84944964128f3b..0000000000000000000000000000000000000000 --- a/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "geomag-algorithms", - "version": "1.0.0", - "homepage": "http://geomag.usgs.gov/", - "repository": "https://github.com/usgs/geomag-algorithms.git", - "description": "Geomagnetism algorithms.", - "author": { - "name": "Jeremy Fee", - "email": "jmfee@usgs.gov" - }, - "contributors": [ - { - "name": "Abram Claycomb", - "email": "aclaycomb@usgs.gov" - } - ], - "keywords": [ - "usgs", - "geomag", - "hazdev" - ], - "scripts": { - "clean": "rimraf '**/*.pyc'", - "coverage": "pytest --cov=geomagio --cov-report xml", - "lint": "flake8 && echo \"No linting errors found\"", - "test": "pytest test", - "watch": "npm-watch" - }, - "watch": { - "lint": { - "extensions": "py", - "patterns": [ - "bin", - "geomagio", - "test" - ] - }, - "test": { - "extensions": "py", - "patterns": [ - "bin", - "geomagio", - "test" - ] - } - }, - "license": "Public Domain", - "dependencies": {}, - "devDependencies": { - "npm-watch": "^0.3.0", - "rimraf": "^2.6.2" - }, - "engines": { - "node": ">=4" - } -} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000000000000000000000000000000000000..e80fbdcd7df48d371c89e15862cce541a0653bd7 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1924 @@ +[[package]] +name = "aiomysql" +version = "0.0.21" +description = "MySQL driver for asyncio." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +PyMySQL = ">=0.9,<=0.9.3" + +[package.extras] +sa = ["sqlalchemy (>=1.0)"] + +[[package]] +name = "aiosqlite" +version = "0.17.0" +description = "asyncio bridge to the standard sqlite3 module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing_extensions = ">=3.7.2" + +[[package]] +name = "alembic" +version = "1.6.5" +description = "A database migration tool for SQLAlchemy." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +Mako = "*" +python-dateutil = "*" +python-editor = ">=0.3" +SQLAlchemy = ">=1.3.0" + +[[package]] +name = "anyio" +version = "3.3.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16)"] + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "asgiref" +version = "3.4.1" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "authlib" +version = "0.15.4" +description = "The ultimate Python library in building OAuth and OpenID Connect servers." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +cryptography = "*" + +[package.extras] +client = ["requests"] + +[[package]] +name = "black" +version = "21.7b0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.8.1,<1" +regex = ">=2020.1.8" +tomli = ">=0.2.6,<2.0.0" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +python2 = ["typed-ast (>=1.4.2)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2021.5.30" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "cffi" +version = "1.14.6" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.4" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "5.5" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +toml = ["toml"] + +[[package]] +name = "cryptography" +version = "3.4.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + +[[package]] +name = "cycler" +version = "0.10.0" +description = "Composable style cycles" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[[package]] +name = "data-science-types" +version = "0.2.23" +description = "Type stubs for Python machine learning libraries" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["black", "flake8", "flake8-pyi", "matplotlib", "mypy (==0.770)", "numpy", "pandas", "pytest"] + +[[package]] +name = "databases" +version = "0.4.3" +description = "Async database support for Python." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiomysql = {version = "*", optional = true, markers = "extra == \"mysql\""} +aiosqlite = {version = "*", optional = true, markers = "extra == \"sqlite\""} +sqlalchemy = "<1.4" + +[package.extras] +mysql = ["aiomysql"] +postgresql = ["asyncpg"] +postgresql_aiopg = ["aiopg"] +sqlite = ["aiosqlite"] + +[[package]] +name = "decorator" +version = "5.0.9" +description = "Decorators for Humans" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "dparse" +version = "0.5.1" +description = "A parser for Python dependency files" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +packaging = "*" +pyyaml = "*" +toml = "*" + +[package.extras] +pipenv = ["pipenv"] + +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "fastapi" +version = "0.68.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.14.2" + +[package.extras] +all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] +dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "markdown-include (>=0.6.0,<0.7.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] +test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<3.0.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.812)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.4.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] + +[[package]] +name = "future" +version = "0.18.2" +description = "Clean single-source support for Python 3 and 2" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "h11" +version = "0.12.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "httpcore" +version = "0.13.6" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +anyio = ">=3.0.0,<4.0.0" +h11 = ">=0.11,<0.13" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] + +[[package]] +name = "httptools" +version = "0.2.0" +description = "A collection of framework independent HTTP protocol utils." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["Cython (==0.29.22)"] + +[[package]] +name = "httpx" +version = "0.18.1" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.13.0,<0.14.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotlicffi (>=1.0.0,<2.0.0)"] +http2 = ["h2 (>=3.0.0,<4.0.0)"] + +[[package]] +name = "idna" +version = "3.2" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "4.6.4" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "kiwisolver" +version = "1.3.1" +description = "A fast implementation of the Cassowary constraint solver" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "lxml" +version = "4.6.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["beautifulsoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "mako" +version = "1.1.5" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["babel"] +lingua = ["lingua"] + +[[package]] +name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "matplotlib" +version = "3.4.3" +description = "Python plotting package" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cycler = ">=0.10" +kiwisolver = ">=1.0.1" +numpy = ">=1.16" +pillow = ">=6.2.0" +pyparsing = ">=2.2.1" +python-dateutil = ">=2.7" + +[[package]] +name = "mypy" +version = "0.910" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +toml = "*" +typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<1.5.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "numpy" +version = "1.21.2" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.7,<3.11" + +[[package]] +name = "obspy" +version = "1.2.2" +description = "ObsPy - a Python framework for seismological observatories." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +decorator = "*" +future = ">=0.12.4" +lxml = "*" +matplotlib = ">=1.1.0" +numpy = ">=1.6.1" +requests = "*" +scipy = ">=0.9.0" +sqlalchemy = "*" + +[package.extras] +arclink = ["cryptography"] +"io.shapefile" = ["pyshp"] +tests = ["flake8 (>=2)", "pyimgur", "pyproj", "pep8-naming"] + +[[package]] +name = "openpyxl" +version = "3.0.7" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +category = "main" +optional = false +python-versions = ">=3.6," + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "openpyxl-stubs" +version = "0.1.19" +description = "Type stubs for openpyxl" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +mypy = ">=0.720" +openpyxl = ">=3.0.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "packaging" +version = "21.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "pillow" +version = "8.3.1" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycurl" +version = "7.44.1" +description = "PycURL -- A Python Interface To The cURL library" +category = "main" +optional = true +python-versions = ">=3.5" + +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pymysql" +version = "0.9.3" +description = "Pure Python MySQL Driver" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +rsa = ["cryptography"] + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.4" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "2.12.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +coverage = ">=5.2.1" +pytest = ">=4.6" +toml = "*" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "0.19.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-editor" +version = "1.0.4" +description = "Programmatically open an editor, capture the result." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "regex" +version = "2021.8.21" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.26.0" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "safety" +version = "1.10.3" +description = "Checks installed dependencies for known vulnerabilities." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +Click = ">=6.0" +dparse = ">=0.5.1" +packaging = "*" +requests = "*" + +[[package]] +name = "scipy" +version = "1.7.1" +description = "SciPy: Scientific Library for Python" +category = "main" +optional = false +python-versions = ">=3.7,<3.10" + +[package.dependencies] +numpy = ">=1.16.5,<1.23.0" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "sniffio" +version = "1.2.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "sqlalchemy" +version = "1.3.24" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mysql = ["mysqlclient"] +oracle = ["cx-oracle"] +postgresql = ["psycopg2"] +postgresql_pg8000 = ["pg8000 (<1.16.6)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] + +[[package]] +name = "sqlalchemy-stubs" +version = "0.4" +description = "SQLAlchemy stubs and mypy plugin" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +mypy = ">=0.790" +typing-extensions = ">=3.7.4" + +[[package]] +name = "sqlalchemy-utc" +version = "0.12.0" +description = "SQLAlchemy type to store aware datetime values" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +SQLAlchemy = ">=0.9.0" + +[[package]] +name = "starlette" +version = "0.14.2" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "1.2.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typer" +version = "0.3.2" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +click = ">=7.1.1,<7.2.0" + +[package.extras] +test = ["pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.782)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)", "shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)"] +all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"] + +[[package]] +name = "types-python-dateutil" +version = "0.1.6" +description = "Typing stubs for python-dateutil" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-requests" +version = "2.25.6" +description = "Typing stubs for requests" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "urllib3" +version = "1.26.6" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvicorn" +version = "0.15.0" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +asgiref = ">=3.4.0" +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.2.0,<0.3.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchgod = {version = ">=0.6", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=9.1", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["websockets (>=9.1)", "httptools (>=0.2.0,<0.3.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] + +[[package]] +name = "uvloop" +version = "0.16.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] + +[[package]] +name = "watchgod" +version = "0.7" +description = "Simple, modern file watching and code reload in python." +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "websockets" +version = "9.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "zipp" +version = "3.5.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[extras] +pycurl = ["pycurl"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7,<3.10" +content-hash = "e26f5b9c5310e47b824ace6cd751aeffa33522c0c68bd6fb50ca7a3eeaeb4359" + +[metadata.files] +aiomysql = [ + {file = "aiomysql-0.0.21-py3-none-any.whl", hash = "sha256:a81a97da3dd732635926a8ea6adbbf2d1345799680bf61b5f89e730bcec88cc5"}, + {file = "aiomysql-0.0.21.tar.gz", hash = "sha256:811569c0db118dd2685f0878f5cebf17a11e89a995fa14261d5fa0254113842c"}, +] +aiosqlite = [ + {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, + {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, +] +alembic = [ + {file = "alembic-1.6.5-py2.py3-none-any.whl", hash = "sha256:e78be5b919f5bb184e3e0e2dd1ca986f2362e29a2bc933c446fe89f39dbe4e9c"}, + {file = "alembic-1.6.5.tar.gz", hash = "sha256:a21fedebb3fb8f6bbbba51a11114f08c78709377051384c9c5ead5705ee93a51"}, +] +anyio = [ + {file = "anyio-3.3.0-py3-none-any.whl", hash = "sha256:929a6852074397afe1d989002aa96d457e3e1e5441357c60d03e7eea0e65e1b0"}, + {file = "anyio-3.3.0.tar.gz", hash = "sha256:ae57a67583e5ff8b4af47666ff5651c3732d45fd26c929253748e796af860374"}, +] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +asgiref = [ + {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, + {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +authlib = [ + {file = "Authlib-0.15.4-py2.py3-none-any.whl", hash = "sha256:d9fe5edb59801b16583faa86f88d798d99d952979b9616d5c735b9170b41ae2c"}, + {file = "Authlib-0.15.4.tar.gz", hash = "sha256:37df3a2554bc6fe0da3cc6848c44fac2ae40634a7f8fc72543947f4330b26464"}, +] +black = [ + {file = "black-21.7b0-py3-none-any.whl", hash = "sha256:1c7aa6ada8ee864db745b22790a32f94b2795c253a75d6d9b5e439ff10d23116"}, + {file = "black-21.7b0.tar.gz", hash = "sha256:c8373c6491de9362e39271630b65b964607bc5c79c83783547d76c839b3aa219"}, +] +certifi = [ + {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, + {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, +] +cffi = [ + {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, + {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, + {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, + {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, + {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, + {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, + {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, + {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, + {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, + {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, + {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, + {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, + {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, + {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, + {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, + {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, + {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, + {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, + {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"}, + {file = "charset_normalizer-2.0.4-py3-none-any.whl", hash = "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, +] +cryptography = [ + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, +] +cycler = [ + {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, + {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"}, +] +data-science-types = [ + {file = "data-science-types-0.2.23.tar.gz", hash = "sha256:8096b9a35a8a187bf9a122b4707c97de841d810744690ee2a4ac30c6462e0d16"}, + {file = "data_science_types-0.2.23-py3-none-any.whl", hash = "sha256:bca319abc0e53a0316f9fcb887937e942477cb9e5fc63c8581e0b0438903b977"}, +] +databases = [ + {file = "databases-0.4.3-py3-none-any.whl", hash = "sha256:f82b02c28fdddf7ffe7ee1945f5abef44d687ba97b9a1c81492c7f035d4c90e6"}, + {file = "databases-0.4.3.tar.gz", hash = "sha256:1521db7f6d3c581ff81b3552e130b27a13aefea2a57295e65738081831137afc"}, +] +decorator = [ + {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, + {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, +] +dparse = [ + {file = "dparse-0.5.1-py3-none-any.whl", hash = "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994"}, + {file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"}, +] +et-xmlfile = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] +fastapi = [ + {file = "fastapi-0.68.0-py3-none-any.whl", hash = "sha256:f4dba2596b1e0a1f962834c3b9ec4291a7aec387a1031c6c2e25bf239d27fd0f"}, + {file = "fastapi-0.68.0.tar.gz", hash = "sha256:c9256a89b0436223b45f53fe3a39b178f3da6be5841a2c59deedff4b676d003f"}, +] +future = [ + {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, +] +gunicorn = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] +h11 = [ + {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, + {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, +] +httpcore = [ + {file = "httpcore-0.13.6-py3-none-any.whl", hash = "sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff"}, + {file = "httpcore-0.13.6.tar.gz", hash = "sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e"}, +] +httptools = [ + {file = "httptools-0.2.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:79dbc21f3612a78b28384e989b21872e2e3cf3968532601544696e4ed0007ce5"}, + {file = "httptools-0.2.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:78d03dd39b09c99ec917d50189e6743adbfd18c15d5944392d2eabda688bf149"}, + {file = "httptools-0.2.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a23166e5ae2775709cf4f7ad4c2048755ebfb272767d244e1a96d55ac775cca7"}, + {file = "httptools-0.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3ab1f390d8867f74b3b5ee2a7ecc9b8d7f53750bd45714bf1cb72a953d7dfa77"}, + {file = "httptools-0.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a7594f9a010cdf1e16a58b3bf26c9da39bbf663e3b8d46d39176999d71816658"}, + {file = "httptools-0.2.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:01b392a166adcc8bc2f526a939a8aabf89fe079243e1543fd0e7dc1b58d737cb"}, + {file = "httptools-0.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:80ffa04fe8c8dfacf6e4cef8277347d35b0442c581f5814f3b0cf41b65c43c6e"}, + {file = "httptools-0.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d5682eeb10cca0606c4a8286a3391d4c3c5a36f0c448e71b8bd05be4e1694bfb"}, + {file = "httptools-0.2.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a289c27ccae399a70eacf32df9a44059ca2ba4ac444604b00a19a6c1f0809943"}, + {file = "httptools-0.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:813871f961edea6cb2fe312f2d9b27d12a51ba92545380126f80d0de1917ea15"}, + {file = "httptools-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:cc9be041e428c10f8b6ab358c6b393648f9457094e1dcc11b4906026d43cd380"}, + {file = "httptools-0.2.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b08d00d889a118f68f37f3c43e359aab24ee29eb2e3fe96d64c6a2ba8b9d6557"}, + {file = "httptools-0.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fd3b8905e21431ad306eeaf56644a68fdd621bf8f3097eff54d0f6bdf7262065"}, + {file = "httptools-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:200fc1cdf733a9ff554c0bb97a4047785cfaad9875307d6087001db3eb2b417f"}, + {file = "httptools-0.2.0.tar.gz", hash = "sha256:94505026be56652d7a530ab03d89474dc6021019d6b8682281977163b3471ea0"}, +] +httpx = [ + {file = "httpx-0.18.1-py3-none-any.whl", hash = "sha256:ad2e3db847be736edc4b272c4d5788790a7e5789ef132fc6b5fef8aeb9e9f6e0"}, + {file = "httpx-0.18.1.tar.gz", hash = "sha256:0a2651dd2b9d7662c70d12ada5c290abcf57373b9633515fe4baa9f62566086f"}, +] +idna = [ + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.6.4-py3-none-any.whl", hash = "sha256:ed5157fef23a4bc4594615a0dd8eba94b2bb36bf2a343fa3d8bb2fa0a62a99d5"}, + {file = "importlib_metadata-4.6.4.tar.gz", hash = "sha256:7b30a78db2922d78a6f47fb30683156a14f3c6aa5cc23f77cc8967e9ab2d002f"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +kiwisolver = [ + {file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-win32.whl", hash = "sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454"}, + {file = "kiwisolver-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18"}, + {file = "kiwisolver-1.3.1-cp38-cp38-win32.whl", hash = "sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81"}, + {file = "kiwisolver-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e"}, + {file = "kiwisolver-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54"}, + {file = "kiwisolver-1.3.1-cp39-cp39-win32.whl", hash = "sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030"}, + {file = "kiwisolver-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6"}, + {file = "kiwisolver-1.3.1.tar.gz", hash = "sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248"}, +] +lxml = [ + {file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"}, + {file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"}, + {file = "lxml-4.6.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"}, + {file = "lxml-4.6.3-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"}, + {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"}, + {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"}, + {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"}, + {file = "lxml-4.6.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:64812391546a18896adaa86c77c59a4998f33c24788cadc35789e55b727a37f4"}, + {file = "lxml-4.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c1a40c06fd5ba37ad39caa0b3144eb3772e813b5fb5b084198a985431c2f1e8d"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16"}, + {file = "lxml-4.6.3-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"}, + {file = "lxml-4.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"}, + {file = "lxml-4.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617"}, + {file = "lxml-4.6.3-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"}, + {file = "lxml-4.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"}, + {file = "lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92"}, + {file = "lxml-4.6.3-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"}, + {file = "lxml-4.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"}, + {file = "lxml-4.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae"}, + {file = "lxml-4.6.3-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"}, + {file = "lxml-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"}, + {file = "lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a"}, + {file = "lxml-4.6.3-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"}, + {file = "lxml-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"}, + {file = "lxml-4.6.3.tar.gz", hash = "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"}, +] +mako = [ + {file = "Mako-1.1.5-py2.py3-none-any.whl", hash = "sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23"}, + {file = "Mako-1.1.5.tar.gz", hash = "sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3"}, +] +markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, +] +matplotlib = [ + {file = "matplotlib-3.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c988bb43414c7c2b0a31bd5187b4d27fd625c080371b463a6d422047df78913"}, + {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1c5efc278d996af8a251b2ce0b07bbeccb821f25c8c9846bdcb00ffc7f158aa"}, + {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eeb1859efe7754b1460e1d4991bbd4a60a56f366bc422ef3a9c5ae05f0bc70b5"}, + {file = "matplotlib-3.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:844a7b0233e4ff7fba57e90b8799edaa40b9e31e300b8d5efc350937fa8b1bea"}, + {file = "matplotlib-3.4.3-cp37-cp37m-win32.whl", hash = "sha256:85f0c9cf724715e75243a7b3087cf4a3de056b55e05d4d76cc58d610d62894f3"}, + {file = "matplotlib-3.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c70b6311dda3e27672f1bf48851a0de816d1ca6aaf3d49365fbdd8e959b33d2b"}, + {file = "matplotlib-3.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b884715a59fec9ad3b6048ecf3860f3b2ce965e676ef52593d6fa29abcf7d330"}, + {file = "matplotlib-3.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a78a3b51f29448c7f4d4575e561f6b0dbb8d01c13c2046ab6c5220eb25c06506"}, + {file = "matplotlib-3.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a724e3a48a54b8b6e7c4ae38cd3d07084508fa47c410c8757e9db9791421838"}, + {file = "matplotlib-3.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48e1e0859b54d5f2e29bb78ca179fd59b971c6ceb29977fb52735bfd280eb0f5"}, + {file = "matplotlib-3.4.3-cp38-cp38-win32.whl", hash = "sha256:01c9de93a2ca0d128c9064f23709362e7fefb34910c7c9e0b8ab0de8258d5eda"}, + {file = "matplotlib-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:ebfb01a65c3f5d53a8c2a8133fec2b5221281c053d944ae81ff5822a68266617"}, + {file = "matplotlib-3.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b53f336a4688cfce615887505d7e41fd79b3594bf21dd300531a4f5b4f746a"}, + {file = "matplotlib-3.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:fcd6f1954943c0c192bfbebbac263f839d7055409f1173f80d8b11a224d236da"}, + {file = "matplotlib-3.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6be8df61b1626e1a142c57e065405e869e9429b4a6dab4a324757d0dc4d42235"}, + {file = "matplotlib-3.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:41b6e307458988891fcdea2d8ecf84a8c92d53f84190aa32da65f9505546e684"}, + {file = "matplotlib-3.4.3-cp39-cp39-win32.whl", hash = "sha256:f72657f1596199dc1e4e7a10f52a4784ead8a711f4e5b59bea95bdb97cf0e4fd"}, + {file = "matplotlib-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:f15edcb0629a0801738925fe27070480f446fcaa15de65946ff946ad99a59a40"}, + {file = "matplotlib-3.4.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:556965514b259204637c360d213de28d43a1f4aed1eca15596ce83f768c5a56f"}, + {file = "matplotlib-3.4.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:54a026055d5f8614f184e588f6e29064019a0aa8448450214c0b60926d62d919"}, + {file = "matplotlib-3.4.3.tar.gz", hash = "sha256:fc4f526dfdb31c9bd6b8ca06bf9fab663ca12f3ec9cdf4496fb44bc680140318"}, +] +mypy = [ + {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, + {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, + {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, + {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, + {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, + {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, + {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, + {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, + {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, + {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, + {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, + {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, + {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, + {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, + {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, + {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, + {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, + {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, + {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, + {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, + {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, + {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, + {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +numpy = [ + {file = "numpy-1.21.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52a664323273c08f3b473548bf87c8145b7513afd63e4ebba8496ecd3853df13"}, + {file = "numpy-1.21.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a7b9db0a2941434cd930dacaafe0fc9da8f3d6157f9d12f761bbde93f46218"}, + {file = "numpy-1.21.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f2dc79c093f6c5113718d3d90c283f11463d77daa4e83aeeac088ec6a0bda52"}, + {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a55e4d81c4260386f71d22294795c87609164e22b28ba0d435850fbdf82fc0c5"}, + {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:426a00b68b0d21f2deb2ace3c6d677e611ad5a612d2c76494e24a562a930c254"}, + {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:298156f4d3d46815eaf0fcf0a03f9625fc7631692bd1ad851517ab93c3168fc6"}, + {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09858463db6dd9f78b2a1a05c93f3b33d4f65975771e90d2cf7aadb7c2f66edf"}, + {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:805459ad8baaf815883d0d6f86e45b3b0b67d823a8f3fa39b1ed9c45eaf5edf1"}, + {file = "numpy-1.21.2-cp37-cp37m-win32.whl", hash = "sha256:f545c082eeb09ae678dd451a1b1dbf17babd8a0d7adea02897a76e639afca310"}, + {file = "numpy-1.21.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b160b9a99ecc6559d9e6d461b95c8eec21461b332f80267ad2c10394b9503496"}, + {file = "numpy-1.21.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a5109345f5ce7ddb3840f5970de71c34a0ff7fceb133c9441283bb8250f532a3"}, + {file = "numpy-1.21.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:209666ce9d4a817e8a4597cd475b71b4878a85fa4b8db41d79fdb4fdee01dde2"}, + {file = "numpy-1.21.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c01b59b33c7c3ba90744f2c695be571a3bd40ab2ba7f3d169ffa6db3cfba614f"}, + {file = "numpy-1.21.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e42029e184008a5fd3d819323345e25e2337b0ac7f5c135b7623308530209d57"}, + {file = "numpy-1.21.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7fdc7689daf3b845934d67cb221ba8d250fdca20ac0334fea32f7091b93f00d3"}, + {file = "numpy-1.21.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550564024dc5ceee9421a86fc0fb378aa9d222d4d0f858f6669eff7410c89bef"}, + {file = "numpy-1.21.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf75d5825ef47aa51d669b03ce635ecb84d69311e05eccea083f31c7570c9931"}, + {file = "numpy-1.21.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a9da45b748caad72ea4a4ed57e9cd382089f33c5ec330a804eb420a496fa760f"}, + {file = "numpy-1.21.2-cp38-cp38-win32.whl", hash = "sha256:e167b9805de54367dcb2043519382be541117503ce99e3291cc9b41ca0a83557"}, + {file = "numpy-1.21.2-cp38-cp38-win_amd64.whl", hash = "sha256:466e682264b14982012887e90346d33435c984b7fead7b85e634903795c8fdb0"}, + {file = "numpy-1.21.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:dd0e3651d210068d13e18503d75aaa45656eef51ef0b261f891788589db2cc38"}, + {file = "numpy-1.21.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92a0ab128b07799dd5b9077a9af075a63467d03ebac6f8a93e6440abfea4120d"}, + {file = "numpy-1.21.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fde50062d67d805bc96f1a9ecc0d37bfc2a8f02b937d2c50824d186aa91f2419"}, + {file = "numpy-1.21.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:640c1ccfd56724f2955c237b6ccce2e5b8607c3bc1cc51d3933b8c48d1da3723"}, + {file = "numpy-1.21.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5de64950137f3a50b76ce93556db392e8f1f954c2d8207f78a92d1f79aa9f737"}, + {file = "numpy-1.21.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b342064e647d099ca765f19672696ad50c953cac95b566af1492fd142283580f"}, + {file = "numpy-1.21.2-cp39-cp39-win32.whl", hash = "sha256:30fc68307c0155d2a75ad19844224be0f2c6f06572d958db4e2053f816b859ad"}, + {file = "numpy-1.21.2-cp39-cp39-win_amd64.whl", hash = "sha256:b5e8590b9245803c849e09bae070a8e1ff444f45e3f0bed558dd722119eea724"}, + {file = "numpy-1.21.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d96a6a7d74af56feb11e9a443150216578ea07b7450f7c05df40eec90af7f4a7"}, + {file = "numpy-1.21.2.zip", hash = "sha256:423216d8afc5923b15df86037c6053bf030d15cc9e3224206ef868c2d63dd6dc"}, +] +obspy = [ + {file = "obspy-1.2.2-cp27-cp27m-win32.whl", hash = "sha256:86e8e891a10258b1f37aa31122c69f62ad22f8d0a86a19a4da28c9efe686f541"}, + {file = "obspy-1.2.2-cp27-cp27m-win_amd64.whl", hash = "sha256:afac75ceb23c139ae974b326c683bdfff5de04d2887497e53de845394e46dc81"}, + {file = "obspy-1.2.2-cp35-cp35m-win32.whl", hash = "sha256:160312d65f883a6fe92dec103b288434a32d92e0e89b9d200d43dd71909f027f"}, + {file = "obspy-1.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:67ae3aab54551a9a0ee1b3d8b504b4b4648f55fc73672d50369ad7b636baa2e9"}, + {file = "obspy-1.2.2-cp36-cp36m-win32.whl", hash = "sha256:ce36135d01d59c1fff50b60b90b7c8d1ca8e1f7ac1b9ca53a0aadec487bc268d"}, + {file = "obspy-1.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:503b7eebbd8eb4479235d2271ab410f22b54d1e742ffdd662f93698655b28d35"}, + {file = "obspy-1.2.2-cp37-cp37m-win32.whl", hash = "sha256:0a512e87a4a27b4257846c58ff215857d68f5908dfa57cdae98ce6b7afa4da8c"}, + {file = "obspy-1.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c6ea8694d9af6ec1406b2643d4ffb77eec37f364445bfd23f51c50549c2c0eac"}, + {file = "obspy-1.2.2-cp38-cp38-win32.whl", hash = "sha256:31cab86fbf0a2e2e490dc871fe47fe44241f12637d58bdaab5fa3698ac9abe85"}, + {file = "obspy-1.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:5cb684a43e3d8e10f6176a715aa4c3d2460bec33a6be21fb58675868cf4e0d63"}, + {file = "obspy-1.2.2.zip", hash = "sha256:a0f2b0915beeb597762563fa0358aa1b4d6b09ffda49909c760b5cdf5bdc419e"}, +] +openpyxl = [ + {file = "openpyxl-3.0.7-py2.py3-none-any.whl", hash = "sha256:46af4eaf201a89b610fcca177eed957635f88770a5462fb6aae4a2a52b0ff516"}, + {file = "openpyxl-3.0.7.tar.gz", hash = "sha256:6456a3b472e1ef0facb1129f3c6ef00713cebf62e736cd7a75bcc3247432f251"}, +] +openpyxl-stubs = [ + {file = "openpyxl-stubs-0.1.19.tar.gz", hash = "sha256:6e803d07660b7e989cb165d9ba37c8e6062a22ce22b849a9325ef5e7e2a23b3c"}, + {file = "openpyxl_stubs-0.1.19-py3-none-any.whl", hash = "sha256:fbd0f38ff4a0b4c3e205752f268890d8d3542079ec26e954ecc9a764d3c9c407"}, +] +packaging = [ + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +pillow = [ + {file = "Pillow-8.3.1-1-cp36-cp36m-win_amd64.whl", hash = "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24"}, + {file = "Pillow-8.3.1-1-cp37-cp37m-win_amd64.whl", hash = "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a"}, + {file = "Pillow-8.3.1-1-cp38-cp38-win_amd64.whl", hash = "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093"}, + {file = "Pillow-8.3.1-1-cp39-cp39-win_amd64.whl", hash = "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77"}, + {file = "Pillow-8.3.1-1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c"}, + {file = "Pillow-8.3.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:196560dba4da7a72c5e7085fccc5938ab4075fd37fe8b5468869724109812edd"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c9569049d04aaacd690573a0398dbd8e0bf0255684fee512b413c2142ab723"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37"}, + {file = "Pillow-8.3.1-cp36-cp36m-win32.whl", hash = "sha256:a17ca41f45cf78c2216ebfab03add7cc350c305c38ff34ef4eef66b7d76c5229"}, + {file = "Pillow-8.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de"}, + {file = "Pillow-8.3.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0"}, + {file = "Pillow-8.3.1-cp37-cp37m-win32.whl", hash = "sha256:2b6dfa068a8b6137da34a4936f5a816aba0ecc967af2feeb32c4393ddd671cba"}, + {file = "Pillow-8.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500"}, + {file = "Pillow-8.3.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4"}, + {file = "Pillow-8.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:15a2808e269a1cf2131930183dcc0419bc77bb73eb54285dde2706ac9939fa8e"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844"}, + {file = "Pillow-8.3.1-cp38-cp38-win32.whl", hash = "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367"}, + {file = "Pillow-8.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8"}, + {file = "Pillow-8.3.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e"}, + {file = "Pillow-8.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b2efa07f69dc395d95bb9ef3299f4ca29bcb2157dc615bae0b42c3c20668ffc"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83"}, + {file = "Pillow-8.3.1-cp39-cp39-win32.whl", hash = "sha256:9a211b663cf2314edbdb4cf897beeb5c9ee3810d1d53f0e423f06d6ebbf9cd5d"}, + {file = "Pillow-8.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:114f816e4f73f9ec06997b2fde81a92cbf0777c9e8f462005550eed6bae57e63"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:147bd9e71fb9dcf08357b4d530b5167941e222a6fd21f869c7911bac40b9994d"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1fd5066cd343b5db88c048d971994e56b296868766e461b82fa4e22498f34d77"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c03e24be975e2afe70dfc5da6f187eea0b49a68bb2b69db0f30a61b7031cee4"}, + {file = "Pillow-8.3.1.tar.gz", hash = "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pycurl = [ + {file = "pycurl-7.44.1.tar.gz", hash = "sha256:5bcef4d988b74b99653602101e17d8401338d596b9234d263c728a0c3df003e8"}, +] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] +pymysql = [ + {file = "PyMySQL-0.9.3-py2.py3-none-any.whl", hash = "sha256:3943fbbbc1e902f41daf7f9165519f140c4451c179380677e6a848587042561a"}, + {file = "PyMySQL-0.9.3.tar.gz", hash = "sha256:d8c059dcd81dedb85a9f034d5e22dcb4442c0b201908bede99e306d65ea7c8e7"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, +] +pytest-cov = [ + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +python-dotenv = [ + {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, + {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, +] +python-editor = [ + {file = "python-editor-1.0.4.tar.gz", hash = "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b"}, + {file = "python_editor-1.0.4-py2-none-any.whl", hash = "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8"}, + {file = "python_editor-1.0.4-py2.7.egg", hash = "sha256:ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522"}, + {file = "python_editor-1.0.4-py3-none-any.whl", hash = "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d"}, + {file = "python_editor-1.0.4-py3.5.egg", hash = "sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +regex = [ + {file = "regex-2021.8.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b0c211c55d4aac4309c3209833c803fada3fc21cdf7b74abedda42a0c9dc3ce"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d5209c3ba25864b1a57461526ebde31483db295fc6195fdfc4f8355e10f7376"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c835c30f3af5c63a80917b72115e1defb83de99c73bc727bddd979a3b449e183"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:615fb5a524cffc91ab4490b69e10ae76c1ccbfa3383ea2fad72e54a85c7d47dd"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9966337353e436e6ba652814b0a957a517feb492a98b8f9d3b6ba76d22301dcc"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a49f85f0a099a5755d0a2cc6fc337e3cb945ad6390ec892332c691ab0a045882"}, + {file = "regex-2021.8.21-cp310-cp310-win32.whl", hash = "sha256:f93a9d8804f4cec9da6c26c8cfae2c777028b4fdd9f49de0302e26e00bb86504"}, + {file = "regex-2021.8.21-cp310-cp310-win_amd64.whl", hash = "sha256:a795829dc522227265d72b25d6ee6f6d41eb2105c15912c230097c8f5bfdbcdc"}, + {file = "regex-2021.8.21-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bca14dfcfd9aae06d7d8d7e105539bd77d39d06caaae57a1ce945670bae744e0"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41acdd6d64cd56f857e271009966c2ffcbd07ec9149ca91f71088574eaa4278a"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96f0c79a70642dfdf7e6a018ebcbea7ea5205e27d8e019cad442d2acfc9af267"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45f97ade892ace20252e5ccecdd7515c7df5feeb42c3d2a8b8c55920c3551c30"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f9974826aeeda32a76648fc677e3125ade379869a84aa964b683984a2dea9f1"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea9753d64cba6f226947c318a923dadaf1e21cd8db02f71652405263daa1f033"}, + {file = "regex-2021.8.21-cp36-cp36m-win32.whl", hash = "sha256:ef9326c64349e2d718373415814e754183057ebc092261387a2c2f732d9172b2"}, + {file = "regex-2021.8.21-cp36-cp36m-win_amd64.whl", hash = "sha256:6dbd51c3db300ce9d3171f4106da18fe49e7045232630fe3d4c6e37cb2b39ab9"}, + {file = "regex-2021.8.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a89ca4105f8099de349d139d1090bad387fe2b208b717b288699ca26f179acbe"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6c2b1d78ceceb6741d703508cd0e9197b34f6bf6864dab30f940f8886e04ade"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34ba9e39f8269fd66ab4f7a802794ffea6d6ac500568ec05b327a862c21ce23"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ecb6e7c45f9cd199c10ec35262b53b2247fb9a408803ed00ee5bb2b54aa626f5"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:330836ad89ff0be756b58758878409f591d4737b6a8cef26a162e2a4961c3321"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:71a904da8c9c02aee581f4452a5a988c3003207cb8033db426f29e5b2c0b7aea"}, + {file = "regex-2021.8.21-cp37-cp37m-win32.whl", hash = "sha256:b511c6009d50d5c0dd0bab85ed25bc8ad6b6f5611de3a63a59786207e82824bb"}, + {file = "regex-2021.8.21-cp37-cp37m-win_amd64.whl", hash = "sha256:93f9f720081d97acee38a411e861d4ce84cbc8ea5319bc1f8e38c972c47af49f"}, + {file = "regex-2021.8.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a195e26df1fbb40ebee75865f9b64ba692a5824ecb91c078cc665b01f7a9a36"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06ba444bbf7ede3890a912bd4904bb65bf0da8f0d8808b90545481362c978642"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8d551f1bd60b3e1c59ff55b9e8d74607a5308f66e2916948cafd13480b44a3"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ebbceefbffae118ab954d3cd6bf718f5790db66152f95202ebc231d58ad4e2c2"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccd721f1d4fc42b541b633d6e339018a08dd0290dc67269df79552843a06ca92"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ae87ab669431f611c56e581679db33b9a467f87d7bf197ac384e71e4956b4456"}, + {file = "regex-2021.8.21-cp38-cp38-win32.whl", hash = "sha256:38600fd58c2996829480de7d034fb2d3a0307110e44dae80b6b4f9b3d2eea529"}, + {file = "regex-2021.8.21-cp38-cp38-win_amd64.whl", hash = "sha256:61e734c2bcb3742c3f454dfa930ea60ea08f56fd1a0eb52d8cb189a2f6be9586"}, + {file = "regex-2021.8.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b091dcfee169ad8de21b61eb2c3a75f9f0f859f851f64fdaf9320759a3244239"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:640ccca4d0a6fcc6590f005ecd7b16c3d8f5d52174e4854f96b16f34c39d6cb7"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac95101736239260189f426b1e361dc1b704513963357dc474beb0f39f5b7759"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b79dc2b2e313565416c1e62807c7c25c67a6ff0a0f8d83a318df464555b65948"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b623fc429a38a881ab2d9a56ef30e8ea20c72a891c193f5ebbddc016e083ee"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8021dee64899f993f4b5cca323aae65aabc01a546ed44356a0965e29d7893c94"}, + {file = "regex-2021.8.21-cp39-cp39-win32.whl", hash = "sha256:d6ec4ae13760ceda023b2e5ef1f9bc0b21e4b0830458db143794a117fdbdc044"}, + {file = "regex-2021.8.21-cp39-cp39-win_amd64.whl", hash = "sha256:03840a07a402576b8e3a6261f17eb88abd653ad4e18ec46ef10c9a63f8c99ebd"}, + {file = "regex-2021.8.21.tar.gz", hash = "sha256:faf08b0341828f6a29b8f7dd94d5cf8cc7c39bfc3e67b78514c54b494b66915a"}, +] +requests = [ + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, +] +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] +safety = [ + {file = "safety-1.10.3-py2.py3-none-any.whl", hash = "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84"}, + {file = "safety-1.10.3.tar.gz", hash = "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5"}, +] +scipy = [ + {file = "scipy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2a0eeaab01258e0870c4022a6cd329aef3b7c6c2b606bd7cf7bb2ba9820ae561"}, + {file = "scipy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f52470e0548cdb74fb8ddf06773ffdcca7c97550f903b1c51312ec19243a7f7"}, + {file = "scipy-1.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:787749110a23502031fb1643c55a2236c99c6b989cca703ea2114d65e21728ef"}, + {file = "scipy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3304bd5bc32e00954ac4b3f4cc382ca8824719bf348aacbec6347337d6b125fe"}, + {file = "scipy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:d1388fbac9dd591ea630da75c455f4cc637a7ca5ecb31a6b6cef430914749cde"}, + {file = "scipy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d648aa85dd5074b1ed83008ae987c3fbb53d68af619fce1dee231f4d8bd40e2f"}, + {file = "scipy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc61e3e5ff92d2f32bb263621d54a9cff5e3f7c420af3d1fa122ce2529de2bd9"}, + {file = "scipy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a496b42dbcd04ea9924f5e92be63af3d8e0f43a274b769bfaca0a297327d54ee"}, + {file = "scipy-1.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d13f31457f2216e5705304d9f28e2826edf75487410a57aa99263fa4ffd792c2"}, + {file = "scipy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:90c07ba5f34f33299a428b0d4fa24c30d2ceba44d63f8385b2b05be460819fcb"}, + {file = "scipy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:efdd3825d54c58df2cc394366ca4b9166cf940a0ebddeb87b6c10053deb625ea"}, + {file = "scipy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:71cfc96297617eab911e22216e8a8597703202e95636d9406df9af5c2ac99a2b"}, + {file = "scipy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ee952f39a4a4c7ba775a32b664b1f4b74818548b65f765987adc14bb78f5802"}, + {file = "scipy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:611f9cb459d0707dd8e4de0c96f86e93f61aac7475fcb225e9ec71fecdc5cebf"}, + {file = "scipy-1.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e101bceeb9e65a90dadbc5ca31283403a2d4667b9c178db29109750568e8d112"}, + {file = "scipy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4729b41a4cdaf4cd011aeac816b532f990bdf97710cef59149d3e293115cf467"}, + {file = "scipy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:c9951e3746b68974125e5e3445008a4163dd6d20ae0bbdae22b38cb8951dc11b"}, + {file = "scipy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:da9c6b336e540def0b7fd65603da8abeb306c5fc9a5f4238665cbbb5ff95cf58"}, + {file = "scipy-1.7.1.tar.gz", hash = "sha256:6b47d5fa7ea651054362561a28b1ccc8da9368a39514c1bbf6c0977a1c376764"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sniffio = [ + {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, + {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, +] +sqlalchemy = [ + {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, + {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, +] +sqlalchemy-stubs = [ + {file = "sqlalchemy-stubs-0.4.tar.gz", hash = "sha256:c665d6dd4482ef642f01027fa06c3d5e91befabb219dc71fc2a09e7d7695f7ae"}, + {file = "sqlalchemy_stubs-0.4-py3-none-any.whl", hash = "sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5"}, +] +sqlalchemy-utc = [ + {file = "SQLAlchemy-Utc-0.12.0.tar.gz", hash = "sha256:c21a7a95d02f04c04fdcdcc24a89a9ae3031737d934e558fb2efe6fe72af9666"}, + {file = "SQLAlchemy_Utc-0.12.0-py2.py3-none-any.whl", hash = "sha256:1a30e52ee2e2e284e985bba3ecf28a998231ce754c7f97fff97a841a84bf1456"}, +] +starlette = [ + {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, + {file = "starlette-0.14.2.tar.gz", hash = "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, + {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] +typer = [ + {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, + {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, +] +types-python-dateutil = [ + {file = "types-python-dateutil-0.1.6.tar.gz", hash = "sha256:b02de39a54ce6e3fadfdc7dba77d8519fbfb6ca049920e190b5f89c74d5f9de6"}, + {file = "types_python_dateutil-0.1.6-py3-none-any.whl", hash = "sha256:5b6241ea9fca2d8878cc152017d9524da62a7a856b98e31006e68b02aab47442"}, +] +types-requests = [ + {file = "types-requests-2.25.6.tar.gz", hash = "sha256:e21541c0f55c066c491a639309159556dd8c5833e49fcde929c4c47bdb0002ee"}, + {file = "types_requests-2.25.6-py3-none-any.whl", hash = "sha256:a5a305b43ea57bf64d6731f89816946a405b591eff6de28d4c0fd58422cee779"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] +urllib3 = [ + {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, + {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, +] +uvicorn = [ + {file = "uvicorn-0.15.0-py3-none-any.whl", hash = "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1"}, + {file = "uvicorn-0.15.0.tar.gz", hash = "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"}, +] +uvloop = [ + {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, + {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"}, + {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"}, + {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9"}, + {file = "uvloop-0.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638"}, + {file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450"}, + {file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805"}, + {file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382"}, + {file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee"}, + {file = "uvloop-0.16.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464"}, + {file = "uvloop-0.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab"}, + {file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f"}, + {file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897"}, + {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f"}, + {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"}, + {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, +] +watchgod = [ + {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, + {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, +] +websockets = [ + {file = "websockets-9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da"}, + {file = "websockets-9.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b4ad84b156cf50529b8ac5cc1638c2cf8680490e3fccb6121316c8c02620a2e4"}, + {file = "websockets-9.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2cf04601633a4ec176b9cc3d3e73789c037641001dbfaf7c411f89cd3e04fcaf"}, + {file = "websockets-9.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5c8f0d82ea2468282e08b0cf5307f3ad022290ed50c45d5cb7767957ca782880"}, + {file = "websockets-9.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:caa68c95bc1776d3521f81eeb4d5b9438be92514ec2a79fececda814099c8314"}, + {file = "websockets-9.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d2c2d9b24d3c65b5a02cac12cbb4e4194e590314519ed49db2f67ef561c3cf58"}, + {file = "websockets-9.1-cp36-cp36m-win32.whl", hash = "sha256:f31722f1c033c198aa4a39a01905951c00bd1c74f922e8afc1b1c62adbcdd56a"}, + {file = "websockets-9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3ddff38894c7857c476feb3538dd847514379d6dc844961dc99f04b0384b1b1b"}, + {file = "websockets-9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:51d04df04ed9d08077d10ccbe21e6805791b78eac49d16d30a1f1fe2e44ba0af"}, + {file = "websockets-9.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f68c352a68e5fdf1e97288d5cec9296664c590c25932a8476224124aaf90dbcd"}, + {file = "websockets-9.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b43b13e5622c5a53ab12f3272e6f42f1ce37cd5b6684b2676cb365403295cd40"}, + {file = "websockets-9.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9147868bb0cc01e6846606cd65cbf9c58598f187b96d14dd1ca17338b08793bb"}, + {file = "websockets-9.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:836d14eb53b500fd92bd5db2fc5894f7c72b634f9c2a28f546f75967503d8e25"}, + {file = "websockets-9.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:48c222feb3ced18f3dc61168ca18952a22fb88e5eb8902d2bf1b50faefdc34a2"}, + {file = "websockets-9.1-cp37-cp37m-win32.whl", hash = "sha256:900589e19200be76dd7cbaa95e9771605b5ce3f62512d039fb3bc5da9014912a"}, + {file = "websockets-9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ab5ee15d3462198c794c49ccd31773d8a2b8c17d622aa184f669d2b98c2f0857"}, + {file = "websockets-9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85e701a6c316b7067f1e8675c638036a796fe5116783a4c932e7eb8e305a3ffe"}, + {file = "websockets-9.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b2e71c4670ebe1067fa8632f0d081e47254ee2d3d409de54168b43b0ba9147e0"}, + {file = "websockets-9.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:230a3506df6b5f446fed2398e58dcaafdff12d67fe1397dff196411a9e820d02"}, + {file = "websockets-9.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:7df3596838b2a0c07c6f6d67752c53859a54993d4f062689fdf547cb56d0f84f"}, + {file = "websockets-9.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:826ccf85d4514609219725ba4a7abd569228c2c9f1968e8be05be366f68291ec"}, + {file = "websockets-9.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0dd4eb8e0bbf365d6f652711ce21b8fd2b596f873d32aabb0fbb53ec604418cc"}, + {file = "websockets-9.1-cp38-cp38-win32.whl", hash = "sha256:1d0971cc7251aeff955aa742ec541ee8aaea4bb2ebf0245748fbec62f744a37e"}, + {file = "websockets-9.1-cp38-cp38-win_amd64.whl", hash = "sha256:7189e51955f9268b2bdd6cc537e0faa06f8fffda7fb386e5922c6391de51b077"}, + {file = "websockets-9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9e5fd6dbdf95d99bc03732ded1fc8ef22ebbc05999ac7e0c7bf57fe6e4e5ae2"}, + {file = "websockets-9.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9e7fdc775fe7403dbd8bc883ba59576a6232eac96dacb56512daacf7af5d618d"}, + {file = "websockets-9.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:597c28f3aa7a09e8c070a86b03107094ee5cdafcc0d55f2f2eac92faac8dc67d"}, + {file = "websockets-9.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:ad893d889bc700a5835e0a95a3e4f2c39e91577ab232a3dc03c262a0f8fc4b5c"}, + {file = "websockets-9.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:1d6b4fddb12ab9adf87b843cd4316c4bd602db8d5efd2fb83147f0458fe85135"}, + {file = "websockets-9.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:ebf459a1c069f9866d8569439c06193c586e72c9330db1390af7c6a0a32c4afd"}, + {file = "websockets-9.1-cp39-cp39-win32.whl", hash = "sha256:be5fd35e99970518547edc906efab29afd392319f020c3c58b0e1a158e16ed20"}, + {file = "websockets-9.1-cp39-cp39-win_amd64.whl", hash = "sha256:85db8090ba94e22d964498a47fdd933b8875a1add6ebc514c7ac8703eb97bbf0"}, + {file = "websockets-9.1.tar.gz", hash = "sha256:276d2339ebf0df4f45df453923ebd2270b87900eda5dfd4a6b0cfa15f82111c3"}, +] +zipp = [ + {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..a3d76cee9f83cbae8cda869070c42776043e40fe --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,73 @@ +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + + +[tool.poetry] +name = "geomag-algorithms" +authors = ["HazDev Team <gs-haz_dev_team_group@usgs.gov>"] +description = "USGS Geomag Algorithms Library" +homepage="https://code.usgs.gov/ghsc/geomag/geomag-algorithms" +include = [ + "LICENSE.md" +] +keywords = ["usgs", "geomag", "geomagnetism"] +license = "CC0-1.0" +packages = [ + {include = "geomagio" } +] +repository="https://code.usgs.gov/ghsc/geomag/geomag-algorithms" +version = "1.4.2" + + +[tool.poetry.dependencies] +python = "^3.7,<3.10" +# core +numpy = "^1.21.1" +obspy = "^1.2.2" +openpyxl = "^3.0.7" +pycurl = {version = "^7.44.1", optional = true} +pydantic = "^1.8.2" +requests = "^2.26.0" +scipy = "^1.7.1" +typer = "^0.3.2" +# webservice +alembic = "^1.6.5" +Authlib = "^0.15.4" +cryptography = "^3.4.7" +databases = {extras = ["mysql", "sqlite"], version = "^0.4.3"} +fastapi = "^0.68.0" +gunicorn = "^20.1.0" +SQLAlchemy = "^1.3.24" +SQLAlchemy-Utc = "^0.12.0" +typing-extensions = "^3.10.0" +uvicorn = {extras = ["standard"], version = "^0.15.0"} +httpx = "0.18.1" + + +[tool.poetry.dev-dependencies] +black = "^21.7b0" +pytest = "^6.2.4" +pytest-cov = "^2.12.1" +safety = "^1.10.3" +mypy = "^0.910" +# type hints +data-science-types = "^0.2.23" +openpyxl-stubs = "^0.1.19" +sqlalchemy-stubs = "^0.4" +types-python-dateutil = "^0.1.6" +types-requests = "^2.25.6" + + +[tool.poetry.extras] +pycurl = ["pycurl"] + + +[tool.poetry.scripts] +generate-matrix = "geomagio.processing.affine_matrix:main" +geomag-metadata = "geomagio.metadata.main:main" +geomag-monitor = "geomagio.processing.monitor:main" +geomag-py = "geomagio.Controller:main" +magproc-prepfiles = "geomagio.processing.magproc:main" +make-cal = "geomagio.processing.make_cal:main" +obsrio-filter = "geomagio.processing.obsrio:main" diff --git a/scripts/custom.config.sh b/scripts/custom.config.sh old mode 100644 new mode 100755 index 05ec4d3325ef208dc2ec58581e6f08ac25048f39..7f41fafdd0e122766611d31c0772913be01f61fd --- a/scripts/custom.config.sh +++ b/scripts/custom.config.sh @@ -1,10 +1,45 @@ +#!/bin/bash + +function get_ehp_server_ini { + local key=$1; shift; + + grep "${key}" "${EHP_SERVER_INI}" \ + | tail -n 1 \ + | cut -d '=' -f 2 \ + | tr -d ' ' \ + ; +} export SITE_URL="${SITE_URL_PREFIX}geomag${SITE_URL_SUFFIX}"; export BASE_HREF=${BASE_HREF:-ws}; export SERVICE_MAP=( - "/${BASE_HREF}":'web' + "/${BASE_HREF}:web" ); # Algorithms Environment Variables export DATA_HOST=${DATA_HOST:-cwbpub.cr.usgs.gov}; export DATA_PORT=${DATA_PORT:-2060}; export DATA_TYPE=${DATA_TYPE:-edge}; + +# Web Service Environment Variables + +export DATABASE_URL=${DATABASE_URL:-""} +if [ -f "${EHP_SERVER_INI}" ]; then + # when ehp server ini exists, read connection information + DB_HOST=$(get_ehp_server_ini 'restricted_database='); + DB_NAME=geomag_operations + DB_USER=$(get_ehp_server_ini 'mysql_web_absolutes_user='); + DB_PASS=$(get_ehp_server_ini 'mysql_web_absolutes_password='); + export DATABASE_URL="mysql://${DB_USER}:${DB_PASS}@${DB_HOST}/${DB_NAME}"; +fi + +export OPENID_CLIENT_ID=${OPENID_CLIENT_ID:-""} +export OPENID_CLIENT_SECRET=${OPENID_CLIENT_SECRET:-""} +export OPENID_METADATA_URL=${OPENID_METADATA_URL:-""} +export SECRET_KEY=${SECRET_KEY:-""} +export SECRET_SALT=${SECRET_SALT:-""} +export ADMIN_GROUP=${ADMIN_GROUP:-""} +export REVIEWER_GROUP=${REVIEWER_GROUP:-""} + +if [[ $TARGET_HOSTNAME == *"mage"* ]]; then + export DATA_HOST=${TARGET_HOSTNAME} +fi diff --git a/scripts/custom.funcs.sh b/scripts/custom.funcs.sh index 9082bc665a1d61e99f9836d063e306699a2e92fb..e6a75db4625ef407a17fb24cc824a33995a9b034 100644 --- a/scripts/custom.funcs.sh +++ b/scripts/custom.funcs.sh @@ -5,6 +5,71 @@ preStackDeployHook () { writeYmlFile; } + +## +# override the default updateRouting, which adds a trailing slash... +## +updateRouting () { + local appName=$1; shift; + local stackName=$1; shift; + local serviceMap=$@; + + local dir="$(pwd -P)"; + local stamp=$(date); + + local configFile="${dir}/${appName}.conf"; + local serverFile="${dir}/${appName}.server"; + local upstreamIdx=0; + + debug "Re-routing traffic to ${stackName} stack."; + echo "# Auto generated ${stamp} for ${stackName}" > $configFile; + echo "# Auto generated ${stamp} for ${stackName}" > $serverFile; + + for service in ${serviceMap[@]}; do + local name="${stackName}_$(echo $service | awk -F: '{print $2}')"; + local upstreamName="${name}_${upstreamIdx}"; + local path="$(echo $service | awk -F: '{print $1}')"; + local port=$(getPublishedPort $name 2> /dev/null); + + if [ -z "${port}" ]; then + # No port exposed. Continue. + debug "No port exposed for ${name}. Not routing. Moving on."; + continue; + fi + + echo "upstream ${upstreamName} {" >> $configFile; + for host in ${TARGET_HOSTS[@]}; do + echo " server ${host}:${port};" >> $configFile; + done + echo "}" >> $configFile; + + cat <<- EO_SERVER_SNIP >> $serverFile + # do not include trailing slash here, map can if needed + location ${path} { + proxy_pass http://${upstreamName}; + proxy_connect_timeout 5s; + proxy_set_header Host \$host; + proxy_set_header X-Client-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + # keep redirects on https + proxy_redirect off; + } + EO_SERVER_SNIP + # ^^ Note: TAB indentation required + + upstreamIdx=$((upstreamIdx + 1)); + done + + if [ $(configsDiffer ${appName} ${configFile} ${serverFile}) ]; then + debug "Updating configuration for ${appName}"; + routerConfig --update ${appName} ${configFile} ${serverFile}; + else + debug "${appName} configuration not changed. Skipping router update."; + fi +} + + ## # Write a customized YML file for deploying the stack. Necessary because # by default, YML files do not allow variables for defining configs. @@ -34,9 +99,17 @@ services: - 8000 environment: - BASE_HREF=${BASE_HREF} - - DATA_HOST=${TARGET_HOSTNAME} + - DATA_HOST=${DATA_HOST} - DATA_PORT=${DATA_PORT} - DATA_TYPE=${DATA_TYPE} + - DATABASE_URL=${DATABASE_URL} + - OPENID_CLIENT_ID=${OPENID_CLIENT_ID} + - OPENID_CLIENT_SECRET=${OPENID_CLIENT_SECRET} + - OPENID_METADATA_URL=${OPENID_METADATA_URL} + - SECRET_KEY=${SECRET_KEY} + - SECRET_SALT=${SECRET_SALT} + - ADMIN_GROUP=${ADMIN_GROUP} + - REVIEWER_GROUP=${REVIEWER_GROUP} - SITE_URL=${SITE_URL} - WEBSERVICE=true EO_YML diff --git a/setup.py b/setup.py deleted file mode 100644 index 29212231e0bda9dc9e4064d9a6bbde16dcf1ca39..0000000000000000000000000000000000000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -import os -import setuptools -import setuptools.ssl_support - -# configure ssl certifiate bundle from environment, if set -ssl_cert_file = os.environ.get("SSL_CERT_FILE") or os.environ.get("PIP_CERT") -if ssl_cert_file: - setuptools.ssl_support.cert_paths = [ssl_cert_file] - -setuptools.setup( - name="geomag-algorithms", - version="1.0.0", - description="USGS Geomag Algorithms Library", - url="https://github.com/usgs/geomag-algorithms", - packages=setuptools.find_packages(exclude=["test*"]), - project_urls={ - "Bug Reports": "https://github.com/usgs/geomag-algorithms/issues", - "Source": "https://github.com/usgs/geomag-algorithms", - }, - python_requires=">=3.6, <4", - scripts=["bin/geomag.py", "bin/geomag_webservice.py", "bin/make_cal.py"], - setup_requires=[ - "setuptools-pipfile", - ], - use_pipfile=True, - entry_points={ - "console_scripts": ["magproc-prepfiles=geomagio.processing.magproc:main"], - }, -) diff --git a/test/DerivedTimeseriesFactory_test.py b/test/DerivedTimeseriesFactory_test.py new file mode 100644 index 0000000000000000000000000000000000000000..debb0b213fa3b747e15e9f51b5540738cb5a9508 --- /dev/null +++ b/test/DerivedTimeseriesFactory_test.py @@ -0,0 +1,121 @@ +from typing import List + +from obspy import Stream + +from geomagio import TimeseriesUtility +from geomagio.algorithm import Algorithm, DeltaFAlgorithm, XYZAlgorithm +from geomagio.DerivedTimeseriesFactory import DerivedTimeseriesFactory, get_missing +from geomagio.iaga2002 import StreamIAGA2002Factory +from geomagio.edge import EdgeFactory + + +def test_derive_trace(): + """test.DerivedTimeseriesFactory_test.test_derive_trace()""" + timeseries = get_derived_timeseries( + "etc/filter/BOU20200101vsec.sec", ["H", "E", "Z", "F"], "variation", "second" + ) + factory = DerivedTimeseriesFactory(EdgeFactory()) + assert factory._derive_trace( + input_timeseries=timeseries, channel="G", data_type="variation" + ) == DeltaFAlgorithm(informat="obs").process(timeseries=timeseries) + assert factory._derive_trace( + input_timeseries=timeseries, channel="X", data_type="variation" + ) == XYZAlgorithm(informat="obs", outformat="geo").process(timeseries=timeseries) + assert factory._derive_trace( + input_timeseries=timeseries, channel="Y", data_type="variation" + ) == XYZAlgorithm(informat="obs", outformat="geo").process(timeseries=timeseries) + assert factory._derive_trace( + input_timeseries=timeseries, channel="D", data_type="variation" + ) == XYZAlgorithm(informat="obs", outformat="obsd").process(timeseries=timeseries) + timeseries = get_derived_timeseries( + "etc/adjusted/BOU201601adj.min", ["X", "Y", "Z", "F"], "adjusted", "minute" + ) + assert factory._derive_trace( + input_timeseries=timeseries, channel="G", data_type="adjusted" + ) == DeltaFAlgorithm(informat="geo").process(timeseries=timeseries) + assert factory._derive_trace( + input_timeseries=timeseries, channel="H", data_type="adjusted" + ) == XYZAlgorithm(informat="geo", outformat="mag").process(timeseries=timeseries) + assert factory._derive_trace( + input_timeseries=timeseries, channel="D", data_type="adjusted" + ) == XYZAlgorithm(informat="geo", outformat="mag").process(timeseries=timeseries) + + +def test_get_derived_input_channels(): + """test.DerivedTimeseriesFactory_test.test_get_derived_input_channels()""" + factory = DerivedTimeseriesFactory(EdgeFactory(host=None, port=None)) + assert factory._get_derived_input_channels(channel="G", data_type="variation") == [ + "H", + "E", + "Z", + "F", + ] + assert factory._get_derived_input_channels(channel="G", data_type="adjusted") == [ + "X", + "Y", + "Z", + "F", + ] + assert factory._get_derived_input_channels(channel="X", data_type="variation") == [ + "H", + "E", + ] + assert factory._get_derived_input_channels(channel="H", data_type="adjusted") == [ + "X", + "Y", + ] + # invalid channel, should return empty list + assert factory._get_derived_input_channels(channel="Q", data_type="variation") == [] + + +def test_get_timeseries(): + """test.DerivedTimeseriesFactory_test.test_get_timeseries()""" + variation_url = "etc/filter/BOU20200101vsec.sec" + timeseries = get_derived_timeseries( + variation_url, ["H", "E", "Z", "F"], "variation", "second" + ) + assert TimeseriesUtility.get_channels(timeseries) == ["H", "E", "Z", "F"] + timeseries = get_derived_timeseries(variation_url, ["G"], "variation", "second") + assert TimeseriesUtility.get_channels(timeseries) == ["G"] + timeseries = get_derived_timeseries( + variation_url, ["X", "Y"], "variation", "second" + ) + assert set(TimeseriesUtility.get_channels(timeseries)) == set(["X", "Y"]) + adjusted_url = "etc/adjusted/BOU201601adj.min" + timeseries = get_derived_timeseries( + adjusted_url, ["X", "Y", "Z", "F"], "adjusted", "minute" + ) + assert TimeseriesUtility.get_channels(timeseries) == ["X", "Y", "Z", "F"] + timeseries = get_derived_timeseries(adjusted_url, ["G"], "adjusted", "minute") + assert TimeseriesUtility.get_channels(timeseries) == ["G"] + timeseries = get_derived_timeseries(adjusted_url, ["H", "D"], "adjusted", "minute") + assert set(TimeseriesUtility.get_channels(timeseries)) == set(["H", "D"]) + + +def test_get_missing(): + """test.DerivedTimeseriesFactory_test.test_get_missing()""" + desired = ["X", "Y", "D", "G"] + assert set(get_missing(input=Stream(), desired=desired)) == set(desired) + desired = ["H", "E", "Z", "F"] + timeseries = get_derived_timeseries( + "etc/filter/BOU20200101vsec.sec", desired, "variation", "second" + ) + assert get_missing(input=timeseries, desired=desired) == [] + + +def get_derived_timeseries( + url: str, channels: List[str], data_type: str, interval: str +) -> Stream: + with open(url, "r") as file: + return DerivedTimeseriesFactory( + StreamIAGA2002Factory(stream=file) + ).get_timeseries( + starttime=None, + endtime=None, + observatory="BOU", + channels=channels, + interval=interval, + add_empty_channels=False, + derive_missing=True, + type=data_type, + ) diff --git a/test/ObservatoryMetadata_test.py b/test/ObservatoryMetadata_test.py index 902abffbbb523c61581055f2d0eb4e5a27b920d5..ca3d5ada427824296aef9b8547a27b8c828a5448 100644 --- a/test/ObservatoryMetadata_test.py +++ b/test/ObservatoryMetadata_test.py @@ -1,6 +1,6 @@ """Tests for ObservatoryMetadata.py""" -from geomagio import ObservatoryMetadata +from geomagio.ObservatoryMetadata import ObservatoryMetadata, DEFAULT_INTERVAL_SPECIFIC from numpy.testing import assert_equal import obspy.core @@ -22,26 +22,16 @@ METADATA = { + " through INTERMAGNET and acknowledgement templates" + " can be found at www.intermagnet.org", }, - "interval_specific": { - "minute": { - "data_interval_type": "filtered 1-minute (00:15-01:45) ", - "filter_comments": [ - "Vector 1-minute values are computed" - + " from 1-second values using the INTERMAGNET gaussian" - + " filter centered on the minute. Scalar 1-minute values" - + " are computed from 1-second values using the" - + " INTERMAGNET gaussian filter centered on the minute. " - ], - }, - "second": {"data_interval_type": "Average 1-Second"}, - }, + "interval_specific": DEFAULT_INTERVAL_SPECIFIC, } } DATA_INTERVAL_TYPE = { - "minute": {"data_interval_type": "filtered 1-minute (00:29-01:30) "}, - "second": {"data_interval_type": "filtered 1-Second"}, + "day": {"data_interval_type": "1-day"}, + "hour": {"data_interval_type": "1-hour"}, + "minute": {"data_interval_type": "1-minute"}, + "second": {"data_interval_type": "1-second"}, } @@ -65,4 +55,4 @@ def test_set_metadata(): observatorymetadata.set_metadata(stats, "BOU", "MVH", "quasi-definitive", "second") assert_equal(stats["declination_base"], 20000) print(stats) - assert_equal(stats["data_interval_type"], "Average 1-Second") + assert_equal(stats["data_interval_type"], "1-second") diff --git a/test/TimeseriesUtility_test.py b/test/TimeseriesUtility_test.py index 827b0afeced6bc9e7a80c0cb75f48bc3cf98ee34..0259364ed35d9a5ce0b58a4b7d6379a7dd6e48ec 100644 --- a/test/TimeseriesUtility_test.py +++ b/test/TimeseriesUtility_test.py @@ -55,6 +55,49 @@ def test_create_empty_trace(): TimeseriesUtility.pad_timeseries(timeseries, starttime - 90, endtime + 90) assert_equal(len(trace3.data), trace3.stats.npts) assert_equal(timeseries[0].stats.starttime, timeseries[2].stats.starttime) + # test hourly/daily starttime shift + hour_trace = TimeseriesUtility.create_empty_trace( + starttime=trace1.stats.starttime, + endtime=trace1.stats.endtime, + observatory=observatory, + channel="F", + type="variation", + interval="hour", + network=network, + station=trace1.stats.station, + location=location, + ) + + assert_equal(hour_trace.stats.starttime, UTCDateTime("2018-01-01T00:29:30Z")) + + day_trace = TimeseriesUtility.create_empty_trace( + starttime=trace1.stats.starttime, + endtime=trace1.stats.endtime, + observatory=observatory, + channel="F", + type="variation", + interval="day", + network=network, + station=trace1.stats.station, + location=location, + ) + + assert_equal(day_trace.stats.starttime, UTCDateTime("2018-01-01T11:59:30Z")) + + short_trace = TimeseriesUtility.create_empty_trace( + starttime=trace1.stats.starttime, + endtime=trace1.stats.starttime + 1, + observatory=observatory, + channel="F", + type="variation", + interval="day", + network=network, + station=trace1.stats.station, + location=location, + ) + + assert_equal(short_trace.stats.starttime, UTCDateTime("2018-01-01T11:59:30Z")) + assert_equal(short_trace.stats.endtime, short_trace.stats.starttime) def test_get_stream_gaps(): @@ -449,6 +492,22 @@ def test_pad_and_trim_trace_fixfloat(): ) +def test_round_usecs(): + """TimeseriesUtility_test.test_round_usecs() + This tests whether microsecond values are rounded or + not depending on residual microsecond values + """ + # test case with no residual microseconds + time = TimeseriesUtility.round_usecs(UTCDateTime("2020-10-07T00:00:00Z")) + assert_equal(time, UTCDateTime("2020-10-07T00:00:00Z")) + # test case with residual microseconds + time = TimeseriesUtility.round_usecs(UTCDateTime("2020-10-07T00:00:00.995600Z")) + assert_equal(time, UTCDateTime("2020-10-07T00:00:00.996000Z")) + # test case with rounding to next second + time = TimeseriesUtility.round_usecs(UTCDateTime("2020-10-07T00:00:00.9995Z")) + assert_equal(time, UTCDateTime("2020-10-07T00:00:01.000Z")) + + def _create_trace(data, channel, starttime, delta=60.0): stats = Stats() stats.channel = channel diff --git a/test/WebService_test.py b/test/WebService_test.py deleted file mode 100644 index 99c5e0ad50c517a27d92ffbad46de19ae5ec0092..0000000000000000000000000000000000000000 --- a/test/WebService_test.py +++ /dev/null @@ -1,165 +0,0 @@ -"""Unit Tests for WebService""" -from urllib.parse import parse_qs -from datetime import datetime -from numpy.testing import assert_equal, assert_raises -import numpy -import webtest - -from geomagio.WebService import _get_param -from geomagio.WebService import WebService -import obspy.core -from obspy.core.stream import Stream -from obspy.core.utcdatetime import UTCDateTime - - -class TestFactory(object): - "Factory to test for 200 and 400 response statuses." - - @staticmethod - def get_timeseries( - observatory=None, - channels=None, - starttime=None, - endtime=None, - type=None, - interval=None, - ): - stream = obspy.core.Stream() - for channel in channels: - stats = obspy.core.Stats() - stats.channel = channel - stats.starttime = starttime - stats.network = "Test" - stats.station = observatory - stats.location = observatory - if interval == "second": - stats.sampling_rate = 1.0 - elif interval == "minute": - stats.sampling_rate = 1.0 / 60.0 - elif interval == "hourly": - stats.sampling_rate = 1.0 / 3600.0 - elif interval == "daily": - stats.sampling_rate = 1.0 / 86400.0 - length = int((endtime - starttime) * stats.sampling_rate) - stats.npts = length + 1 - data = numpy.full(length, numpy.nan, dtype=numpy.float64) - trace = obspy.core.Trace(data, stats) - stream.append(trace) - return stream - - -class ErrorFactory(object): - "Factory to test for 500 response status." - - @staticmethod - def get_timeseries( - observatory=None, - channels=None, - starttime=None, - endtime=None, - type=None, - interval=None, - ): - pass - - -def test__get_param(): - """WebService_test.test__get_param() - - Call function _get_param to make certain it gets back - the appropriate values and raises exceptions for invalid values. - """ - params = { - "id": None, - "elements": "H,E,Z,F", - "sampling_period": ["1", "60"], - } - assert_raises(Exception, _get_param, params, "id", required=True) - elements = _get_param(params, "elements") - assert_equal(elements, "H,E,Z,F") - assert_raises(Exception, _get_param, params, "sampling_period") - - -def test_fetch(): - """WebService_test.test_fetch()) - - Call function WebService.fetch to confirm tht it returns an - obspy.core.stream object. - """ - service = WebService(TestFactory()) - query = service.parse( - parse_qs( - "id=BOU&starttime=2016-06-06" - "&endtime=2016-06-07&elements=H,E,Z,F&sampling_period=60" - "&format=iaga2002&type=variation" - ) - ) - timeseries = service.fetch(query) - assert_equal(isinstance(timeseries, Stream), True) - - -def test_parse(): - """WebService_test.test_parse() - - Create WebService instance and call parse to confirm that query - string values are applied to the correct class attribute. Also - confirm that default values are applied correctly. - """ - service = WebService(TestFactory()) - query = service.parse( - parse_qs( - "id=BOU&starttime=2016-06-06" - "&endtime=2016-06-07&elements=H,E,Z,F&sampling_period=60" - "&format=iaga2002&type=variation" - ) - ) - assert_equal(query.observatory_id, "BOU") - assert_equal(query.starttime, UTCDateTime(2016, 6, 6, 0)) - assert_equal(query.endtime, UTCDateTime(2016, 6, 7, 0)) - assert_equal(query.elements, ["H", "E", "Z", "F"]) - assert_equal(query.sampling_period, "60") - assert_equal(query.output_format, "iaga2002") - assert_equal(query.data_type, "variation") - # Test that defaults are set for unspecified values - now = datetime.now() - today = UTCDateTime(year=now.year, month=now.month, day=now.day, hour=0) - tomorrow = today + (24 * 60 * 60 - 1) - query = service.parse(parse_qs("id=BOU")) - assert_equal(query.observatory_id, "BOU") - assert_equal(query.starttime, today) - assert_equal(query.endtime, tomorrow) - assert_equal(query.elements, ("X", "Y", "Z", "F")) - assert_equal(query.sampling_period, "60") - assert_equal(query.output_format, "iaga2002") - assert_equal(query.data_type, "variation") - assert_raises(Exception, service.parse, parse_qs("/?id=bad")) - - -def test_requests(): - """WebService_test.test_requests() - - Use TestApp to confirm correct response status, status int, - and content-type. - """ - app = webtest.TestApp(WebService(TestFactory())) - # Check invalid request (bad values) - response = app.get("/?id=bad", expect_errors=True) - assert_equal(response.status_int, 400) - assert_equal(response.status, "400 Bad Request") - assert_equal(response.content_type, "text/plain") - # Check invalid request (duplicates) - response = app.get("/?id=BOU&id=BOU", expect_errors=True) - assert_equal(response.status_int, 400) - assert_equal(response.status, "400 Bad Request") - assert_equal(response.content_type, "text/plain") - # Check valid request (upper and lower case) - response = app.get("/?id=BOU") - assert_equal(response.status_int, 200) - assert_equal(response.status, "200 OK") - assert_equal(response.content_type, "text/plain") - # Test internal server error (use fake factory) - app = webtest.TestApp(WebService(ErrorFactory(), error_stream=None)) - response = app.get("/?id=BOU", expect_errors=True) - assert_equal(response.status_int, 500) - assert_equal(response.status, "500 Internal Server Error") - assert_equal(response.content_type, "text/plain") diff --git a/test/adjusted_test/adjusted_test.py b/test/adjusted_test/adjusted_test.py new file mode 100644 index 0000000000000000000000000000000000000000..2ac3747660af93332bdfc4cd17c6fe29e106d666 --- /dev/null +++ b/test/adjusted_test/adjusted_test.py @@ -0,0 +1,511 @@ +import json + +import numpy as np +from numpy.testing import assert_equal, assert_array_almost_equal +from obspy.core import UTCDateTime +import pytest + +from geomagio.adjusted import AdjustedMatrix +from geomagio.adjusted.Affine import Affine, get_epochs +from geomagio.adjusted.transform import ( + LeastSq, + QRFactorization, + Rescale3D, + RotationTranslationXY, + SVD, + ShearYZ, + TranslateOrigins, + ZRotationHscale, + TranslateOrigins, + ZRotationHscaleZbaseline, + ZRotationShear, +) +from test.residual_test.residual_test import ( + get_json_readings, + get_spreadsheet_directory_readings, +) + + +def format_result(result) -> dict: + Ms = [] + for M in result: + m = [] + for row in M: + m.append(list(row)) + Ms.append(m) + return Ms + + +def get_excpected_matrices(observatory, key): + with open(f"etc/adjusted/{observatory}_expected.json", "r") as file: + expected = json.load(file) + return expected[key] + + +def get_expected_synthetic_result(key): + with open("etc/adjusted/synthetic.json") as file: + expected = json.load(file) + return expected["results"][key] + + +def get_sythetic_variables(): + with open("etc/adjusted/synthetic.json") as file: + data = json.load(file) + variables = data["variables"] + ordinates = np.array([variables["h_ord"], variables["e_ord"], variables["z_ord"]]) + absolutes = np.array([variables["x_abs"], variables["y_abs"], variables["z_abs"]]) + weights = np.arange(0, len(ordinates[0])) + return ordinates, absolutes, weights + + +def test_BOU201911202001_infinite_one_interval(): + readings = get_json_readings("etc/residual/BOU20191001.json") + result = Affine( + observatory="BOU", + starttime=UTCDateTime("2019-11-01T00:00:00Z"), + endtime=UTCDateTime("2020-01-31T23:59:00Z"), + transforms=[ + RotationTranslationXY(memory=np.inf, acausal=True), + TranslateOrigins(memory=np.inf, acausal=True), + ], + update_interval=None, + ).calculate( + readings=readings, + ) + + matrices = format_result([adjusted_matrix.matrix for adjusted_matrix in result]) + expected_matrices = get_excpected_matrices("BOU", "inf_one_interval") + for i in range(len(matrices)): + assert_array_almost_equal( + matrices[i], + expected_matrices[i], + decimal=3, + err_msg=f"Matrix {i} not equal", + ) + assert_equal(len(matrices), 1) + + +def test_BOU201911202001_infinite_weekly(): + readings = get_json_readings("etc/residual/BOU20191001.json") + + starttime = UTCDateTime("2019-11-01T00:00:00Z") + endtime = UTCDateTime("2020-01-31T23:59:00Z") + + update_interval = 86400 * 7 + + result = Affine( + observatory="BOU", + starttime=starttime, + endtime=endtime, + update_interval=update_interval, + transforms=[ + RotationTranslationXY(memory=np.inf, acausal=True), + TranslateOrigins(memory=np.inf, acausal=True), + ], + ).calculate( + readings=readings, + ) + + matrices = format_result([adjusted_matrix.matrix for adjusted_matrix in result]) + expected_matrices = get_excpected_matrices("BOU", "inf_weekly") + for i in range(len(matrices)): + assert_array_almost_equal( + matrices[i], + expected_matrices[i], + decimal=3, + err_msg=f"Matrix {i} not equal", + ) + assert_equal(len(matrices), ((endtime - starttime) // update_interval) + 1) + + +def test_BOU201911202001_invalid_readings(): + starttime = UTCDateTime("2019-11-01T00:00:00Z") + with pytest.raises( + ValueError, match=f"No valid observations for: {starttime}" + ) as error: + readings = [] + result = Affine( + observatory="BOU", + starttime=starttime, + endtime=UTCDateTime("2020-01-31T23:59:00Z"), + transforms=[ + RotationTranslationXY(memory=np.inf, acausal=True), + TranslateOrigins(memory=np.inf, acausal=True), + ], + update_interval=None, + ).calculate( + readings=readings, + ) + + +def test_BOU201911202001_short_acausal(): + readings = get_json_readings("etc/residual/BOU20191001.json") + + starttime = UTCDateTime("2019-11-01T00:00:00Z") + endtime = UTCDateTime("2020-01-31T23:59:00Z") + + update_interval = 86400 * 7 + + result = Affine( + observatory="BOU", + starttime=starttime, + endtime=endtime, + update_interval=update_interval, + transforms=[ + RotationTranslationXY(memory=(86400 * 100), acausal=True), + TranslateOrigins(memory=(86400 * 10), acausal=True), + ], + ).calculate( + readings=readings, + ) + + matrices = format_result([adjusted_matrix.matrix for adjusted_matrix in result]) + expected_matrices = get_excpected_matrices("BOU", "short_acausal") + for i in range(len(matrices)): + assert_array_almost_equal( + matrices[i], + expected_matrices[i], + decimal=3, + err_msg=f"Matrix {i} not equal", + ) + assert_equal(len(matrices), ((endtime - starttime) // update_interval) + 1) + + +def test_BOU201911202001_short_causal(): + readings = get_json_readings("etc/residual/BOU20191001.json") + + starttime = UTCDateTime("2019-11-01T00:00:00Z") + endtime = UTCDateTime("2020-01-31T23:59:00Z") + + update_interval = 86400 * 7 + + result = Affine( + observatory="BOU", + starttime=starttime, + endtime=endtime, + update_interval=update_interval, + transforms=[ + RotationTranslationXY(memory=(86400 * 100), acausal=False), + TranslateOrigins(memory=(86400 * 10), acausal=False), + ], + ).calculate(readings=readings) + + matrices = format_result([adjusted_matrix.matrix for adjusted_matrix in result]) + expected_matrices = get_excpected_matrices("BOU", "short_causal") + for i in range(len(matrices)): + assert_array_almost_equal( + matrices[i], + expected_matrices[i], + decimal=3, + err_msg=f"Matrix {i} not equal", + ) + assert_equal(len(matrices), ((endtime - starttime) // update_interval) + 1) + + +def test_CMO2015_infinite_one_interval(): + readings = get_spreadsheet_directory_readings( + observatory="CMO", + starttime=UTCDateTime("2015-01-01T00:00:00Z"), + endtime=UTCDateTime("2015-12-31T23:59:00Z"), + path="etc/residual/Caldata/", + ) + + assert len(readings) == 146 + + result = Affine( + observatory="CMO", + starttime=UTCDateTime("2015-02-01T00:00:00Z"), + endtime=UTCDateTime("2015-11-27T23:59:00Z"), + transforms=[ + RotationTranslationXY(memory=np.inf, acausal=True), + TranslateOrigins(memory=np.inf, acausal=True), + ], + acausal=True, + update_interval=None, + ).calculate( + readings=readings, + ) + + matrices = format_result([adjusted_matrix.matrix for adjusted_matrix in result]) + expected_matrices = get_excpected_matrices("CMO", "inf_one_interval") + for i in range(len(matrices)): + assert_array_almost_equal( + matrices[i], + expected_matrices[i], + decimal=3, + err_msg=f"Matrix {i} not equal", + ) + + assert_equal(len(matrices), 1) + + +def test_CMO2015_infinite_weekly(): + readings = get_spreadsheet_directory_readings( + observatory="CMO", + starttime=UTCDateTime("2015-01-01T00:00:00Z"), + endtime=UTCDateTime("2015-12-31T23:59:00Z"), + path="etc/residual/Caldata/", + ) + assert len(readings) == 146 + + starttime = UTCDateTime("2015-02-01T00:00:00Z") + endtime = UTCDateTime("2015-11-27T23:59:00Z") + + update_interval = 86400 * 7 + + result = Affine( + observatory="CMO", + starttime=starttime, + endtime=endtime, + transforms=[ + RotationTranslationXY(memory=np.inf, acausal=True), + TranslateOrigins(memory=np.inf, acausal=True), + ], + update_interval=update_interval, + acausal=True, + ).calculate( + readings=readings, + ) + + matrices = format_result([adjusted_matrix.matrix for adjusted_matrix in result]) + expected_matrices = get_excpected_matrices("CMO", "inf_weekly") + for i in range(len(matrices)): + assert_array_almost_equal( + matrices[i], + expected_matrices[i], + decimal=3, + err_msg=f"Matrix {i} not equal", + ) + assert_equal(len(matrices), ((endtime - starttime) // update_interval) + 1) + + +def test_CMO2015_short_acausal(): + readings = get_spreadsheet_directory_readings( + observatory="CMO", + starttime=UTCDateTime("2015-01-01T00:00:00Z"), + endtime=UTCDateTime("2015-12-31T23:59:00Z"), + path="etc/residual/Caldata/", + ) + assert len(readings) == 146 + + starttime = UTCDateTime("2015-02-01T00:00:00Z") + endtime = UTCDateTime("2015-11-27T23:59:00Z") + + update_interval = 86400 * 7 + + result = Affine( + observatory="CMO", + starttime=starttime, + endtime=endtime, + update_interval=update_interval, + transforms=[ + RotationTranslationXY(memory=(86400 * 100), acausal=True), + TranslateOrigins(memory=(86400 * 10), acausal=True), + ], + ).calculate( + readings=readings, + ) + + matrices = format_result([adjusted_matrix.matrix for adjusted_matrix in result]) + expected_matrices = get_excpected_matrices("CMO", "short_acausal") + for i in range(len(matrices)): + assert_array_almost_equal( + matrices[i], + expected_matrices[i], + decimal=3, + err_msg=f"Matrix {i} not equal", + ) + assert_equal(len(matrices), ((endtime - starttime) // update_interval) + 1) + + +def test_CMO2015_short_causal(): + readings = get_spreadsheet_directory_readings( + observatory="CMO", + starttime=UTCDateTime("2015-01-01T00:00:00Z"), + endtime=UTCDateTime("2015-12-31T23:59:00Z"), + path="etc/residual/Caldata/", + ) + assert len(readings) == 146 + + starttime = UTCDateTime("2015-02-01T00:00:00Z") + endtime = UTCDateTime("2015-11-27T23:59:00Z") + + update_interval = 86400 * 7 + + result = Affine( + observatory="CMO", + starttime=starttime, + endtime=endtime, + update_interval=update_interval, + transforms=[ + RotationTranslationXY(memory=(86400 * 100), acausal=False), + TranslateOrigins(memory=(86400 * 10), acausal=False), + ], + ).calculate( + readings=readings, + ) + + matrices = format_result([adjusted_matrix.matrix for adjusted_matrix in result]) + expected_matrices = get_excpected_matrices("CMO", "short_causal") + for i in range(len(matrices)): + assert_array_almost_equal( + matrices[i], + expected_matrices[i], + decimal=3, + err_msg=f"Matrix {i} not equal", + ) + assert_equal(len(matrices), ((endtime - starttime) // update_interval) + 1) + + +def test_get_epochs(): + readings = get_json_readings("etc/residual/BOU20200101.json") + # force a bad measurement for second reading + readings[2].absolutes[1].absolute = 0 + epochs = [r.time for r in readings if r.get_absolute("H").absolute == 0] + assert len(epochs) == 1 + epoch_start, epoch_end = get_epochs( + epochs=epochs, + time=UTCDateTime("2019-11-01T00:00:00Z"), + ) + assert epoch_start is None + assert epoch_end == readings[2].time + epoch_start, epoch_end = get_epochs( + epochs=epochs, + time=UTCDateTime("2020-01-07T00:00:00Z"), + ) + assert epoch_start == readings[2].time + assert epoch_end is None + epoch_start, epoch_end = get_epochs( + epochs=epochs, + time=UTCDateTime("2020-01-07T00:00:00Z"), + ) + + +def test_LeastSq_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + LeastSq().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("LeastSq"), + decimal=3, + ) + + +def test_QRFactorization_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + QRFactorization().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("QRFactorization"), + decimal=3, + ) + + +def test_Rescale3D_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + Rescale3D().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("Rescale3D"), + decimal=3, + ) + + +def test_RotationTranslationXY_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + RotationTranslationXY().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("RotationTranslationXY"), + decimal=3, + ) + + +def test_ShearYZ_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + ShearYZ().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("ShearYZ"), + decimal=3, + ) + + +def test_SVD_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + SVD().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("SVD"), + decimal=3, + ) + + +def test_TranslateOrigins_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + TranslateOrigins().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("TranslateOrigins"), + decimal=3, + ) + + +def test_ZRotationHscale_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + ZRotationHscale().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("ZRotationHscale"), + decimal=3, + ) + + +def test_ZRotationHscaleZbaseline_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + ZRotationHscaleZbaseline().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("ZRotationHscaleZbaseline"), + decimal=3, + ) + + +def test_ZRotationShear_synthetic(): + ordinates, absolutes, weights = get_sythetic_variables() + assert_array_almost_equal( + ZRotationShear().calculate( + ordinates=ordinates, + absolutes=absolutes, + weights=weights, + ), + get_expected_synthetic_result("ZRotationShear"), + decimal=3, + ) diff --git a/test/algorithm_test/AdjustedAlgorithm_test.py b/test/algorithm_test/AdjustedAlgorithm_test.py index f37f93f78893e6f13b22e50923724bca0b4ee260..220d498c6f1bf6a65e59243523cd73789b6dc95e 100644 --- a/test/algorithm_test/AdjustedAlgorithm_test.py +++ b/test/algorithm_test/AdjustedAlgorithm_test.py @@ -1,26 +1,61 @@ -from geomagio.algorithm import AdjustedAlgorithm as adj +from geomagio.adjusted import AdjustedMatrix +from geomagio.algorithm import AdjustedAlgorithm import geomagio.iaga2002 as i2 -from numpy.testing import assert_almost_equal, assert_equal +from numpy.testing import assert_almost_equal, assert_array_equal, assert_equal def test_construct(): """algorithm_test.AdjustedAlgorithm_test.test_construct()""" # load adjusted data transform matrix and pier correction - a = adj(statefile="etc/adjusted/adjbou_state_.json") + a = AdjustedAlgorithm(statefile="etc/adjusted/adjbou_state_.json") - assert_almost_equal(actual=a.matrix[0, 0], desired=9.83427577e-01, decimal=6) + assert_almost_equal(actual=a.matrix.matrix[0][0], desired=9.83427577e-01, decimal=6) - assert_equal(actual=a.pier_correction, desired=-22) + assert_equal(actual=a.matrix.pier_correction, desired=-22) -def test_process_XYZF(): - """algorithm_test.AdjustedAlgorithm_test.test_process() +def assert_streams_almost_equal(adjusted, expected, channels): + for channel in channels: + assert_almost_equal( + actual=adjusted.select(channel=channel)[0].data, + desired=expected.select(channel=channel)[0].data, + decimal=2, + ) + + +def test_process_XYZF_AdjustedMatrix(): + """algorithm_test.AdjustedAlgorithm_test.test_process_XYZF_AdjustedMatrix() Check adjusted data processing versus files generated from original script """ - # load adjusted data transform matrix and pier correction - a = adj(statefile="etc/adjusted/adjbou_state_.json") + # Initiate algorithm with AdjustedMatrix object + a = AdjustedAlgorithm( + matrix=AdjustedMatrix( + matrix=[ + [ + 0.9834275767090617, + -0.15473074200902157, + 0.027384986324932026, + -1276.164681191976, + ], + [ + 0.16680172992706568, + 0.987916201012128, + -0.0049868332295851525, + -0.8458192581350419, + ], + [ + -0.006725053082782385, + -0.011809351484171948, + 0.9961869012493976, + 905.3800885796844, + ], + [0, 0, 0, 1], + ], + pier_correction=-22, + ) + ) # load boulder Jan 16 files from /etc/ directory with open("etc/adjusted/BOU201601vmin.min") as f: @@ -31,37 +66,80 @@ def test_process_XYZF(): # process hezf (raw) channels with loaded transform adjusted = a.process(raw) - # compare channels from adjusted and expected streams - assert_almost_equal( - actual=adjusted.select(channel="X")[0].data, - desired=expected.select(channel="X")[0].data, - decimal=2, + assert_streams_almost_equal( + adjusted=adjusted, expected=expected, channels=["X", "Y", "Z", "F"] ) - assert_almost_equal( - actual=adjusted.select(channel="Y")[0].data, - desired=expected.select(channel="Y")[0].data, - decimal=2, + + +def test_process_reverse_polarity_AdjustedMatrix(): + """algorithm_test.AdjustedAlgorithm_test.test_process_reverse_polarity_AdjustedMatrix() + + Check adjusted data processing versus files generated from + original script. Tests reverse polarity martix. + """ + # Initiate algorithm with AdjustedMatrix object + a = AdjustedAlgorithm( + matrix=AdjustedMatrix( + matrix=[ + [-1, 0, 0], + [0, -1, 0], + [0, 0, 1], + ], + pier_correction=-22, + ), + inchannels=["H", "E"], + outchannels=["H", "E"], ) - assert_almost_equal( - actual=adjusted.select(channel="Z")[0].data, - desired=expected.select(channel="Z")[0].data, - decimal=2, + + # load boulder May 20 files from /etc/ directory + with open("etc/adjusted/BOU202005vmin.min") as f: + raw = i2.IAGA2002Factory().parse_string(f.read()) + with open("etc/adjusted/BOU202005adj.min") as f: + expected = i2.IAGA2002Factory().parse_string(f.read()) + + # process he(raw) channels with loaded transform + adjusted = a.process(raw) + + assert_streams_almost_equal( + adjusted=adjusted, expected=expected, channels=["H", "E"] ) - assert_almost_equal( - actual=adjusted.select(channel="F")[0].data, - desired=expected.select(channel="F")[0].data, - decimal=2, + + +def test_process_XYZF_statefile(): + """algorithm_test.AdjustedAlgorithm_test.test_process_XYZF_statefile() + + Check adjusted data processing versus files generated from + original script + + Uses statefile to generate AdjustedMatrix + """ + # load adjusted data transform matrix and pier correction + a = AdjustedAlgorithm(statefile="etc/adjusted/adjbou_state_.json") + + # load boulder Jan 16 files from /etc/ directory + with open("etc/adjusted/BOU201601vmin.min") as f: + raw = i2.IAGA2002Factory().parse_string(f.read()) + with open("etc/adjusted/BOU201601adj.min") as f: + expected = i2.IAGA2002Factory().parse_string(f.read()) + + # process hezf (raw) channels with loaded transform + adjusted = a.process(raw) + + assert_streams_almost_equal( + adjusted=adjusted, expected=expected, channels=["X", "Y", "Z", "F"] ) -def test_process_reverse_polarity(): - """algorithm_test.AdjustedAlgorithm_test.test_process() +def test_process_reverse_polarity_statefile(): + """algorithm_test.AdjustedAlgorithm_test.test_process_reverse_polarity_statefile() Check adjusted data processing versus files generated from original script. Tests reverse polarity martix. + + Uses statefile to generate AdjustedMatrix """ # load adjusted data transform matrix and pier correction - a = adj( + a = AdjustedAlgorithm( statefile="etc/adjusted/adjbou_state_HE_.json", inchannels=["H", "E"], outchannels=["H", "E"], @@ -76,14 +154,24 @@ def test_process_reverse_polarity(): # process he(raw) channels with loaded transform adjusted = a.process(raw) - # compare channels from adjusted and expected streams - assert_almost_equal( - actual=adjusted.select(channel="H")[0].data, - desired=expected.select(channel="H")[0].data, - decimal=2, - ) - assert_almost_equal( - actual=adjusted.select(channel="E")[0].data, - desired=expected.select(channel="E")[0].data, - decimal=2, + assert_streams_almost_equal( + adjusted=adjusted, expected=expected, channels=["H", "E"] ) + + +def test_process_no_statefile(): + """algorithm_test.AdjustedAlgorithm_test.test_process_no_statefile() + + Check adjusted data processing versus raw data + + Uses default AdjustedMatrix with identity transform + """ + # initialize adjusted algorithm with no statefile + a = AdjustedAlgorithm() + # load boulder Jan 16 files from /etc/ directory + with open("etc/adjusted/BOU201601vmin.min") as f: + raw = i2.IAGA2002Factory().parse_string(f.read()) + # process hezf (raw) channels with identity transform + adjusted = a.process(raw) + for i in range(len(adjusted)): + assert_array_equal(adjusted[i].data, raw[i].data) diff --git a/test/algorithm_test/FilterAlgorithm_test.py b/test/algorithm_test/FilterAlgorithm_test.py index d0062d357b80a1887c713fdc930053bab7972322..b4c21d45afc0581d78d340d7310cdf6cdeb1e30a 100644 --- a/test/algorithm_test/FilterAlgorithm_test.py +++ b/test/algorithm_test/FilterAlgorithm_test.py @@ -3,8 +3,9 @@ import json from numpy.testing import assert_almost_equal, assert_equal import numpy as np from obspy import read, UTCDateTime +import pytest -from geomagio.algorithm import FilterAlgorithm +from geomagio.algorithm.FilterAlgorithm import FilterAlgorithm, get_nearest_time import geomagio.iaga2002 as i2 @@ -48,6 +49,10 @@ def test_second(): assert_almost_equal(u_filt.data, u.data, 2) assert_almost_equal(v_filt.data, v.data, 2) assert_almost_equal(w_filt.data, w.data, 2) + assert_equal(filtered[0].stats.starttime, UTCDateTime("2020-01-06T00:00:00Z")) + assert_equal(filtered[0].stats.endtime, UTCDateTime("2020-01-06T04:00:00Z")) + assert_equal(u_filt.stats.data_interval, "second") + assert_equal(u_filt.stats.data_interval_type, "1-second") def test_minute(): @@ -89,47 +94,102 @@ def test_minute(): assert_almost_equal(u_filt.data, u.data, 2) assert_almost_equal(v_filt.data, v.data, 2) assert_almost_equal(w_filt.data, w.data, 2) + assert_equal(filtered[0].stats.starttime, UTCDateTime("2020-01-06T00:00:00Z")) + assert_equal(filtered[0].stats.endtime, UTCDateTime("2020-01-06T04:00:00Z")) + assert_equal(filtered[0].stats.data_interval, "minute") + assert_equal(filtered[0].stats.data_interval_type, "1-minute") def test_hour(): """algorithm_test.FilterAlgorithm_test.test_hour() - Tests algorithm for 10Hz to hour. + Tests algorithm for 1min to hour. """ - f = FilterAlgorithm(input_sample_period=0.1, output_sample_period=3600.0) + f = FilterAlgorithm(input_sample_period=60.0, output_sample_period=3600.0) - # generation of 10HZ_filter_hor.mseed - # starttime = UTCDateTime('2020-01-06T00:00:00Z') - # endtime = UTCDateTime('2020-01-06T04:00:00Z') - # m = MiniSeedFactory(port=2061, host='...', - # convert_channels=['U', 'V', 'W']) - # f = FilterAlgorithm(input_sample_period=0.1, + # generation of hor_filter_min.mseed + # starttime = UTCDateTime("2020-08-31T00:00:00Z") + # endtime = UTCDateTime("2020-08-31T03:00:00Z") + # e = EdgeFactory() + # f = FilterAlgorithm(input_sample_period=60.0, # output_sample_period=3600.0) # starttime, endtime = f.get_input_interval(starttime,endtime) - # LLO = m.get_timeseries(observatory='LLO', + # BOU = e.get_timeseries(observatory='BOU', # starttime=starttime,endtime=endtime, - # channels=['U_Volt', 'U_Bin', 'V_Volt', - # 'V_Bin', 'W_Volt', 'W_Bin'], - # interval='tenhertz', type='variaton') - # LLO.write('10HZ_filter_hor.mseed') + # channels=["H", "E", "Z", "F"], + # interval="minute", type='variaton') + # LLO.write('hour_filter_min.mseed') - llo = read("etc/filter/10HZ_filter_hor.mseed") - filtered = f.process(llo) + bou = read("etc/filter/hor_filter_min.mseed") + filtered = f.process(bou) - with open("etc/filter/LLO20200106vhor.hor", "r") as f: + with open("etc/filter/BOU20200831vhor.hor", "r") as f: iaga = i2.StreamIAGA2002Factory(stream=f) - LLO = iaga.get_timeseries(starttime=None, endtime=None, observatory="LLO") + BOU = iaga.get_timeseries(starttime=None, endtime=None, observatory="BOU") - u = LLO.select(channel="U")[0] - v = LLO.select(channel="V")[0] - w = LLO.select(channel="W")[0] + h = BOU.select(channel="H")[0] + e = BOU.select(channel="E")[0] + z = BOU.select(channel="Z")[0] + f = BOU.select(channel="F")[0] - u_filt = filtered.select(channel="U")[0] - v_filt = filtered.select(channel="V")[0] - w_filt = filtered.select(channel="W")[0] + h_filt = filtered.select(channel="H")[0] + e_filt = filtered.select(channel="E")[0] + z_filt = filtered.select(channel="Z")[0] + f_filt = filtered.select(channel="F")[0] + + assert_almost_equal(h_filt.data, h.data, 2) + assert_almost_equal(e_filt.data, e.data, 2) + assert_almost_equal(z_filt.data, z.data, 2) + assert_almost_equal(f_filt.data, f.data, 2) + assert_equal(filtered[0].stats.starttime, UTCDateTime("2020-08-31T00:29:30")) + assert_equal(filtered[0].stats.endtime, UTCDateTime("2020-08-31T03:29:30")) + assert_equal(filtered[0].stats.data_interval, "hour") + assert_equal(filtered[0].stats.data_interval_type, "1-hour (00-59)") - assert_almost_equal(u_filt.data, u.data, 2) - assert_almost_equal(v_filt.data, v.data, 2) - assert_almost_equal(w_filt.data, w.data, 2) + +def test_day(): + """algorithm_test.FilterAlgorithm_test.test_hour() + Tests algorithm for 1min to day. + """ + f = FilterAlgorithm(input_sample_period=60.0, output_sample_period=86400.0) + + # generation of day_filter_min.mseed + # starttime = UTCDateTime("2020-08-27T00:00:00Z") + # endtime = UTCDateTime("2020-08-30T00:00:00Z") + # e = EdgeFactory() + # f = FilterAlgorithm(input_sample_period=60.0, + # output_sample_period=86400.0) + # starttime, endtime = f.get_input_interval(starttime,endtime) + # BOU = e.get_timeseries(observatory='BOU', + # starttime=starttime,endtime=endtime, + # channels=["H", "E", "Z", "F"], + # interval="minute", type='variaton') + # LLO.write('day_filter_min.mseed') + + bou = read("etc/filter/day_filter_min.mseed") + filtered = f.process(bou) + + with open("etc/filter/BOU20200831vday.day", "r") as f: + iaga = i2.StreamIAGA2002Factory(stream=f) + BOU = iaga.get_timeseries(starttime=None, endtime=None, observatory="BOU") + + h = BOU.select(channel="H")[0] + e = BOU.select(channel="E")[0] + z = BOU.select(channel="Z")[0] + f = BOU.select(channel="F")[0] + + h_filt = filtered.select(channel="H")[0] + e_filt = filtered.select(channel="E")[0] + z_filt = filtered.select(channel="Z")[0] + f_filt = filtered.select(channel="F")[0] + + assert_almost_equal(h_filt.data, h.data, 2) + assert_almost_equal(e_filt.data, e.data, 2) + assert_almost_equal(z_filt.data, z.data, 2) + assert_almost_equal(f_filt.data, f.data, 2) + assert_equal(filtered[0].stats.starttime, UTCDateTime("2020-08-27T11:59:30")) + assert_equal(filtered[0].stats.endtime, UTCDateTime("2020-08-30T11:59:30")) + assert_equal(filtered[0].stats.data_interval, "day") + assert_equal(filtered[0].stats.data_interval_type, "1-day (00:00-23:59)") def test_custom(): @@ -175,6 +235,10 @@ def test_custom(): assert_almost_equal(u_filt.data, u.data, 2) assert_almost_equal(v_filt.data, v.data, 2) assert_almost_equal(w_filt.data, w.data, 2) + assert_equal(filtered[0].stats.starttime, UTCDateTime("2020-01-06T00:00:00Z")) + assert_equal(filtered[0].stats.endtime, UTCDateTime("2020-01-06T04:00:00Z")) + assert_equal(filtered[0].stats.data_interval, "second") + assert_equal(filtered[0].stats.data_interval_type, "filtered custom interval") def test_starttime_shift(): @@ -216,9 +280,62 @@ def test_starttime_shift(): assert_equal(filtered[0].stats.endtime, UTCDateTime("2020-01-01T00:13:00Z")) -def test_prepare_step(): - """algorithm_test.FilterAlgorithm_test.test_custom() - Tests algorithm for 10Hz to second with custom filter coefficients. +def test_align_trace(): + """algorithm_test.FilterAlgorithm_test.test_align_trace() + Tests algorithm for minute to hour with expected behavior, trailing samples, and missing samples + """ + f = FilterAlgorithm(input_sample_period=60.0, output_sample_period=3600.0) + bou = read("etc/filter/hor_filter_min.mseed") + step = f.get_filter_steps()[0] + # check intial assumptions + starttime, _ = f.align_trace(step, bou[0]) + assert_equal(starttime, UTCDateTime("2020-08-31T00:29:30")) + # check for filtered product producing the correct interval with trailing samples + trimmed = bou.copy().trim( + starttime=UTCDateTime("2020-08-31T01:00:00"), + endtime=UTCDateTime("2020-08-31T02:04:00"), + ) + starttime, _ = f.align_trace(step, trimmed[0]) + assert_equal(starttime, UTCDateTime("2020-08-31T01:29:30")) + # test for skipped sample when not enough data is given for first interval + trimmed = bou.copy().trim( + starttime=UTCDateTime("2020-08-31T01:30:00"), endtime=bou[0].stats.endtime + ) + starttime, _ = f.align_trace(step, trimmed[0]) + assert_equal(starttime, UTCDateTime("2020-08-31T02:29:30")) + + +def test_get_nearest__oneday_average(): + """algorithm_test.FilterAlgorithm_test.test_get_nearest__oneday_average() + Tests get_nearest_time for minute to day + """ + f = FilterAlgorithm(input_sample_period=60.0, output_sample_period=86400.0) + step = f.get_filter_steps()[0] + time = UTCDateTime("2020-08-20T01:00:00") + aligned = get_nearest_time(step=step, output_time=time) + # filter is average for day, should be first/last minute samples of 2020-08-20 + assert_equal(aligned["data_start"], UTCDateTime("2020-08-20T00:00:00")) + assert_equal(aligned["time"], UTCDateTime("2020-08-20T11:59:30")) + assert_equal(aligned["data_end"], UTCDateTime("2020-08-20T23:59:00")) + + +def test_get_nearest__intermagnet_minute(): + """algorithm_test.FilterAlgorithm_test.test_get_nearest__intermagnet_minute() + Tests get_nearest_time for second to minute + """ + f = FilterAlgorithm(input_sample_period=1.0, output_sample_period=60.0) + step = f.get_filter_steps()[0] + time = UTCDateTime("2020-08-20T01:00:13") + aligned = get_nearest_time(step=step, output_time=time) + # filter uses 91 samples, should be 01:00:00 +/- 45 seconds + assert_equal(aligned["data_start"], UTCDateTime("2020-08-20T00:59:15")) + assert_equal(aligned["time"], UTCDateTime("2020-08-20T01:00:00")) + assert_equal(aligned["data_end"], UTCDateTime("2020-08-20T01:00:45")) + + +def test_validate_step(): + """algorithm_test.FilterAlgorithm_test.test_validate_steps() + Validates algorithm steps 10 Hz to second with custom coefficients. """ with open("etc/filter/coeffs.json", "rb") as f: step = json.loads(f.read()) @@ -227,14 +344,12 @@ def test_prepare_step(): half = numtaps // 2 # check initial assumption assert_equal(numtaps % 2, 1) - # expect step to be unchanged when window has odd length - unchanged = f._prepare_step(step) - assert_equal(unchanged, step) - # expect step to be extended when window has event length - even_step = {"window": np.delete(step["window"], numtaps // 2, 0)} - assert_equal(len(even_step["window"]) % 2, 0) - prepared = f._prepare_step(step) - assert_equal(len(prepared["window"]) % 2, 1) - # value is inserted in middle - assert_equal(prepared["window"][: half + 1], step["window"][: half + 1]) - assert_equal(prepared["window"][half:], step["window"][half:]) + f._validate_step(step) + # expect step to raise a value error when window has an even length + step = { + "window": np.delete(step["window"], numtaps // 2, 0), + "type": "firfilter", + } + assert_equal(len(step["window"]) % 2, 0) + with pytest.raises(ValueError): + f._validate_step(step) diff --git a/test/api_test/__init__.py b/test/api_test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/api_test/ws_test/__init__.py b/test/api_test/ws_test/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/api_test/ws_test/data_test.py b/test/api_test/ws_test/data_test.py new file mode 100644 index 0000000000000000000000000000000000000000..800b0cd783e46078dd0f757cffaec8bd1793bc13 --- /dev/null +++ b/test/api_test/ws_test/data_test.py @@ -0,0 +1,24 @@ +from numpy.testing import assert_equal +from obspy import UTCDateTime + +from geomagio.api.ws.data import get_data_query +from geomagio.api.ws.DataApiQuery import OutputFormat, SamplingPeriod + + +def test_get_data_query(): + query = get_data_query( + id="BOU", + starttime="2020-09-01T00:00:01", + endtime=None, + elements=["X,Y,Z,F"], + data_type="R1", + sampling_period=60, + format="iaga2002", + ) + assert_equal(query.id, "BOU") + assert_equal(query.starttime, UTCDateTime("2020-09-01T00:00:01")) + assert_equal(query.endtime, UTCDateTime("2020-09-02T00:00:00.999")) + assert_equal(query.elements, ["X", "Y", "Z", "F"]) + assert_equal(query.sampling_period, SamplingPeriod.MINUTE) + assert_equal(query.format, OutputFormat.IAGA2002) + assert_equal(query.data_type, "R1") diff --git a/test/edge_test/EdgeFactory_test.py b/test/edge_test/EdgeFactory_test.py index fca38a8c8df6fb8cd08a7717ff2114f5e7f5daf3..52a4f662f5fbb21aa483ffe34b8819e2d55cb6ce 100644 --- a/test/edge_test/EdgeFactory_test.py +++ b/test/edge_test/EdgeFactory_test.py @@ -5,63 +5,7 @@ from geomagio.edge import EdgeFactory from numpy.testing import assert_equal -def test__get_edge_network(): - """edge_test.EdgeFactory_test.test__get_edge_network()""" - # _get_edge_network should always return NT for use by USGS geomag - assert_equal(EdgeFactory()._get_edge_network(" ", " ", " ", " "), "NT") - - -def test__get_edge_station(): - """edge_test.EdgeFactory_test.test__get_edge_station()""" - # _get_edge_station will return the observatory code passed in. - assert_equal(EdgeFactory()._get_edge_station("BOU", " ", " ", " "), "BOU") - - -def test__get_edge_channel(): - """edge_test.EdgeFactory_test.test__get_edge_channel()""" - # Call private function _get_edge_channel, make certain - # it gets back the appropriate 2 character code. - assert_equal(EdgeFactory()._get_edge_channel("", "D", "", "minute"), "MVD") - assert_equal(EdgeFactory()._get_edge_channel("", "E", "", "minute"), "MVE") - assert_equal(EdgeFactory()._get_edge_channel("", "F", "", "minute"), "MSF") - assert_equal(EdgeFactory()._get_edge_channel("", "H", "", "minute"), "MVH") - assert_equal(EdgeFactory()._get_edge_channel("", "DIST", "", "minute"), "MDT") - assert_equal(EdgeFactory()._get_edge_channel("", "DST", "", "minute"), "MGD") - assert_equal(EdgeFactory()._get_edge_channel("", "E-E", "", "minute"), "MQE") - assert_equal(EdgeFactory()._get_edge_channel("", "E-N", "", "minute"), "MQN") - - -def test__get_edge_location(): - """edge_test.EdgeFactory_test.test__get_edge_location()""" - # Call _get_edge_location, make certain it returns the correct edge - # location code. - assert_equal(EdgeFactory()._get_edge_location("", "", "variation", ""), "R0") - assert_equal(EdgeFactory()._get_edge_location("", "", "quasi-definitive", ""), "Q0") - assert_equal(EdgeFactory()._get_edge_location("", "", "definitive", ""), "D0") - - -def test__get_interval_code(): - """edge_test.EdgeFactory_test.test__get_interval_code()""" - assert_equal(EdgeFactory()._get_interval_code("day"), "D") - assert_equal(EdgeFactory()._get_interval_code("hour"), "H") - assert_equal(EdgeFactory()._get_interval_code("minute"), "M") - assert_equal(EdgeFactory()._get_interval_code("second"), "S") - - -def test__set_metadata(): - """edge_test.EdgeFactory_test.test__set_metadata()""" - # Call _set_metadata with 2 traces, and make certain the stats get - # set for both traces. - trace1 = Trace() - trace2 = Trace() - stream = Stream(traces=[trace1, trace2]) - EdgeFactory()._set_metadata(stream, "BOU", "H", "variation", "minute") - assert_equal(stream[0].stats["channel"], "H") - assert_equal(stream[1].stats["channel"], "H") - - -# def test_get_timeseries(): -def dont_get_timeseries(): +def test_get_timeseries(): """edge_test.EdgeFactory_test.test_get_timeseries()""" # Call get_timeseries, and test stats for comfirmation that it came back. # TODO, need to pass in host and port from a config file, or manually @@ -85,3 +29,90 @@ def dont_get_timeseries(): "H", "Expect timeseries stats channel to be equal to H", ) + assert_equal( + timeseries.select(channel="H")[0].stats.data_type, + "variation", + "Expect timeseries stats data_type to be equal to variation", + ) + + +def test_get_timeseries_by_location(): + """test.edge_test.EdgeFactory_test.test_get_timeseries_by_location()""" + edge_factory = EdgeFactory(host="TODO", port="TODO") + timeseries = edge_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("H"), + "R0", + "minute", + ) + assert_equal( + timeseries.select(channel="H")[0].stats.data_type, + "R0", + "Expect timeseries stats data_type to be equal to R0", + ) + timeseries = edge_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("H"), + "A0", + "minute", + ) + assert_equal( + timeseries.select(channel="H")[0].stats.data_type, + "A0", + "Expect timeseries stats data_type to be equal to A0", + ) + timeseries = edge_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("X"), + "Q0", + "minute", + ) + assert_equal( + timeseries.select(channel="X")[0].stats.data_type, + "Q0", + "Expect timeseries stats data_type to be equal to Q0", + ) + timeseries = edge_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("X"), + "D0", + "minute", + ) + assert_equal( + timeseries.select(channel="X")[0].stats.data_type, + "D0", + "Expect timeseries stats data_type to be equal to D0", + ) + + +def test_add_empty_channels(): + """edge_test.EdgeFactory_test.test_add_empty_channels()""" + edge_factory = EdgeFactory(host="TODO", port="TODO") + timeseries = edge_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("H"), + "variation", + "minute", + add_empty_channels=False, + ) + assert len(timeseries) == 0 + timeseries = edge_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("H"), + "variation", + "minute", + add_empty_channels=True, # default + ) + assert len(timeseries) == 1 diff --git a/test/edge_test/LegacySNCL_test.py b/test/edge_test/LegacySNCL_test.py new file mode 100644 index 0000000000000000000000000000000000000000..190dc09a9a20376f6a3cd2aeb49c754d37bf41b1 --- /dev/null +++ b/test/edge_test/LegacySNCL_test.py @@ -0,0 +1,203 @@ +from geomagio.edge.LegacySNCL import LegacySNCL, get_channel, get_location + + +def test_data_type(): + """edge_test.LegacySNCL_test.test_data_type()""" + assert ( + LegacySNCL(station="BOU", channel="LFU", location="R0").data_type == "variation" + ) + assert ( + LegacySNCL(station="BOU", channel="LFU", location="A0").data_type == "adjusted" + ) + assert ( + LegacySNCL(station="BOU", channel="LFU", location="Q0").data_type + == "quasi-definitive" + ) + assert ( + LegacySNCL(station="BOU", channel="LFU", location="D0").data_type + == "definitive" + ) + + +def test_element(): + """edge_test.LegacySNCL_test.test_element()""" + assert ( + LegacySNCL( + station="BOU", + channel="MVD", + location="R0", + ).element + == "D" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MVU", + location="R0", + ).element + == "U" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MSF", + location="R0", + ).element + == "F" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MVH", + location="R0", + ).element + == "H" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MQE", + location="R0", + ).element + == "E-E" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MQN", + location="R0", + ).element + == "E-N" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MEH", + location="R0", + ).element + == "H_Volt" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MYH", + location="R0", + ).element + == "H_Bin" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MVH", + location="R1", + ).element + == "H_Sat" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MDT", + location="R0", + ).element + == "DIST" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MGD", + location="R0", + ).element + == "DST" + ) + + +def test_get_channel(): + """edge_test.LegacySNCL_test.test_get_channel()""" + assert get_channel(element="D", interval="second") == "SVD" + assert get_channel(element="F", interval="minute") == "MSF" + assert get_channel(element="H", interval="hour") == "HVH" + assert get_channel(element="E-E", interval="day") == "DQE" + assert get_channel(element="E-N", interval="minute") == "MQN" + assert get_channel(element="SQ", interval="minute") == "MSQ" + assert get_channel(element="SV", interval="minute") == "MSV" + assert get_channel(element="UK1", interval="minute") == "UK1" + assert get_channel(element="DIST", interval="minute") == "MDT" + assert get_channel(element="DST", interval="minute") == "MGD" + assert get_channel(element="UK1.R0", interval="minute") == "UK1" + + +def test_get_location(): + """edge_test.LegacySNCL_test.test_get_location()""" + assert get_location(element="D", data_type="variation") == "R0" + assert get_location(element="D", data_type="adjusted") == "A0" + assert get_location(element="D", data_type="quasi-definitive") == "Q0" + assert get_location(element="D", data_type="definitive") == "D0" + assert get_location(element="D_Sat", data_type="variation") == "R1" + assert get_location(element="D_Sat", data_type="adjusted") == "A1" + assert get_location(element="D", data_type="R0") == "R0" + assert get_location(element="D", data_type="A0") == "A0" + assert get_location(element="D", data_type="Q0") == "Q0" + assert get_location(element="D", data_type="D0") == "D0" + assert get_location(element="D", data_type="R1") == "R1" + assert get_location(element="D", data_type="A1") == "A1" + + +def test_get_sncl(): + """edge_test.LegacySNCL_test.test_get_sncl()""" + assert LegacySNCL.get_sncl( + station="BOU", data_type="variation", interval="second", element="H" + ) == LegacySNCL(station="BOU", network="NT", channel="SVH", location="R0") + assert LegacySNCL.get_sncl( + station="BOU", data_type="R0", interval="second", element="H" + ) == LegacySNCL(station="BOU", network="NT", channel="SVH", location="R0") + + +def test_interval(): + """edge_test.LegacySNCL_test.test_interval()""" + assert ( + LegacySNCL( + station="BOU", + channel="SVH", + location="R0", + data_format="legacy", + ).interval + == "second" + ) + assert ( + LegacySNCL( + station="BOU", + channel="MVH", + location="R0", + data_format="legacy", + ).interval + == "minute" + ) + assert ( + LegacySNCL( + station="BOU", + channel="HVH", + location="R0", + data_format="legacy", + ).interval + == "hour" + ) + assert ( + LegacySNCL( + station="BOU", + channel="DVH", + location="R0", + data_format="legacy", + ).interval + == "day" + ) + + +def test_parse_sncl(): + """edge_test.LegacySNCL_test.test_parse_sncl()""" + assert LegacySNCL(station="BOU", channel="MVH", location="R0").parse_sncl() == { + "station": "BOU", + "network": "NT", + "data_type": "variation", + "element": "H", + "interval": "minute", + } diff --git a/test/edge_test/MiniSeedFactory_test.py b/test/edge_test/MiniSeedFactory_test.py index 24397dbcbde8e20781418f133a23696609b6ec9e..516850f79ab14bfc8dc98290512f6779b6b5ac00 100644 --- a/test/edge_test/MiniSeedFactory_test.py +++ b/test/edge_test/MiniSeedFactory_test.py @@ -1,58 +1,14 @@ """Tests for MiniSeedFactory.py""" +import io import numpy from numpy.testing import assert_equal -from obspy.core import Stats, Stream, Trace, UTCDateTime -from geomagio import TimeseriesUtility -from geomagio.edge import MiniSeedFactory - - -def test__get_edge_network(): - """edge_test.MiniSeedFactory_test.test__get_edge_network()""" - # _get_edge_network should always return NT for use by USGS geomag - assert_equal(MiniSeedFactory()._get_edge_network(" ", " ", " ", " "), "NT") - +from obspy.core import read, Stats, Stream, Trace, UTCDateTime +import pytest -def test__get_edge_station(): - """edge_test.MiniSeedFactory_test.test__get_edge_station()""" - # _get_edge_station will return the observatory code passed in. - assert_equal(MiniSeedFactory()._get_edge_station("BOU", " ", " ", " "), "BOU") - - -def test__get_edge_channel(): - """edge_test.MiniSeedFactory_test.test__get_edge_channel()""" - # Call private function _get_edge_channel, make certain - # it gets back the appropriate 2 character code. - factory = MiniSeedFactory() - assert_equal(factory._get_edge_channel("", "D", "", "minute"), "UFD") - assert_equal(factory._get_edge_channel("", "U", "", "minute"), "UFU") - assert_equal(factory._get_edge_channel("", "F", "", "minute"), "UFF") - assert_equal(factory._get_edge_channel("", "H", "", "minute"), "UFH") - assert_equal(factory._get_edge_channel("", "BEU", "", "minute"), "BEU") - assert_equal(factory._get_edge_channel("", "Dst4", "", "minute"), "UX4") - assert_equal(factory._get_edge_channel("", "Dst3", "", "minute"), "UX3") - assert_equal(factory._get_edge_channel("", "E-E", "", "minute"), "UQE") - assert_equal(factory._get_edge_channel("", "E-N", "", "minute"), "UQN") - - -def test__get_edge_location(): - """edge_test.MiniSeedFactory_test.test__get_edge_location()""" - # Call _get_edge_location, make certain it returns the correct edge - # location code. - assert_equal(MiniSeedFactory()._get_edge_location("", "", "variation", ""), "R0") - assert_equal( - MiniSeedFactory()._get_edge_location("", "", "quasi-definitive", ""), "Q0" - ) - assert_equal(MiniSeedFactory()._get_edge_location("", "", "definitive", ""), "D0") - - -def test__get_interval_code(): - """edge_test.MiniSeedFactory_test.test__get_interval_code()""" - assert_equal(MiniSeedFactory()._get_interval_code("day"), "P") - assert_equal(MiniSeedFactory()._get_interval_code("hour"), "R") - assert_equal(MiniSeedFactory()._get_interval_code("minute"), "U") - assert_equal(MiniSeedFactory()._get_interval_code("second"), "L") - assert_equal(MiniSeedFactory()._get_interval_code("tenhertz"), "B") +from geomagio import TimeseriesUtility +from geomagio.edge import MiniSeedFactory, MiniSeedInputClient +from .MockMiniSeedClient import MockMiniSeedClient class MockMiniSeedInputClient(object): @@ -67,6 +23,14 @@ class MockMiniSeedInputClient(object): self.last_sent = stream +@pytest.fixture(scope="class") +def miniseed_factory() -> MiniSeedFactory: + """instance of MiniSeedFactory with MockMiniseedClient""" + factory = MiniSeedFactory() + factory.client = MockMiniSeedClient() + yield factory + + def test__put_timeseries(): """edge_test.MiniSeedFactory_test.test__put_timeseries()""" trace1 = __create_trace([0, 1, 2, 3, numpy.nan, 5, 6, 7, 8, 9], channel="H") @@ -80,16 +44,45 @@ def test__put_timeseries(): sent = client.last_sent assert_equal(len(sent), 2) # first trace includes [0...4] - assert_equal(sent[0].stats.channel, "LFH") + assert_equal(sent[0].stats.channel, "LFU") assert_equal(len(sent[0]), 4) assert_equal(sent[0].stats.endtime, trace1.stats.starttime + 3) # second trace includes [5...9] - assert_equal(sent[1].stats.channel, "LFH") + assert_equal(sent[1].stats.channel, "LFU") assert_equal(len(sent[1]), 5) assert_equal(sent[1].stats.starttime, trace1.stats.starttime + 5) assert_equal(sent[1].stats.endtime, trace1.stats.endtime) +def test__pre_process(): + """edge_test.MiniSeedFactory_test.test__pre_process()""" + trace = __create_trace(numpy.arange((86400 * 2) + 1), channel="H") + processed = MiniSeedInputClient(host=None)._pre_process(stream=Stream(trace)) + assert len(processed) == 2 + for trace in processed: + assert trace.data.dtype == "float32" + stats = trace.stats + assert stats.npts == 86400 + assert stats.starttime.timestamp % 86400 == 0 + assert stats.endtime.timestamp % 86400 != 0 + + +def test__format_miniseed(): + """edge_test.MiniseedFactory_test.test__format_miniseed()""" + buf = io.BytesIO() + trace = __create_trace(numpy.arange((86400 * 2) + 1), channel="H") + MiniSeedInputClient(host=None)._format_miniseed(stream=Stream(trace), buf=buf) + block_size = 512 + data = buf.getvalue() + n_blocks = int(len(data) / block_size) + assert n_blocks == 1516 + # 759th block is start of second day(758 blocks per day for 1Hz data) + block_start = 758 * block_size + block = data[block_start : block_start + block_size] + out_stream = read(io.BytesIO(block)) + assert out_stream[0].stats.starttime.timestamp % 86400 == 0 + + def test__set_metadata(): """edge_test.MiniSeedFactory_test.test__set_metadata()""" # Call _set_metadata with 2 traces, and make certain the stats get @@ -102,14 +95,12 @@ def test__set_metadata(): assert_equal(stream[1].stats["channel"], "H") -# def test_get_timeseries(): -def dont_get_timeseries(): +def test_get_timeseries(miniseed_factory): """edge_test.MiniSeedFactory_test.test_get_timeseries()""" # Call get_timeseries, and test stats for comfirmation that it came back. # TODO, need to pass in host and port from a config file, or manually # change for a single test. - edge_factory = MiniSeedFactory(host="TODO", port="TODO") - timeseries = edge_factory.get_timeseries( + timeseries = miniseed_factory.get_timeseries( UTCDateTime(2015, 3, 1, 0, 0, 0), UTCDateTime(2015, 3, 1, 1, 0, 0), "BOU", @@ -127,6 +118,67 @@ def dont_get_timeseries(): "H", "Expect timeseries stats channel to be equal to H", ) + assert_equal( + timeseries.select(channel="H")[0].stats.data_type, + "variation", + "Expect timeseries stats data_type to be equal to variation", + ) + + +def test_get_timeseries_by_location(miniseed_factory): + """test.edge_test.MiniSeedFactory_test.test_get_timeseries_by_location()""" + timeseries = miniseed_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("H"), + "R0", + "minute", + ) + assert_equal( + timeseries.select(channel="H")[0].stats.data_type, + "R0", + "Expect timeseries stats data_type to be equal to R0", + ) + timeseries = miniseed_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("H"), + "A0", + "minute", + ) + assert_equal( + timeseries.select(channel="H")[0].stats.data_type, + "A0", + "Expect timeseries stats data_type to be equal to A0", + ) + timeseries = miniseed_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("X"), + "Q0", + "minute", + ) + assert_equal( + timeseries.select(channel="X")[0].stats.data_type, + "Q0", + "Expect timeseries stats data_type to be equal to Q0", + ) + timeseries = miniseed_factory.get_timeseries( + UTCDateTime(2015, 3, 1, 0, 0, 0), + UTCDateTime(2015, 3, 1, 1, 0, 0), + "BOU", + ("X"), + "D0", + "minute", + ) + assert_equal( + timeseries.select(channel="X")[0].stats.data_type, + "D0", + "Expect timeseries stats data_type to be equal to D0", + ) def __create_trace( @@ -136,7 +188,7 @@ def __create_trace( channel="H", location="R0", data_interval="second", - data_type="interval", + data_type="variation", ): """ Utility to create a trace containing the given numpy array. diff --git a/test/edge_test/MockMiniSeedClient.py b/test/edge_test/MockMiniSeedClient.py new file mode 100644 index 0000000000000000000000000000000000000000..88c6a4b612b5382e4eafb070501f83779c1a4dc1 --- /dev/null +++ b/test/edge_test/MockMiniSeedClient.py @@ -0,0 +1,39 @@ +import numpy +from obspy import Stream, UTCDateTime +from obspy.clients.neic.client import Client + +from geomagio import TimeseriesUtility +from geomagio.edge import SNCL + + +class MockMiniSeedClient(Client): + """replaces default obspy miniseed client's get_waveforms method to return trace of ones""" + + def get_waveforms( + self, + network: str, + station: str, + location: str, + channel: str, + starttime: UTCDateTime, + endtime: UTCDateTime, + ): + sncl = SNCL( + station=station, + network=network, + channel=channel, + location=location, + ) + trace = TimeseriesUtility.create_empty_trace( + starttime=starttime, + endtime=endtime, + observatory=station, + channel=channel, + type=sncl.data_type, + interval=sncl.interval, + network=network, + station=station, + location=location, + ) + trace.data = numpy.ones(trace.stats.npts) + return Stream([trace]) diff --git a/test/edge_test/RawInputClient_test.py b/test/edge_test/RawInputClient_test.py index 850f1a7dfff04cc18d0dc45ffc860d334ba36bd8..ef21290aca7b15bb3784628999673fe8a30431bb 100644 --- a/test/edge_test/RawInputClient_test.py +++ b/test/edge_test/RawInputClient_test.py @@ -1,6 +1,8 @@ """Tests for RawInputClient.py""" import numpy +from datetime import datetime +import logging from obspy.core import Stats, Trace, UTCDateTime from geomagio.edge import EdgeFactory, RawInputClient from numpy.testing import assert_equal @@ -56,7 +58,7 @@ def test_raw_input_client(): def test__get_tag(): - """edge_test.RawInputClient_test.test_raw_input_client()""" + """edge_test.RawInputClient_test.test__get_tag()""" network = "NT" station = "BOU" channel = "MVH" @@ -72,3 +74,63 @@ def test__get_tag(): ) tag_send = client._get_tag() assert_equal(tag_send is not None, True) + + +def test__get_time_values(caplog): + """edge_test.RawInputClient_test.test__get_time_values()""" + network = "NT" + station = "BOU" + channel = "MVH" + location = "R0" + client = MockRawInputClient( + tag="tag", + host="host", + port="port", + station=station, + channel=channel, + location=location, + network=network, + ) + + # test rounding up of microsecond value + time = UTCDateTime("2020-10-07T00:00:15.196855Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 281) + assert_equal(secs, 15) + assert_equal(usecs, 197000) + # test rounding down of microsecond value + time = UTCDateTime("2020-10-07T00:00:15.196455Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 281) + assert_equal(secs, 15) + assert_equal(usecs, 196000) + # test top of second adjustment + time = UTCDateTime("2020-10-07T00:00:00.999999Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 281) + assert_equal(secs, 1) + assert_equal(usecs, 0) + # test top of day adjustment + time = UTCDateTime("2020-10-07T23:59:59.999999Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 282) + assert_equal(secs, 0) + assert_equal(usecs, 0) + # assert if previous 4 tests generate 4 warning messages + assert_equal(len(caplog.messages), 4) + + # clear warnings from log + caplog.clear() + # test ideal case + time = UTCDateTime("2020-10-07T00:00:00.232000Z") + yr, doy, secs, usecs = client._get_time_values(time) + assert_equal(yr, 2020) + assert_equal(doy, 281) + assert_equal(secs, 0) + assert_equal(usecs, 232000) + # assert if previous test does not generate a warning message + assert_equal(len(caplog.messages), 0) diff --git a/test/edge_test/SNCL_test.py b/test/edge_test/SNCL_test.py new file mode 100644 index 0000000000000000000000000000000000000000..0ce923683a3dae72e4fdbd9d9df1e6dafe1659a0 --- /dev/null +++ b/test/edge_test/SNCL_test.py @@ -0,0 +1,254 @@ +from geomagio.edge.SNCL import SNCL, get_channel, get_location + + +def test_data_type(): + """edge_test.SNCL_test.test_data_type()""" + assert SNCL(station="BOU", channel="LFU", location="R0").data_type == "variation" + assert SNCL(station="BOU", channel="LFU", location="A0").data_type == "adjusted" + assert ( + SNCL(station="BOU", channel="LFU", location="Q0").data_type + == "quasi-definitive" + ) + assert SNCL(station="BOU", channel="LFU", location="D0").data_type == "definitive" + + +def test_element(): + """edge_test.SNCL_test.test_element()""" + assert ( + SNCL( + station="BOU", + channel="UFD", + location="R0", + ).element + == "D" + ) + assert ( + SNCL( + station="BOU", + channel="UFU", + location="R0", + ).element + == "U" + ) + assert ( + SNCL( + station="BOU", + channel="UFF", + location="R0", + ).element + == "F" + ) + assert ( + SNCL( + station="BOU", + channel="UFH", + location="R0", + ).element + == "H" + ) + assert ( + SNCL( + station="BOU", + channel="UX4", + location="R0", + ).element + == "Dst4" + ) + assert ( + SNCL( + station="BOU", + channel="UX3", + location="R0", + ).element + == "Dst3" + ) + assert ( + SNCL( + station="BOU", + channel="UQE", + location="R0", + ).element + == "E-E" + ) + assert ( + SNCL( + station="BOU", + channel="UQN", + location="R0", + ).element + == "E-N" + ) + assert ( + SNCL( + station="BOU", + channel="BEU", + location="R0", + ).element + == "U_Volt" + ) + assert ( + SNCL( + station="BOU", + channel="BYU", + location="R0", + ).element + == "U_Bin" + ) + assert ( + SNCL( + station="BOU", + channel="UFU", + location="R1", + ).element + == "U_Sat" + ) + + +def test_get_channel(): + """edge_test.SNCL_test.test_get_channel()""" + assert ( + get_channel(element="U_Volt", interval="tenhertz", data_type="variation") + == "BEU" + ) + assert ( + get_channel(element="U_Bin", interval="tenhertz", data_type="variation") + == "BYU" + ) + assert get_channel(element="D", interval="second", data_type="variation") == "LFD" + assert get_channel(element="D", interval="second", data_type="R0") == "LFD" + assert get_channel(element="F", interval="minute", data_type="variation") == "UFF" + assert get_channel(element="U", interval="hour", data_type="variation") == "RFU" + assert get_channel(element="V", interval="hour", data_type="variation") == "RFV" + assert get_channel(element="W", interval="hour", data_type="variation") == "RFW" + assert get_channel(element="H", interval="hour", data_type="variation") == "RFU" + assert get_channel(element="H", interval="hour", data_type="R0") == "RFH" + assert get_channel(element="E", interval="hour", data_type="variation") == "RFV" + assert get_channel(element="E", interval="hour", data_type="R0") == "RFE" + assert get_channel(element="Z", interval="hour", data_type="variation") == "RFW" + assert get_channel(element="Z", interval="hour", data_type="R0") == "RFZ" + # not variation data_type, test that H,Z is not converted to U,V + assert get_channel(element="H", interval="hour", data_type="adjusted") == "RFH" + assert get_channel(element="H", interval="hour", data_type="A0") == "RFH" + assert get_channel(element="Z", interval="hour", data_type="adjusted") == "RFZ" + assert get_channel(element="Z", interval="hour", data_type="A0") == "RFZ" + assert get_channel(element="Dst4", interval="day", data_type="variation") == "PX4" + assert ( + get_channel(element="Dst3", interval="minute", data_type="variation") == "UX3" + ) + assert get_channel(element="E-E", interval="minute", data_type="variation") == "UQE" + assert get_channel(element="E-N", interval="minute", data_type="variation") == "UQN" + assert get_channel(element="UK1", interval="minute", data_type="variation") == "UK1" + assert ( + get_channel(element="U_Dist", interval="minute", data_type="variation") == "UFU" + ) + assert get_channel(element="U", interval="minute", data_type="RD") == "UFU" + assert ( + get_channel(element="U_SQ", interval="minute", data_type="variation") == "UFU" + ) + assert get_channel(element="U", interval="minute", data_type="RQ") == "UFU" + assert ( + get_channel(element="U_SV", interval="minute", data_type="variation") == "UFU" + ) + assert get_channel(element="U", interval="minute", data_type="RV") == "UFU" + assert ( + get_channel(element="U_Dist", interval="minute", data_type="adjusted") == "UFU" + ) + assert get_channel(element="U", interval="minute", data_type="AD") == "UFU" + assert get_channel(element="U_SQ", interval="minute", data_type="adjusted") == "UFU" + assert get_channel(element="U", interval="minute", data_type="AQ") == "UFU" + assert get_channel(element="U_SV", interval="minute", data_type="adjusted") == "UFU" + assert get_channel(element="U", interval="minute", data_type="AV") == "UFU" + assert ( + get_channel(element="UK1.R0", interval="minute", data_type="variation") == "UK1" + ) + + +def test_get_location(): + """edge_test.SNCL_test.test_get_location()""" + assert get_location(element="D", data_type="variation") == "R0" + assert get_location(element="D", data_type="R0") == "R0" + assert get_location(element="D", data_type="adjusted") == "A0" + assert get_location(element="D", data_type="A0") == "A0" + assert get_location(element="D", data_type="quasi-definitive") == "Q0" + assert get_location(element="D", data_type="Q0") == "Q0" + assert get_location(element="D", data_type="definitive") == "D0" + assert get_location(element="D", data_type="D0") == "D0" + assert get_location(element="D_Sat", data_type="variation") == "R1" + assert get_location(element="D", data_type="R1") == "R1" + assert get_location(element="D_Dist", data_type="variation") == "RD" + assert get_location(element="D", data_type="RD") == "RD" + assert get_location(element="D_SQ", data_type="variation") == "RQ" + assert get_location(element="D", data_type="RQ") == "RQ" + assert get_location(element="D_SV", data_type="variation") == "RV" + assert get_location(element="D", data_type="RV") == "RV" + + +def test_get_sncl(): + """edge_test.SNCL_test.test_get_sncl()""" + assert SNCL.get_sncl( + station="BOU", data_type="variation", interval="second", element="U" + ) == SNCL(station="BOU", network="NT", channel="LFU", location="R0") + assert SNCL.get_sncl( + station="BOU", data_type="variation", interval="second", element="H" + ) == SNCL(station="BOU", network="NT", channel="LFU", location="R0") + assert SNCL.get_sncl( + station="BOU", data_type="R0", interval="second", element="H" + ) == SNCL(station="BOU", network="NT", channel="LFH", location="R0") + assert SNCL.get_sncl( + station="BOU", data_type="A0", interval="second", element="H" + ) == SNCL(station="BOU", network="NT", channel="LFH", location="A0") + + +def test_interval(): + """edge_test.SNCL_test.test_interval()""" + assert ( + SNCL( + station="BOU", + channel="BEU", + location="R0", + ).interval + == "tenhertz" + ) + assert ( + SNCL( + station="BOU", + channel="LEU", + location="R0", + ).interval + == "second" + ) + assert ( + SNCL( + station="BOU", + channel="UEU", + location="R0", + ).interval + == "minute" + ) + assert ( + SNCL( + station="BOU", + channel="REU", + location="R0", + ).interval + == "hour" + ) + assert ( + SNCL( + station="BOU", + channel="PEU", + location="R0", + ).interval + == "day" + ) + + +def test_parse_sncl(): + """edge_test.SNCL_test.test_parse_sncl()""" + assert SNCL(station="BOU", channel="UFU", location="R0").parse_sncl() == { + "station": "BOU", + "network": "NT", + "data_type": "variation", + "element": "U", + "interval": "minute", + } diff --git a/test/imfv283_test/IMFV283Parser_test.py b/test/imfv283_test/IMFV283Parser_test.py index 02ca2d3bda7abc23344363c1054c65135b8fe6f7..61d5f92b137242198997a6fec73b8053488fe2d5 100644 --- a/test/imfv283_test/IMFV283Parser_test.py +++ b/test/imfv283_test/IMFV283Parser_test.py @@ -21,24 +21,62 @@ IMFV283_EXAMPLE_FRD = ( + b"@cVAjT@[CAVW@cWAjT@[CAVT@cWAjU@[AAVO@cYAjV@Z}AVK@c[AjV" ) +IMFV283_EXAMPLE_STJ = ( + b"75C1E7AC20259002641G44-3NN027EXE00191`@OA@BWGbx{" + + b"x@@Bh\x7fD`@@@@@@@@@@@@@@@@@@@@@@@@@@@FDODdV}X_yxAGHODlV~L_z|AG" + + b"tODPV\x7f@_{pAxLOC`V\x7fp_|pAxPOBdV@D`}dAxdOAxVAX`~lAx`O@|VAp`\x7fXAyDO@tVCd`@\\Bx`O\x7fXUC|`APByDO\x7fdUEd`AtBx`O~\\UEp`BXBGtO}PUFP`CHB \n75C1E7AC20259001441G44-3NN027EXE00191`@LA@BWGbx{x@@Bh\x7fD`@@@@@@@@@@@@@@@@@@@@@@@@@@@\x7fhN{XU\x7fh_zPA\x7fPN|pU~P_xxA\x7fDN}xU|p_GpA@dO@pV||_FtA@hOA\\Vz|_FDAADOAxV{\\_FpABXOCXV{T_F`ABxODTV{L_F|ACpODxV{x_GPADLODpV|X_GxADpODhV|x_xlAEHODdV|x_yHA \n75C1E7AC20259000241G44-3NN027EXE00191`@IAEfWGby{x@@Bh\x7fD`@@@@@@@@@@@@@@@@@@@@@@@@@@@BxO{pT|h@y|BC@O|XT|t@yhBCDO}DT{p@xpBBpO}dT{H@ydBB`O\x7fLTyh@FHBAPO@PUGL@CxBAHOB\\UFL@BLBADOD\\UCh@\x7fdA@LOEHUB|@~|A@pOGdUB`@}dA\x7fxNx\\UAd@|hA\x7flNytU@H@{PA \n75C1E7AC20258235041G45-3NN027EXE00191`@JAEbWGby{x@@Bh\x7fD`@@@@@@@@@@@@@@@@@@@@@@@@@@@C|O{hT~D@{PBC`O{HT}h@{PBCtO{\\T}|@{TBCXOztT}X@{TBDPO{xT~`@{TBCdO{`T}p@z|BC@OzxT|x@z|BCPO{HT}\\@{DBC\\O{@T}t@{XBC\\O{XT}|@{HBCTO{lT}h@zhBB|O{\\T}H@z\\B \n" +) + def test_parse_msg_header(): """imfv283_test.IMFV283Parser_test.test_parse_msg_header() Call the _parse_header method with a header. - Verify the header name and value are split at the correct column. + Verify the header names and values are split at the correct columns. """ header = IMFV283Parser()._parse_msg_header(IMFV283_EXAMPLE_VIC) + assert_equal(header["daps_platform"], b"75C2A3A8") assert_equal(header["obs"], "VIC") + assert_equal(header["transmission_time"], b"14023012741") + assert_equal(header["data_len"], 191) -def test_parse_goes_header(): - """imfv283_test.IMFV283Parser_test.test_parse_goes_header()""" +def test_parse_goes_header_VIC(): + """imfv283_test.IMFV283Parser_test.test_parse_goes_header_VIC()""" goes_data = IMFV283Parser()._process_ness_block( IMFV283_EXAMPLE_VIC, imfv283_codes.OBSERVATORIES["VIC"], 191 ) - goes_header = IMFV283Parser()._parse_goes_header(goes_data) - assert_equal(goes_header["day"], 23) + actual_goes_header = IMFV283Parser()._parse_goes_header(goes_data) + + expected_header = { + "day": 23, + "minute": 73, + "offset": bytearray(b"\x96\x86\xbd\xc1"), + "orient": 0.0, + "scale": [1, 1, 1, 1], + } + + assert_equal(actual_goes_header, expected_header) + assert_equal(type(actual_goes_header["minute"]), int) + + +def test_parse_goes_header_STJ(): + """imfv283_test.IMFV283Parser_test.test_parse_goes_header_STJ()""" + goes_data = IMFV283Parser()._process_ness_block( + IMFV283_EXAMPLE_STJ, imfv283_codes.OBSERVATORIES["STJ"], 191 + ) + actual_goes_header = IMFV283Parser()._parse_goes_header(goes_data) + + expected_header = { + "day": 259, + "minute": 12, + "offset": bytearray(b"\x97x\xb8\xbe"), + "orient": 0.0, + "scale": [1, 1, 1, 1], + } + + assert_equal(actual_goes_header, expected_header) + assert_equal(type(actual_goes_header["minute"]), int) def test_estimate_data_time__correct_doy(): diff --git a/test/residual_test/residual_test.py b/test/residual_test/residual_test.py index 5679f2ba990f2b79e262a576e2cc85449be8c43c..e1fe36f2b6708130d5bd2c3ef6a139bc2061b795 100644 --- a/test/residual_test/residual_test.py +++ b/test/residual_test/residual_test.py @@ -1,11 +1,16 @@ -from numpy.testing import assert_almost_equal +import json + +from numpy.testing import assert_almost_equal, assert_equal +from pydantic import parse_obj_as import pytest +from typing import List from obspy.core import UTCDateTime from geomagio.residual import ( calculate, Reading, SpreadsheetAbsolutesFactory, + SpreadsheetSummaryFactory, WebAbsolutesFactory, ) @@ -43,14 +48,10 @@ def assert_readings_equal(expected: Reading, actual: Reading, decimal: int): ) -def get_null_absolutes(observatory, starttime, endtime): - """ - Tests functionality of WebAbsolutesFactory and recalculation of absolutes - """ - # establish SpreadsheetAbsolutesFactory for reading test data from Excel - waf = WebAbsolutesFactory() - # Read spreadsheet containing test data - readings = waf.get_readings(observatory, starttime, endtime) +def get_json_readings(filename: str): + with open(filename, "r") as file: + readings = json.load(file) + readings = parse_obj_as(List[Reading], readings) return readings @@ -65,6 +66,33 @@ def get_spreadsheet_absolutes(path): return reading +def get_spreadsheet_directory_readings(path, observatory, starttime, endtime): + ssf = SpreadsheetSummaryFactory(base_directory=path) + readings = ssf.get_readings( + observatory=observatory, starttime=starttime, endtime=endtime + ) + return readings + + +def test_CMO_summaries(): + starttime = UTCDateTime("2015-04-01") + endtime = UTCDateTime("2015-06-15") + readings = get_spreadsheet_directory_readings( + path="etc/residual/Caldata", + observatory="CMO", + starttime=starttime, + endtime=endtime, + ) + for reading in readings: + assert_equal(reading.metadata["station"], "CMO") + assert_equal(reading.metadata["instrument"], 200803) + assert_equal(reading.pier_correction, 10.5) + assert_equal(len(readings), 26) + + assert readings[0].time > starttime + assert readings[-1].time < endtime + + def test_DED_20140952332(): """ Compare calulations to original absolutes obejct from Spreadsheet. @@ -101,11 +129,7 @@ def test_BOU_20190702(): Tests gathering of BOU's metadata for use by calculations. Tests calculations for null method measurements in units of DM. """ - readings = get_null_absolutes( - observatory="BOU", - starttime=UTCDateTime("2019-07-02T00:00:00Z"), - endtime=UTCDateTime("2019-07-03T00:00:00Z"), - ) + readings = get_json_readings("etc/residual/BOU20190702.json") for reading in readings: assert_readings_equal( expected=reading, @@ -120,11 +144,7 @@ def test_BOU_20200422(): Tests gathering of BOU's metadata for use by calculations. Tests calculations for null method measurements in units of DMS. """ - readings = get_null_absolutes( - observatory="BOU", - starttime=UTCDateTime("2020-04-22T00:00:00Z"), - endtime=UTCDateTime("2020-04-23T00:00:00Z"), - ) + readings = get_json_readings("etc/residual/BOU20200422.json") for reading in readings: assert_readings_equal( expected=reading, diff --git a/test_metadata.py b/test_metadata.py index 5d597ed8b74ba94e713c50989cbb7b5cded6fe62..2a975f896f4469d831bf33588eac9539d0f97dda 100644 --- a/test_metadata.py +++ b/test_metadata.py @@ -1,8 +1,12 @@ +import json + from obspy import UTCDateTime -from geomagio.api.db import database, metadata_table +from geomagio.adjusted import AdjustedMatrix, Metric +from geomagio.api.db import database, MetadataDatabaseFactory from geomagio.api.ws.Observatory import OBSERVATORIES from geomagio.metadata import Metadata, MetadataCategory +from geomagio.residual import SpreadsheetAbsolutesFactory, WebAbsolutesFactory test_metadata = [ @@ -109,11 +113,69 @@ for observatory in OBSERVATORIES: ) ) +# get null readings +readings = WebAbsolutesFactory().get_readings( + observatory="BOU", + starttime=UTCDateTime("2020-01-01"), + endtime=UTCDateTime("2020-01-07"), +) +# get residual reading +reading = SpreadsheetAbsolutesFactory().parse_spreadsheet( + "etc/residual/DED-20140952332.xlsm" +) +readings.append(reading) + +for reading in readings: + json_string = reading.json() + reading_dict = json.loads(json_string) + try: + reviewer = reading.metadata["reviewer"] + except KeyError: + reviewer = None + test_metadata.append( + Metadata( + category=MetadataCategory.READING, + created_by="test_metadata.py", + network="NT", + updated_by=reviewer, + starttime=reading.time, + endtime=reading.time, + station=reading.metadata["station"], + metadata=reading_dict, + ) + ) + + +adjusted_matrix = AdjustedMatrix( + matrix=[ + [0.9796103131299191, 0.20090702926851434, 0.0, -18.30071487449033], + [-0.20090702926851361, 0.9796103131299198, 0.0, 406.9685381264491], + [0.0, 0.0, 1.0, 708.4810320770974], + [0.0, 0.0, 0.0, 1.0], + ], + pier_correction=-4.0, + metrics=[ + Metric(element="X", absmean=0.5365143131738377, stddev=0.7246802312326883), + Metric(element="Y", absmean=1.3338248076759354, stddev=2.1390294659087816), + Metric(element="Z", absmean=0.7020521498941688, stddev=0.8991572596148847), + Metric(element="dF", absmean=0.47978562187806045, stddev=0.5128104930705225), + ], +) + +test_metadata.append( + Metadata( + category="adjusted-matrix", + station="FRD", + network="NT", + metadata=adjusted_matrix.dict(), + ) +) + async def load_test_metadata(): await database.connect() for meta in test_metadata: - await metadata_table.create_metadata(meta) + await MetadataDatabaseFactory(database=database).create_metadata(meta) await database.disconnect()