diff --git a/.dockerignore b/.dockerignore index 6a5d174d3fa806a3216e35a4e9087a4458c1e192..bbd458dcf488e8c522fbae7923909f96a4327383 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ .coverage .DS_Store +.git node_modules *.pyc coverage.xml \ No newline at end of file diff --git a/.gitignore b/.gitignore index ead4351e3696c8fcfc89ca47cdab2509c05fa565..9bfe76f05b8f51f83587d5f11cd636e5c519e900 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ node_modules *.pyc coverage.xml .ipynb_checkpoints* +.vscode + diff --git a/.travis.yml b/.travis.yml index b5c30814cde493b0e4b87ccff3704b4ae6139805..75888c7cc7fa7471b1d81ec0e1c558b092c39d25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,3 @@ -# sudo: false indicates travis should use container infrastructure -sudo: false language: python python: - 2.7 @@ -8,9 +6,9 @@ python: before_install: ## courtesy of http://conda.pydata.org/docs/travis.html - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; + wget https://repo.anaconda.com/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; else - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; + wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; fi - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" @@ -20,7 +18,7 @@ before_install: - conda info -a install: - conda config --add channels conda-forge - - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION obspy pycurl nose flake8 coverage webtest 'icu=58.*' + - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION obspy pycurl nose flake8 coverage webtest - source activate test-environment script: - flake8 --config=.flake8 bin/ geomagio/ test/ diff --git a/Dockerfile b/Dockerfile index 2a09b4a0ccb695d311b0c6e736a650de49ae860b..570ebfc8786b4ebbf0bc45b66a3858f60a4de8f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,36 @@ -FROM usgs/centos:latest AS pycurl-build +ARG BUILD_IMAGE=usgs/centos:7 +ARG FROM_IMAGE=usgs/centos:7 +FROM $BUILD_IMAGE AS pycurl-build # install conda dependencies RUN yum install -y \ bzip2 \ gcc \ libcurl-devel \ - && \ - yum clean all + && yum clean all # install conda ENV PATH /conda/bin:$PATH -RUN echo 'export PATH=/conda/bin:$PATH' > /etc/profile.d/conda.sh && \ - curl https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh \ - -o ~/miniconda.sh && \ - /bin/bash ~/miniconda.sh -b -p /conda && \ - rm ~/miniconda.sh - -# install algorithms and dependencies via conda -RUN conda config --set ssl_verify $SSL_CERT_FILE && \ - conda config --add channels conda-forge && \ - conda install --yes jupyter obspy 'icu=58.*' && \ - conda clean -i -l -t -y && \ +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 \ # build pycurl with SFTP support - export PIP_CERT=$SSL_CERT_FILE && \ - export PYCURL_SSL_LIBRARY=nss && \ - pip install pycurl + && export PIP_CERT=$SSL_CERT_FILE \ + && export PYCURL_SSL_LIBRARY=nss \ + && pip install pycurl \ + # clean up + && conda clean --all -y -FROM usgs/centos:latest +FROM $FROM_IMAGE LABEL maintainer="Jeremy Fee <jmfee@usgs.gov>" # use conda install from build container @@ -38,19 +41,19 @@ COPY --from=pycurl-build /etc/profile.d/conda.sh /etc/profile.d/conda.sh # copy library (ignores set in .dockerignore) COPY . /geomag-algorithms -RUN pip install /geomag-algorithms && \ - useradd \ + +RUN pip install /geomag-algorithms \ + && useradd \ -c 'Docker image user' \ -m \ -r \ -s /sbin/nologin \ - geomag_user && \ - mkdir -p /home/geomag_user/notebooks && \ - chown -R geomag_user:geomag_user /home/geomag_user + geomag_user \ + && mkdir -p /home/geomag_user/notebooks \ + && chown -R geomag_user:geomag_user /home/geomag_user USER geomag_user - -WORKDIR /home/geomag_user -EXPOSE 80 +WORKDIR /geomag-algorithms +EXPOSE 8000 # entrypoint needs double quotes ENTRYPOINT [ "/geomag-algorithms/docker-entrypoint.sh" ] diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000000000000000000000000000000000000..aa6d6e40021e783882d1d3418f669a40a041f4e0 --- /dev/null +++ b/Pipfile @@ -0,0 +1,17 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +coverage = "*" +flake8 = "*" +nose = "*" +webtest = "*" + +[packages] +numpy = "*" +scipy = "*" +obspy = "*" +pycurl = "*" + diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000000000000000000000000000000000000..7128cb0b10d7ead63f316c0db079006732c9a3f9 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,396 @@ +{ + "_meta": { + "hash": { + "sha256": "597f6aee7828ec46583efa178894001691088a43af8b48a10c94509dd52d35ff" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + ], + "version": "==2019.11.28" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "cycler": { + "hashes": [ + "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", + "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" + ], + "version": "==0.10.0" + }, + "decorator": { + "hashes": [ + "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", + "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" + ], + "version": "==4.4.1" + }, + "future": { + "hashes": [ + "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" + ], + "version": "==0.18.2" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "kiwisolver": { + "hashes": [ + "sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f", + "sha256:210d8c39d01758d76c2b9a693567e1657ec661229bc32eac30761fa79b2474b0", + "sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7", + "sha256:3b15d56a9cd40c52d7ab763ff0bc700edbb4e1a298dc43715ecccd605002cf11", + "sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe", + "sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c", + "sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5", + "sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75", + "sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187", + "sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641", + "sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883", + "sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5", + "sha256:76275ee077772c8dde04fb6c5bc24b91af1bb3e7f4816fd1852f1495a64dad93", + "sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2", + "sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3", + "sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389", + "sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897", + "sha256:9105ce82dcc32c73eb53a04c869b6a4bc756b43e4385f76ea7943e827f529e4d", + "sha256:933df612c453928f1c6faa9236161a1d999a26cd40abf1dc5d7ebbc6dbfb8fca", + "sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a", + "sha256:9491578147849b93e70d7c1d23cb1229458f71fc79c51d52dce0809b2ca44eea", + "sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c", + "sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326", + "sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0", + "sha256:aa716b9122307c50686356cfb47bfbc66541868078d0c801341df31dca1232a9", + "sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e", + "sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544", + "sha256:d22702cadb86b6fcba0e6b907d9f84a312db9cd6934ee728144ce3018e715ee1", + "sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995", + "sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f", + "sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee", + "sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004", + "sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2", + "sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9", + "sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a", + "sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f", + "sha256:fe51b79da0062f8e9d49ed0182a626a7dc7a0cbca0328f612c6ee5e4711c81e4" + ], + "version": "==1.1.0" + }, + "lxml": { + "hashes": [ + "sha256:00ac0d64949fef6b3693813fe636a2d56d97a5a49b5bbb86e4cc4cc50ebc9ea2", + "sha256:0571e607558665ed42e450d7bf0e2941d542c18e117b1ebbf0ba72f287ad841c", + "sha256:0e3f04a7615fdac0be5e18b2406529521d6dbdb0167d2a690ee328bef7807487", + "sha256:13cf89be53348d1c17b453867da68704802966c433b2bb4fa1f970daadd2ef70", + "sha256:217262fcf6a4c2e1c7cb1efa08bd9ebc432502abc6c255c4abab611e8be0d14d", + "sha256:223e544828f1955daaf4cefbb4853bc416b2ec3fd56d4f4204a8b17007c21250", + "sha256:277cb61fede2f95b9c61912fefb3d43fbd5f18bf18a14fae4911b67984486f5d", + "sha256:3213f753e8ae86c396e0e066866e64c6b04618e85c723b32ecb0909885211f74", + "sha256:4690984a4dee1033da0af6df0b7a6bde83f74e1c0c870623797cec77964de34d", + "sha256:4fcc472ef87f45c429d3b923b925704aa581f875d65bac80f8ab0c3296a63f78", + "sha256:61409bd745a265a742f2693e4600e4dbd45cc1daebe1d5fad6fcb22912d44145", + "sha256:678f1963f755c5d9f5f6968dded7b245dd1ece8cf53c1aa9d80e6734a8c7f41d", + "sha256:6c6d03549d4e2734133badb9ab1c05d9f0ef4bcd31d83e5d2b4747c85cfa21da", + "sha256:6e74d5f4d6ecd6942375c52ffcd35f4318a61a02328f6f1bd79fcb4ffedf969e", + "sha256:7b4fc7b1ecc987ca7aaf3f4f0e71bbfbd81aaabf87002558f5bc95da3a865bcd", + "sha256:7ed386a40e172ddf44c061ad74881d8622f791d9af0b6f5be20023029129bc85", + "sha256:8f54f0924d12c47a382c600c880770b5ebfc96c9fd94cf6f6bdc21caf6163ea7", + "sha256:ad9b81351fdc236bda538efa6879315448411a81186c836d4b80d6ca8217cdb9", + "sha256:bbd00e21ea17f7bcc58dccd13869d68441b32899e89cf6cfa90d624a9198ce85", + "sha256:c3c289762cc09735e2a8f8a49571d0e8b4f57ea831ea11558247b5bdea0ac4db", + "sha256:cf4650942de5e5685ad308e22bcafbccfe37c54aa7c0e30cd620c2ee5c93d336", + "sha256:cfcbc33c9c59c93776aa41ab02e55c288a042211708b72fdb518221cc803abc8", + "sha256:e301055deadfedbd80cf94f2f65ff23126b232b0d1fea28f332ce58137bcdb18", + "sha256:ebbfe24df7f7b5c6c7620702496b6419f6a9aa2fd7f005eb731cc80d7b4692b9", + "sha256:eff69ddbf3ad86375c344339371168640951c302450c5d3e9936e98d6459db06", + "sha256:f6ed60a62c5f1c44e789d2cf14009423cb1646b44a43e40a9cf6a21f077678a1" + ], + "version": "==4.4.2" + }, + "matplotlib": { + "hashes": [ + "sha256:08ccc8922eb4792b91c652d3e6d46b1c99073f1284d1b6705155643e8046463a", + "sha256:161dcd807c0c3232f4dcd4a12a382d52004a498174cbfafd40646106c5bcdcc8", + "sha256:1f9e885bfa1b148d16f82a6672d043ecf11197f6c71ae222d0546db706e52eb2", + "sha256:2d6ab54015a7c0d727c33e36f85f5c5e4172059efdd067f7527f6e5d16ad01aa", + "sha256:5d2e408a2813abf664bd79431107543ecb449136912eb55bb312317edecf597e", + "sha256:61c8b740a008218eb604de518eb411c4953db0cb725dd0b32adf8a81771cab9e", + "sha256:80f10af8378fccc136da40ea6aa4a920767476cdfb3241acb93ef4f0465dbf57", + "sha256:819d4860315468b482f38f1afe45a5437f60f03eaede495d5ff89f2eeac89500", + "sha256:8cc0e44905c2c8fda5637cad6f311eb9517017515a034247ab93d0cf99f8bb7a", + "sha256:8e8e2c2fe3d873108735c6ee9884e6f36f467df4a143136209cff303b183bada", + "sha256:98c2ffeab8b79a4e3a0af5dd9939f92980eb6e3fec10f7f313df5f35a84dacab", + "sha256:d59bb0e82002ac49f4152963f8a1079e66794a4f454457fd2f0dcc7bf0797d30", + "sha256:ee59b7bb9eb75932fe3787e54e61c99b628155b0cedc907864f24723ba55b309" + ], + "version": "==3.1.2" + }, + "numpy": { + "hashes": [ + "sha256:0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", + "sha256:0c0763787133dfeec19904c22c7e358b231c87ba3206b211652f8cbe1241deb6", + "sha256:3d52298d0be333583739f1aec9026f3b09fdfe3ddf7c7028cb16d9d2af1cca7e", + "sha256:43bb4b70585f1c2d153e45323a886839f98af8bfa810f7014b20be714c37c447", + "sha256:475963c5b9e116c38ad7347e154e5651d05a2286d86455671f5b1eebba5feb76", + "sha256:64874913367f18eb3013b16123c9fed113962e75d809fca5b78ebfbb73ed93ba", + "sha256:683828e50c339fc9e68720396f2de14253992c495fdddef77a1e17de55f1decc", + "sha256:6ca4000c4a6f95a78c33c7dadbb9495c10880be9c89316aa536eac359ab820ae", + "sha256:75fd817b7061f6378e4659dd792c84c0b60533e867f83e0d1e52d5d8e53df88c", + "sha256:7d81d784bdbed30137aca242ab307f3e65c8d93f4c7b7d8f322110b2e90177f9", + "sha256:8d0af8d3664f142414fd5b15cabfd3b6cc3ef242a3c7a7493257025be5a6955f", + "sha256:9679831005fb16c6df3dd35d17aa31dc0d4d7573d84f0b44cc481490a65c7725", + "sha256:a8f67ebfae9f575d85fa859b54d3bdecaeece74e3274b0b5c5f804d7ca789fe1", + "sha256:acbf5c52db4adb366c064d0b7c7899e3e778d89db585feadd23b06b587d64761", + "sha256:ada4805ed51f5bcaa3a06d3dd94939351869c095e30a2b54264f5a5004b52170", + "sha256:c7354e8f0eca5c110b7e978034cd86ed98a7a5ffcf69ca97535445a595e07b8e", + "sha256:e2e9d8c87120ba2c591f60e32736b82b67f72c37ba88a4c23c81b5b8fa49c018", + "sha256:e467c57121fe1b78a8f68dd9255fbb3bb3f4f7547c6b9e109f31d14569f490c3", + "sha256:ede47b98de79565fcd7f2decb475e2dcc85ee4097743e551fe26cfc7eb3ff143", + "sha256:f58913e9227400f1395c7b800503ebfdb0772f1c33ff8cb4d6451c06cabdf316", + "sha256:fe39f5fd4103ec4ca3cb8600b19216cd1ff316b4990f4c0b6057ad982c0a34d5" + ], + "index": "pypi", + "version": "==1.17.4" + }, + "obspy": { + "hashes": [ + "sha256:14e7ff1b87c2cb901f3a6bebb64cbc5e8d624a067319c242b55cf77c341018fc", + "sha256:17786240e76629b2e2004232fe97a988c9de5f91e2d3367906598760e0f86fa8", + "sha256:20dbfdfffcbcea675f3749132eddf7ec4fb4e1defb23372573dcb8669651aa9d", + "sha256:290dea98f7a8577911dae7816aae9117adc536b0baf6dde1db0b02c53b108a96", + "sha256:5693b17ef7e7b62e69d28af0732d8f53beea35f64aba3c6a4e0b0996e1f5123e", + "sha256:6141112cf264c7baf345333f6b32effefdc268098f499cc1bc7f14587af17149", + "sha256:6268fceec0a8e717fb3eafd9cf25cc2ead9f956e3e7149ca10124ee488a9901c", + "sha256:ce6fe921980bd877d91abda2b1046b95222b304ba2f50800a932979c87ec1c82", + "sha256:d4f61569702a9cbc4bd00ea54589437ee220edd68c56168c6ebf6710e7e93bcd" + ], + "index": "pypi", + "version": "==1.1.1" + }, + "pycurl": { + "hashes": [ + "sha256:6f08330c5cf79fa8ef68b9912b9901db7ffd34b63e225dce74db56bb21deda8e" + ], + "index": "pypi", + "version": "==7.43.0.3" + }, + "pyparsing": { + "hashes": [ + "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", + "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" + ], + "version": "==2.4.5" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "version": "==2.8.1" + }, + "requests": { + "hashes": [ + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + ], + "version": "==2.22.0" + }, + "scipy": { + "hashes": [ + "sha256:0b8c9dc042b9a47912b18b036b4844029384a5b8d89b64a4901ac3e06876e5f6", + "sha256:18ad034be955df046b5a27924cdb3db0e8e1d76aaa22c635403fe7aee17f1482", + "sha256:225d0b5e140bb66df23d438c7b535303ce8e533f94454f4e5bde5f8d109103ea", + "sha256:2f690ba68ed7caa7c30b6dc48c1deed22c78f3840fa4736083ef4f2bd8baa19e", + "sha256:4b8746f4a755bdb2eeb39d6e253a60481e165cfd74fdfb54d27394bd2c9ec8ac", + "sha256:4ba2ce1a58fe117e993cf316a149cf9926c7c5000c0cdc4bc7c56ae8325612f6", + "sha256:546f0dc020b155b8711159d53c87b36591d31f3327c47974a4fb6b50d91589c2", + "sha256:583f2ccd6a112656c9feb2345761d2b19e9213a094cfced4e7d2c1cae4173272", + "sha256:64bf4e8ae0db2d42b58477817f648d81e77f0b381d0ea4427385bba3f959380a", + "sha256:7be424ee09bed7ced36c9457f99c826ce199fd0c0f5b272cf3d098ff7b29e3ae", + "sha256:869465c7ff89fc0a1e2ea1642b0c65f1b3c05030f3a4c0d53d6a57b2dba7c242", + "sha256:884e619821f47eccd42979488d10fa1e15dbe9f3b7660b1c8c928d203bd3c1a3", + "sha256:a42b0d02150ef4747e225c31c976a304de5dc8202ec35a27111b7bb8176e5f13", + "sha256:a70308bb065562afb936c963780deab359966d71ab4f230368b154dde3136ea4", + "sha256:b01ea5e4cf95a93dc335089f8fbe97852f56fdb74afff238cbdf09793103b6b7", + "sha256:b7b8cf45f9a48f23084f19deb9384a1cccb5e92fbc879b12f97dc4d56fb2eb92", + "sha256:bb0899d3f8b9fe8ef95b79210cf0deb6709542889fadaa438eeb3a28001e09e7", + "sha256:c008f1b58f99f1d1cc546957b3effe448365e0a217df1f1894e358906e91edad", + "sha256:cfee99d085d562a7e3c4afe51ac1fe9b434363489e565a130459307f30077973", + "sha256:dfcb0f0a2d8e958611e0b56536285bb435f03746b6feac0e29f045f7c6caf164", + "sha256:f5d47351aeb1cb6bda14a8908e56648926a6b2d714f89717c71f7ada41282141" + ], + "index": "pypi", + "version": "==1.3.3" + }, + "six": { + "hashes": [ + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + ], + "version": "==1.13.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:afa5541e9dea8ad0014251bc9d56171ca3d8b130c9627c6cb3681cff30be3f8a" + ], + "version": "==1.3.11" + }, + "urllib3": { + "hashes": [ + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" + ], + "version": "==1.25.7" + } + }, + "develop": { + "beautifulsoup4": { + "hashes": [ + "sha256:5279c36b4b2ec2cb4298d723791467e3000e5384a43ea0cdf5d45207c7e97169", + "sha256:6135db2ba678168c07950f9a16c4031822c6f4aec75a65e0a97bc5ca09789931", + "sha256:dcdef580e18a76d54002088602eba453eec38ebbcafafeaabd8cab12b6155d57" + ], + "version": "==4.8.1" + }, + "coverage": { + "hashes": [ + "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", + "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", + "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", + "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", + "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", + "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", + "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", + "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", + "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", + "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", + "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", + "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", + "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", + "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", + "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", + "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", + "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", + "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", + "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", + "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", + "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", + "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", + "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", + "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", + "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", + "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", + "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", + "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", + "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", + "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", + "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", + "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025" + ], + "index": "pypi", + "version": "==4.5.4" + }, + "entrypoints": { + "hashes": [ + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + ], + "version": "==0.3" + }, + "flake8": { + "hashes": [ + "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", + "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" + ], + "index": "pypi", + "version": "==3.7.9" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "nose": { + "hashes": [ + "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", + "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", + "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98" + ], + "index": "pypi", + "version": "==1.3.7" + }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "version": "==2.5.0" + }, + "pyflakes": { + "hashes": [ + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + ], + "version": "==2.1.1" + }, + "six": { + "hashes": [ + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + ], + "version": "==1.13.0" + }, + "soupsieve": { + "hashes": [ + "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5", + "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda" + ], + "version": "==1.9.5" + }, + "waitress": { + "hashes": [ + "sha256:278e09d6849acc1365404bbf7d790d0423b159802e850c726e8cd0a126a2dac7", + "sha256:f103e557725b17ae3c62f9e6005cdd85103f8b68fa86cf7c764ba7adc38ca5a2" + ], + "version": "==1.3.1" + }, + "webob": { + "hashes": [ + "sha256:05aaab7975e0ee8af2026325d656e5ce14a71f1883c52276181821d6d5bf7086", + "sha256:36db8203c67023d68c1b00208a7bf55e3b10de2aa317555740add29c619de12b" + ], + "version": "==1.8.5" + }, + "webtest": { + "hashes": [ + "sha256:41348efe4323a647a239c31cde84e5e440d726ca4f449859264e538d39037fd0", + "sha256:f3a603b8f1dd873b9710cd5a7dd0889cf758d7e1c133b1dae971c04f567e566e" + ], + "index": "pypi", + "version": "==2.0.33" + } + } +} diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 14f4954d147b505599e20b383b51e81740e49cd2..40d4d80dcc7dcd9569df58959fe01a3709b3b32d 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -10,6 +10,8 @@ _term () { } trap _term SIGTERM +# add geomagio to notebook path +export PYTHONPATH=/geomag-algorithms # run jupyter notebook server jupyter notebook \ diff --git a/geomagio/Controller.py b/geomagio/Controller.py index 636377d8dc8cd9b362ef1397cc740a13b3cb8c2c..fc3cf19c48bba3262a83da39f94cb75eca3d831c 100644 --- a/geomagio/Controller.py +++ b/geomagio/Controller.py @@ -307,7 +307,7 @@ def get_input_factory(args): # standard arguments input_factory_args = {} - input_factory_args['interval'] = args.interval + input_factory_args['interval'] = args.input_interval or args.interval input_factory_args['observatory'] = args.observatory input_factory_args['type'] = args.type # stream/url arguments @@ -328,6 +328,12 @@ def get_input_factory(args): port=args.input_port, locationCode=args.locationcode, **input_factory_args) + elif input_type == 'miniseed': + input_factory = edge.MiniSeedFactory( + host=args.input_host, + port=args.input_port, + locationCode=args.locationcode, + **input_factory_args) elif input_type == 'goes': # TODO: deal with other goes arguments input_factory = imfv283.GOESIMFV283Factory( @@ -375,7 +381,7 @@ def get_output_factory(args): # standard arguments output_factory_args = {} - output_factory_args['interval'] = args.interval + output_factory_args['interval'] = args.output_interval or args.interval output_factory_args['observatory'] = args.output_observatory output_factory_args['type'] = args.type # stream/url arguments @@ -658,7 +664,13 @@ def parse_args(args): type=edge.LocationCode) parser.add_argument('--interval', default='minute', - choices=['hourly', 'minute', 'second']) + choices=['day', 'hour', 'minute', 'second', 'tenhertz']) + parser.add_argument('--input-interval', + default=None, + choices=['day', 'hour', 'minute', 'second', 'tenhertz']) + parser.add_argument('--output-interval', + default=None, + choices=['day', 'hour', 'minute', 'second', 'tenhertz']) parser.add_argument('--update', action='store_true', default=False, @@ -740,6 +752,7 @@ def parse_args(args): 'iaga2002', 'imfv122', 'imfv283', + 'miniseed', 'pcdcp')) parser.add_argument('--input-file', diff --git a/geomagio/TimeseriesUtility.py b/geomagio/TimeseriesUtility.py index 7a316ae2432972d3794189b3519f4f4226bbb2b8..046c10d3fc58f17c8cd5ebb351a7adcf4d6bff07 100644 --- a/geomagio/TimeseriesUtility.py +++ b/geomagio/TimeseriesUtility.py @@ -35,13 +35,15 @@ def create_empty_trace(starttime, endtime, observatory, obspy.core.Trace trace for the requested channel """ - if interval == 'second': + if interval == 'tenhertz': + delta = 0.1 + elif interval == 'second': delta = 1. elif interval == 'minute': delta = 60. - elif interval == 'hourly': + elif interval == 'hour': delta = 3600. - elif interval == 'daily': + elif interval == 'day': delta = 86400. stats = obspy.core.Stats() stats.network = network diff --git a/geomagio/edge/EdgeFactory.py b/geomagio/edge/EdgeFactory.py index dd8e13866d480cd8634418fabea32f14f536605a..9b672df4d95b40abdba97306be47d1f37bdd8d7d 100644 --- a/geomagio/edge/EdgeFactory.py +++ b/geomagio/edge/EdgeFactory.py @@ -51,7 +51,7 @@ class EdgeFactory(TimeseriesFactory): type: str the data type {variation, quasi-definitive, definitive} interval: str - the data interval {daily, hourly, minute, second} + the data interval {day, hour, minute, second} observatoryMetadata: ObservatoryMetadata object an ObservatoryMetadata object used to replace the default ObservatoryMetadata. @@ -115,7 +115,7 @@ class EdgeFactory(TimeseriesFactory): list of channels to load type: {'variation', 'quasi-definitive', 'definitive'} data type. - interval: {'daily', 'hourly', 'minute', 'second'} + interval: {'day', 'hour', 'minute', 'second', 'tenhertz'} data interval. Returns @@ -175,7 +175,7 @@ class EdgeFactory(TimeseriesFactory): list of channels to load type: {'variation', 'quasi-definitive', 'definitive'} data type. - interval: {'daily', 'hourly', 'minute', 'second'} + interval: {'day', 'hour', 'minute', 'second', 'tenhertz'} data interval. Notes @@ -427,9 +427,9 @@ class EdgeFactory(TimeseriesFactory): interval type """ interval_code = None - if interval == 'daily': + if interval == 'day': interval_code = 'D' - elif interval == 'hourly': + elif interval == 'hour': interval_code = 'H' elif interval == 'minute': interval_code = 'M' @@ -530,7 +530,7 @@ class EdgeFactory(TimeseriesFactory): channel to load type: {'variation', 'quasi-definitive', 'definitive'} data type. - interval: {'daily', 'hourly', 'minute', 'second'} + interval: {'day', 'hour', 'minute', 'second', 'tenhertz'} data interval. starttime: obspy.core.UTCDateTime endtime: obspy.core.UTCDateTime diff --git a/geomagio/edge/MiniSeedFactory.py b/geomagio/edge/MiniSeedFactory.py new file mode 100644 index 0000000000000000000000000000000000000000..bced26bba9e57bdaf87e01188993135b6b30b899 --- /dev/null +++ b/geomagio/edge/MiniSeedFactory.py @@ -0,0 +1,518 @@ +"""Factory that loads data from earthworm and writes to Edge. + +EdgeFactory uses obspy earthworm class to read data from any +earthworm standard Waveserver using the obspy getWaveform call. + +Writing will be implemented with Edge specific capabilities, +to take advantage of it's newer realtime abilities. + +Edge is the USGS earthquake hazard centers replacement for earthworm. +""" +from __future__ import absolute_import + +import sys +from io import BytesIO +import numpy +import numpy.ma + +import obspy.core +from obspy.clients.neic import client as miniseed + +from .. import ChannelConverter, TimeseriesUtility +from ..TimeseriesFactory import TimeseriesFactory +from ..TimeseriesFactoryException import TimeseriesFactoryException +from ..ObservatoryMetadata import ObservatoryMetadata + + +class MiniSeedFactory(TimeseriesFactory): + """TimeseriesFactory for Edge related data. + + Parameters + ---------- + host: str + a string representing the IP number of the host to connect to. + port: integer + the port number the miniseed query server is listening on. + observatory: str + the observatory code for the desired observatory. + channels: array + an array of channels {H, D, E, F, Z, MGD, MSD, HGD}. + Known since channel names are mapped based on interval and type, + others are passed through, see #_get_edge_channel(). + type: str + the data type {variation, quasi-definitive, definitive} + interval: str + the data interval {'day', 'hour', 'minute', 'second', 'tenhertz'} + observatoryMetadata: ObservatoryMetadata object + an ObservatoryMetadata object used to replace the default + ObservatoryMetadata. + locationCode: str + the location code for the given edge server, overrides type + in get_timeseries/put_timeseries + + See Also + -------- + TimeseriesFactory + + Notes + ----- + This is designed to read from any earthworm style waveserver, but it + currently only writes to an edge. Edge mimics an earthworm style + waveserver close enough that we hope to maintain that compatibility + for reading. + """ + + def __init__(self, host='cwbpub.cr.usgs.gov', port=2061, write_port=7981, + observatory=None, channels=None, type=None, interval=None, + observatoryMetadata=None, locationCode=None): + TimeseriesFactory.__init__(self, observatory, channels, type, interval) + + self.client = miniseed.Client(host, port) + + self.observatoryMetadata = observatoryMetadata or ObservatoryMetadata() + self.locationCode = locationCode + self.interval = interval + self.host = host + self.port = port + self.write_port = write_port + + def get_timeseries(self, starttime, endtime, observatory=None, + channels=None, type=None, interval=None): + """Get timeseries data + + Parameters + ---------- + starttime: obspy.core.UTCDateTime + time of first sample. + endtime: obspy.core.UTCDateTime + time of last sample. + observatory: str + observatory code. + channels: array_like + list of channels to load + type: {'variation', 'quasi-definitive', 'definitive'} + data type. + interval: {'day', 'hour', 'minute', 'second', 'tenhertz'} + data interval. + + Returns + ------- + obspy.core.Stream + timeseries object with requested data. + + Raises + ------ + TimeseriesFactoryException + if invalid values are requested, or errors occur while + retrieving timeseries. + """ + observatory = observatory or self.observatory + channels = channels or self.channels + type = type or self.type + interval = interval or self.interval + + if starttime > endtime: + raise TimeseriesFactoryException( + 'Starttime before endtime "%s" "%s"' % (starttime, endtime)) + + # need this until https://github.com/obspy/obspy/pull/1179 + # replace stdout + original_stdout = sys.stdout + temp_stdout = BytesIO() + try: + sys.stdout = temp_stdout + # get the timeseries + timeseries = obspy.core.Stream() + for channel in channels: + data = self._get_timeseries(starttime, endtime, observatory, + channel, type, interval) + timeseries += data + # restore stdout + finally: + output = temp_stdout.getvalue() + if output: + sys.stderr.write(str(output)) + temp_stdout.close() + sys.stdout = original_stdout + self._post_process(timeseries, starttime, endtime, channels) + + return timeseries + + def put_timeseries(self, timeseries, starttime=None, endtime=None, + observatory=None, channels=None, type=None, interval=None): + """Put timeseries data + + Parameters + ---------- + timeseries: obspy.core.Stream + timeseries object with data to be written + observatory: str + observatory code. + channels: array_like + list of channels to load + type: {'variation', 'quasi-definitive', 'definitive'} + data type. + interval: {'day', 'hour', 'minute', 'second', 'tenhertz'} + data interval. + + Notes + ----- + Streams sent to timeseries are expected to have a single trace per + channel and that trace should have an ndarray, with nan's + representing gaps. + """ + stats = timeseries[0].stats + observatory = observatory or stats.station or self.observatory + channels = channels or self.channels + type = type or self.type or stats.data_type + interval = interval or self.interval or stats.data_interval + + if (starttime is None or endtime is None): + starttime, endtime = TimeseriesUtility.get_stream_start_end_times( + timeseries) + for channel in channels: + if timeseries.select(channel=channel).count() == 0: + raise TimeseriesFactoryException( + 'Missing channel "%s" for output, available channels %s' % + (channel, str(TimeseriesUtility.get_channels(timeseries)))) + for channel in channels: + self._put_channel(timeseries, observatory, channel, type, + interval, starttime, endtime) + + def _convert_stream_to_masked(self, timeseries, channel): + """convert geomag edge traces in a timeseries stream to a MaskedArray + This allows for gaps and splitting. + Parameters + ---------- + stream : obspy.core.stream + a stream retrieved from a geomag edge representing one channel. + channel: string + the channel to be masked. + Returns + ------- + obspy.core.stream + a stream with all traces converted to masked arrays. + """ + stream = timeseries.copy() + for trace in stream.select(channel=channel): + 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': + # 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 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): + """get timeseries data for a single channel. + + Parameters + ---------- + starttime: obspy.core.UTCDateTime + the starttime of the requested data + endtime: obspy.core.UTCDateTime + the endtime of the requested data + 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 + ------- + 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) + data = self.client.get_waveforms(network, station, location, + edge_channel, starttime, endtime) + data.merge() + if data.count() == 0: + data += TimeseriesUtility.create_empty_trace( + starttime, endtime, observatory, channel, type, + interval, network, station, location) + self._set_metadata(data, + observatory, channel, type, interval) + return data + + def _post_process(self, timeseries, starttime, endtime, channels): + """Post process a timeseries stream after the raw data is + is fetched from querymom. Specifically changes + any MaskedArray to a ndarray with nans representing gaps. + Then calls pad_timeseries to deal with gaps at the + beggining or end of the streams. + + Parameters + ---------- + timeseries: obspy.core.stream + The timeseries stream as returned by the call to get_waveforms + starttime: obspy.core.UTCDateTime + the starttime of the requested data + endtime: obspy.core.UTCDateTime + the endtime of the requested data + channels: array_like + list of channels to load + + Notes: the original timeseries object is changed. + """ + for trace in timeseries: + if isinstance(trace.data, numpy.ma.MaskedArray): + trace.data.set_fill_value(numpy.nan) + trace.data = trace.data.filled() + + if 'D' in channels: + for trace in timeseries.select(channel='D'): + trace.data = ChannelConverter.get_radians_from_minutes( + trace.data) + + TimeseriesUtility.pad_timeseries(timeseries, starttime, endtime) + + def _put_channel(self, timeseries, observatory, channel, type, interval, + starttime, endtime): + """Put a channel worth of data + + Parameters + ---------- + timeseries: obspy.core.Stream + timeseries object with data to be written + observatory: str + observatory code. + channel: str + channel to load + type: {'variation', 'quasi-definitive', 'definitive'} + data type. + interval: {'day', 'hour', 'minute', 'second', 'tenhertz'} + data interval. + starttime: obspy.core.UTCDateTime + endtime: obspy.core.UTCDateTime + """ + raise NotImplementedError('"_put_channel" not implemented') + + def _set_metadata(self, stream, observatory, channel, type, interval): + """set metadata for a given stream/channel + Parameters + ---------- + observatory : str + observatory code + channel : str + edge channel code {MVH, MVE, MVD, ...} + type : str + data type {definitive, quasi-definitive, variation} + interval : str + interval length {'day', 'hour', 'minute', 'second', 'tenhertz'} + """ + + for trace in stream: + self.observatoryMetadata.set_metadata(trace.stats, observatory, + channel, type, interval) diff --git a/geomagio/edge/__init__.py b/geomagio/edge/__init__.py index 589bc42bfd74a03997ff1188f08eb5752ab1903d..047575c7d19fef9217a5a2784eec76991c92d8e6 100644 --- a/geomagio/edge/__init__.py +++ b/geomagio/edge/__init__.py @@ -4,10 +4,12 @@ from __future__ import absolute_import from .EdgeFactory import EdgeFactory from .LocationCode import LocationCode +from .MiniSeedFactory import MiniSeedFactory from .RawInputClient import RawInputClient __all__ = [ 'EdgeFactory', 'LocationCode', + 'MiniSeedFactory', 'RawInputClient' ] diff --git a/test/edge_test/EdgeFactory_test.py b/test/edge_test/EdgeFactory_test.py index e0b14e0e803e52de250db196d9eab653a3ec2080..e7a731d3446c75853f94bcd57ff6fed185f7d470 100644 --- a/test/edge_test/EdgeFactory_test.py +++ b/test/edge_test/EdgeFactory_test.py @@ -58,8 +58,8 @@ def test__get_edge_location(): def test__get_interval_code(): """edge_test.EdgeFactory_test.test__get_interval_code() """ - assert_equals(EdgeFactory()._get_interval_code('daily'), 'D') - assert_equals(EdgeFactory()._get_interval_code('hourly'), 'H') + assert_equals(EdgeFactory()._get_interval_code('day'), 'D') + assert_equals(EdgeFactory()._get_interval_code('hour'), 'H') assert_equals(EdgeFactory()._get_interval_code('minute'), 'M') assert_equals(EdgeFactory()._get_interval_code('second'), 'S') diff --git a/test/edge_test/MiniSeedFactory_test.py b/test/edge_test/MiniSeedFactory_test.py new file mode 100644 index 0000000000000000000000000000000000000000..dce141ee1cfd03fa3ee0b0b39899cc99935c014d --- /dev/null +++ b/test/edge_test/MiniSeedFactory_test.py @@ -0,0 +1,92 @@ +"""Tests for MiniSeedFactory.py""" + +from obspy.core import Stream, Trace, UTCDateTime +from geomagio.edge import MiniSeedFactory +from nose.tools import assert_equals + + +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_equals( + MiniSeedFactory()._get_edge_network(' ', ' ', ' ', ' '), + 'NT') + + +def test__get_edge_station(): + """edge_test.MiniSeedFactory_test.test__get_edge_station() + """ + # _get_edge_station will return the observatory code passed in. + assert_equals( + 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_equals(factory._get_edge_channel('', 'D', '', 'minute'), 'UFD') + assert_equals(factory._get_edge_channel('', 'U', '', 'minute'), 'UFU') + assert_equals(factory._get_edge_channel('', 'F', '', 'minute'), 'UFF') + assert_equals(factory._get_edge_channel('', 'H', '', 'minute'), 'UFH') + assert_equals(factory._get_edge_channel('', 'BEU', '', 'minute'), 'BEU') + assert_equals(factory._get_edge_channel('', 'Dst4', '', 'minute'), 'UX4') + assert_equals(factory._get_edge_channel('', 'Dst3', '', 'minute'), 'UX3') + assert_equals(factory._get_edge_channel('', 'E-E', '', 'minute'), 'UQE') + assert_equals(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_equals(MiniSeedFactory()._get_edge_location( + '', '', 'variation', ''), 'R0') + assert_equals(MiniSeedFactory()._get_edge_location( + '', '', 'quasi-definitive', ''), 'Q0') + assert_equals(MiniSeedFactory()._get_edge_location( + '', '', 'definitive', ''), 'D0') + + +def test__get_interval_code(): + """edge_test.MiniSeedFactory_test.test__get_interval_code() + """ + assert_equals(MiniSeedFactory()._get_interval_code('day'), 'P') + assert_equals(MiniSeedFactory()._get_interval_code('hour'), 'R') + assert_equals(MiniSeedFactory()._get_interval_code('minute'), 'U') + assert_equals(MiniSeedFactory()._get_interval_code('second'), 'L') + assert_equals(MiniSeedFactory()._get_interval_code('tenhertz'), 'B') + + +def test__set_metadata(): + """edge_test.MiniSeedFactory_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]) + MiniSeedFactory()._set_metadata(stream, 'BOU', 'H', 'variation', 'minute') + assert_equals(stream[0].stats['channel'], 'H') + assert_equals(stream[1].stats['channel'], 'H') + + +# def test_get_timeseries(): +def dont_get_timeseries(): + """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( + UTCDateTime(2015, 3, 1, 0, 0, 0), UTCDateTime(2015, 3, 1, 1, 0, 0), + 'BOU', ('H'), 'variation', 'minute') + assert_equals(timeseries.select(channel='H')[0].stats.station, + 'BOU', 'Expect timeseries to have stats') + assert_equals(timeseries.select(channel='H')[0].stats.channel, + 'H', 'Expect timeseries stats channel to be equal to H')