# Source code for obspy.clients.fdsn.routing.eidaws_routing_client

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    Routing client for the EIDAWS routing service.

    :copyright:
        The ObsPy Development Team (devs@obspy.org)
    :license:
        GNU Lesser General Public License, Version 3
        (https://www.gnu.org/copyleft/lesser.html)
    """
    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    from future.builtins import *  # NOQA

    import collections

    from ..client import get_bulk_string
    from .routing_client import (
        BaseRoutingClient, _assert_attach_response_not_in_kwargs,
        _assert_filename_not_in_kwargs)

    class EIDAWSRoutingClient(BaseRoutingClient):
        """
        Routing client for the EIDAWS routing service.

        http://www.orfeus-eu.org/data/eida/webservices/routing/

        For waveform queries it will first launch a station query, get the
        station information at each data center with additional constraints
        (e.g. latitude/longitude/...) and use that information for the final
        waveform query. This means that with ObsPy the EIDA routing client
        behaves very similar to the IRIS federator routing client.
        """
        def __init__(self, url="http://www.orfeus-eu.org/eidaws/routing/1",
                     include_providers=None, exclude_providers=None,
                     debug=False, timeout=120, **kwargs):
            """
            Initialize an EIDAWS router client.

            All parameters except ``url`` are passed on to the
            :class:`~obspy.clients.fdsn.routing.routing_clieng.BaseRoutingClient`
            parent class

            :param url: The URL of the routing service.
            :type url: str
            """
            BaseRoutingClient.__init__(self, debug=debug, timeout=timeout,
                                       include_providers=include_providers,
                                       exclude_providers=exclude_providers,
                                       **kwargs)
            self._url = url

        @_assert_filename_not_in_kwargs
        @_assert_attach_response_not_in_kwargs
        def get_waveforms_bulk(self, bulk, **kwargs):
            """
            Get waveforms from multiple data centers.

            Arguments are the same as in
            :meth:`obspy.clients.fdsn.client.Client.get_waveforms_bulk()`.
            Any additional ``**kwargs`` are passed on to each individual service's
            dataselect service if the service supports them (otherwise they are
            silently ignored for that particular fdsnws endpoint).

            The ``filename`` and ``attach_response`` parameters of the single
            provider FDSN client are not supported.

            This can route on a number of different parameters, please see the
            web site of the `EIDAWS Routing Service
            <http://www.orfeus-eu.org/data/eida/webservices/routing/>`_
            for details.
            """
            # Multi-step procedure - first get the stations to be able to use
            # more query parameters - and then construct the waveform string
            # from it.
            #
            # This has to be done for each time interval - otherwise it will get
            # a lot more complicated. I guess in most cases people will use bulk
            # requests for the same time span so it should be fine.

            # Group by time interval - utilize the existing get_bulk_string()
            # method to not have to deal with various different inputs.
            _tmp_bulk_str = get_bulk_string(bulk, {})
            if hasattr(_tmp_bulk_str, "decode"):
                _tmp_bulk_str = _tmp_bulk_str.decode()

            # Parse and split.
            bulk_per_time_interval = collections.defaultdict(list)
            for line in _tmp_bulk_str.splitlines():
                # Cannot really happen - just a safety measure.
                if not line:  # pragma: no cover
                    continue
                item = line.split()
                bulk_per_time_interval[(item[-2], item[-1])].append(item)

            # Build up the new bulk string for each found time interval by
            # querying the station services.
            new_bulk = []
            for t, _b in bulk_per_time_interval.items():
                # channel level and text to keep it fast.
                inv = self.get_stations_bulk(_b, format="text",
                                             level="channel", **kwargs)
                for c in sorted(set(inv.get_contents()["channels"])):
                    new_bulk.append(c.split("."))
                    new_bulk[-1].extend(t)

            # Finally get the waveforms by getting the routes and downloading
            # everytyhing. Don't directly pass in the initializer as the order
            # would not be guaranteed across all Python version.
            arguments = collections.OrderedDict()
            arguments["service"] = "dataselect"
            arguments["format"] = "post"

            bulk_str = get_bulk_string(new_bulk, arguments)
            r = self._download(self._url + "/query", data=bulk_str)
            split = self._split_routing_response(
                r.content.decode() if hasattr(r.content, "decode") else r.content)
            return self._download_waveforms(split, **kwargs)

        @_assert_filename_not_in_kwargs
        def get_stations(self, **kwargs):
            """
            Get stations from multiple data centers.

            Only the ``network``, ``station``, ``location``, ``channel``,
            ``starttime``, and ``endtime`` parameters are used for the actual
            routing. These and all other arguments are then just passed on to each
            single fdsnws station implementation.

            Arguments are the same as in
            :meth:`obspy.clients.fdsn.client.Client.get_stations()`.
            Any additional ``**kwargs`` are passed on to each individual service's
            station service if the service supports them (otherwise they are
            silently ignored for that particular fdsnws endpoint).

            The ``filename`` parameter of the single provider FDSN client is not
            supported for practical reasons.

            This can route on a number of different parameters, please see the
            web site of the `EIDAWS Routing Service
            <http://www.orfeus-eu.org/data/eida/webservices/routing/>`_
            for details.
            """
            return super(EIDAWSRoutingClient, self).get_stations(**kwargs)

        @_assert_filename_not_in_kwargs
        def get_stations_bulk(self, bulk, **kwargs):
            """
            Bulk station download from multiple stations.

            Arguments are the same as in
            :meth:`obspy.clients.fdsn.client.Client.get_stations_bulk()`.
            Any additional ``**kwargs`` are passed on to each individual service's
            station service if the service supports them (otherwise they are
            silently ignored for that particular fdsnws endpoint).

            The ``filename`` parameter of the single provider FDSN client is not
            supported for practical reasons.

            This can route on a number of different parameters, please see the
            web site of the `EIDAWS Routing Service
            <http://www.orfeus-eu.org/data/eida/webservices/routing/>`_
            for details.
            """
            arguments = collections.OrderedDict()
            arguments["service"] = "station"
            arguments["format"] = "post"
            arguments["alternative"] = "false"
            bulk_str = get_bulk_string(bulk, arguments)
            r = self._download(self._url + "/query", data=bulk_str)
            split = self._split_routing_response(
                r.content.decode() if hasattr(r.content, "decode") else r.content)
            return self._download_stations(split, **kwargs)

        @staticmethod
        def _split_routing_response(data):
            """
            Splits the routing responses per data center for the EIDAWS output.

            Returns a dictionary with the keys being the root URLs of the fdsnws
            endpoints and the values the data payloads for that endpoint.

            :param data: The return value from the EIDAWS routing service.
            """
            split = collections.defaultdict(list)
            current_key = None
            for line in data.splitlines():
                line = line.strip()
                if not line:
                    continue
                if "http" in line and "fdsnws" in line:
                    current_key = line[:line.rfind("/fdsnws")]
                    continue
                split[current_key].append(line)

            return {k: "\n".join(v) for k, v in split.items()}

    if __name__ == '__main__':  # pragma: no cover
        import doctest
        doctest.testmod(exclude_empty=True)

### Thank you!

We would like to thank our contributors, whose efforts make this software what it is. These people have helped by writing code and documentation, and by testing. They have created and maintained this product, its associated libraries and applications, our build tools and our web sites.

#### Contributors

- Ackerley, Nick
- Allgeyer, Sebastien
- Ammon, Charles J.
- Antunes, Emanuel
- Arnarsson, Ólafur St.
- Bank, Markus
- Barsch, Robert
- Behr, Yannik
- Bernardi, Fabrizio
- Bernauer, Felix
- Bes de Berc, Maxime
- Beyreuther, Moritz
- Boltz, Shawn
- Bonaimé, Sébastien
- Carothers, Lloyd
- Chamberlain, Calum
- Chambers, Derrick
- Chen, Zhao
- Clark, Adam
- Danecek, Peter
- Egdorf, Sven
- Engels, Fabian
- Ermert, Laura
- Eulenfeld, Tom

- Fabbri, Tommaso
- Falco, Nicholas
- Fee, Jeremy
- Grellier, Clément
- Grunberg, Marc
- Hammer, Conny
- Heimann, Sebastian
- Heiniger, Lukas
- Hope, Gaute
- Igel, Heiner
- Inza, Adolfo
- Isken, Marius
- Ketchum, David
- Koymans, Mathijs
- Kremers, Simon
- Kress, Victor
- Krieger, Lars
- Krischer, Lion
- Kula, Damian
- Käufl, Paul
- Köhler, Andreas
- Lecocq, Thomas
- Leeman, John
- Legovini, Paride

- Lesage, Philippe
- Li, Yulin
- Lomax, Anthony
- Lopes, Rui L.
- MacCarthy, Jonathan
- Maggi, Alessia
- Martin, Henri
- Medlin, Andrew
- Megies, Tobias
- Meschede, Matthias
- Michelini, Alberto
- Miller, Nathaniel C.
- Morgenstern, Bernhard
- Nof, Ran Novitsky
- Panning, Mark P.
- Parker, Tom
- Pestourie, Romain
- Rapagnani, Giovanni
- Reyes, Celso
- Ringler, Adam
- Rothenhäusler, Nicolas
- Russo, Emiliano
- Sales de Andrade, Elliott

- Satriano, Claudio
- Saul, Joachim
- Scheingraber, Chris
- Schneider, Simon
- Sippl, Christian
- Snoke, Arthur
- Stange, Stefan
- Stähler, Simon C.
- Sullivan, Benjamin
- Tian, Dongdong
- Trabant, Chad
- Trani, Luca
- Uieda, Leonardo
- Walker, Andrew
- Walther, Marcus
- Wassermann, Joachim
- Williams, Mark C.
- Winkelman, Andrew
- Zaccarelli, Riccardo
- Zad, Seyed Kasra Hosseini
- Zhu, Lijun
- van Driel, Martin
- van Wijk, Kasper

#### Funds

ObsPy was partially funded by the

- German Science Foundation (DFG) via grant DFG IG 16/9-1
- German Ministry for Education and Research (BMBF), GEOTECHNOLOGIEN grant 03G0646H.
- NERA project (Network of European Research Infrastructures for Earthquake Risk Assessment and Mitigation) under the European Community's Seventh Framework Programme (FP7/2007-2013) grant agreement n° 262330
- Leibniz Institute for Applied Geophysics (LIAG)
- VERCE EU-FP7 project (no. 283543)