Source code for instaseis.database_interfaces.remote_instaseis_db

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Instaseis database class for remote access over HTTP.

:copyright:
    Lion Krischer (lion.krischer@gmail.com), 2020
:license:
    GNU Lesser General Public License, Version 3 [non-commercial/academic use]
    (http://www.gnu.org/copyleft/lgpl.html)
"""
import io
import numpy as np
import obspy
from urllib.parse import urlencode, urlparse
import requests
import warnings

from .base_instaseis_db import BaseInstaseisDB, DEFAULT_MU
from .. import (
    InstaseisError,
    InstaseisWarning,
    Source,
    ForceSource,
    __version__,
)


[docs]class RemoteInstaseisDB(BaseInstaseisDB): """ Remote Instaseis database interface. """ def __init__(self, url, *args, **kwargs): """ :param url: URL to the remote Instaseis server. :type db_path: str """ self.url = url self._scheme, self._netloc, self._path = urlparse(url)[:3] self._path = self._path.strip("/") # Parse the root message of the server. try: root = self._download_url(self._get_url(path="")) except Exception as e: raise InstaseisError( "Failed to connect to remote Instaseis " "server due to: %s" % (str(e)) ) # XXX: Add Instaseis version checks! Make sure server and client are # on the same version! if "type" not in root or root["type"] != "Instaseis Remote Server": raise InstaseisError( "Instaseis server responded with invalid " "response: %s" % (str(root)) ) if root["version"] != __version__: msg = ( "Instaseis versions on server (%s) and on your local " "client (%s) differ and thus things might not work as " "expected." % (root["version"], __version__) ) warnings.warn(msg, InstaseisWarning) self._get_info() def _get_seismograms(self, source, receiver, components=("Z", "N", "E")): """ Extract seismograms for a moment tensor point source from the AxiSEM database. :param source: instaseis.Source or instaseis.ForceSource object :type source: :class:`instaseis.source.Source` or :class:`instaseis.source.ForceSource` :param receiver: instaseis.Receiver object :type receiver: :class:`instaseis.source.Receiver` :param components: a tuple containing any combination of the strings ``"Z"``, ``"N"``, ``"E"``, ``"R"``, and ``"T"`` """ # Collect parameters. params = {"components": "".join(components).upper()} # Start with the receiver. params["receiverlatitude"] = receiver.latitude params["receiverlongitude"] = receiver.longitude if receiver.depth_in_m is not None: params["receiverdepthinmeters"] = receiver.depth_in_m if receiver.network: params["networkcode"] = receiver.network if receiver.station: params["stationcode"] = receiver.station # Do the source. params["sourcelatitude"] = source.latitude params["sourcelongitude"] = source.longitude if source.depth_in_m is not None: params["sourcedepthinmeters"] = source.depth_in_m if isinstance(source, ForceSource): params["fr"] = source.f_r params["ft"] = source.f_t params["fp"] = source.f_p elif isinstance(source, Source): params["mrr"] = source.m_rr params["mtt"] = source.m_tt params["mpp"] = source.m_pp params["mrt"] = source.m_rt params["mrp"] = source.m_rp params["mtp"] = source.m_tp else: raise NotImplementedError url = self._get_url(path="seismograms_raw", **params) r = requests.get(url) if "Instaseis-Mu" not in r.headers: # pragma: no cover warnings.warn( "Mu is not passed via the HTTP headers. Maybe some " "proxy removed it? Mu is now always the default mu.", InstaseisWarning, ) mu = DEFAULT_MU else: mu = float(r.headers["Instaseis-Mu"]) with io.BytesIO(r.content) as fh: fh.seek(0, 0) st = obspy.read(fh) # Convert back to dictionary of numpy arrays...this is a bit # redundant but plays nice with the rest of Instaseis and still # enables a REST API that serves MiniSEED files. data = {"mu": mu} for tr in st: data[tr.stats.channel[-1].upper()] = tr.data return data def _get_url(self, path, **kwargs): # Not tested in the test-suite as it would be awkward to do with the # current setup. But manually vetted and should be good. if self._path: # pragma: no cover path = "/" + self._path + "/" + path url = "%s://%s" % (self._scheme, self._netloc) if path: url += "/%s" % path if kwargs: url += "?%s" % urlencode(kwargs) return url def _download_url(self, url): """ Helper function downloading data from a URL. """ r = requests.get(url) # Not tested in test suite as it would be awkward to do. Manually # tested and should be good. if r.status_code != 200: # pragma: no cover raise InstaseisError( "Status code %i when downloading '%s'" % (r.status_code, url) ) return r.json() def _get_info(self): """ Returns a dictionary with information about the currently loaded database. """ info = self._download_url(self._get_url(path="info")) info["directory"] = self.url # Convert types lost in the translation to JSON. info["datetime"] = obspy.UTCDateTime(info["datetime"]) info["slip"] = np.array(info["slip"], dtype=np.float64) info["sliprate"] = np.array(info["sliprate"], dtype=np.float64) return info