1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6''' 

7Low-level FDSN web service client. 

8 

9This module provides basic functionality to download station metadata, time 

10series data and event information from FDSN web services. Password and token 

11authentication are supported. Query responses are returned as open file-like 

12objects or can be parsed into Pyrocko's native data structures where 

13appropriate. 

14 

15.. _registered-site-names: 

16 

17Registered site names 

18..................... 

19 

20A list of known FDSN site names is maintained within the module for quick 

21selection by the user. This list currently contains the following sites: 

22 

23%s 

24 

25Any other site can be specified by providing its full URL. 

26''' 

27 

28from __future__ import absolute_import 

29 

30import re 

31import logging 

32import socket 

33 

34 

35from pyrocko import util 

36from pyrocko.util import DownloadError 

37from pyrocko import config 

38 

39from pyrocko.util import \ 

40 urlencode, Request, build_opener, HTTPDigestAuthHandler, urlopen, HTTPError 

41 

42try: 

43 newstr = unicode 

44except NameError: 

45 newstr = str 

46 

47 

48logger = logging.getLogger('pyrocko.client.fdsn') 

49 

50g_url = '%(site)s/fdsnws/%(service)s/%(majorversion)i/%(method)s' 

51 

52g_site_abbr = { 

53 'geofon': 'https://geofon.gfz-potsdam.de', 

54 'iris': 'http://service.iris.edu', 

55 'orfeus': 'http://www.orfeus-eu.org', 

56 'bgr': 'http://eida.bgr.de', 

57 'geonet': 'http://service.geonet.org.nz', 

58 'knmi': 'http://rdsa.knmi.nl', 

59 'ncedc': 'http://service.ncedc.org', 

60 'scedc': 'http://service.scedc.caltech.edu', 

61 'usgs': 'http://earthquake.usgs.gov', 

62 'koeri': 'http://eida-service.koeri.boun.edu.tr', 

63 'ethz': 'http://eida.ethz.ch', 

64 'icgc': 'http://ws.icgc.cat', 

65 'ipgp': 'http://eida.ipgp.fr', 

66 'ingv': 'http://webservices.ingv.it', 

67 'isc': 'http://www.isc.ac.uk', 

68 'lmu': 'http://erde.geophysik.uni-muenchen.de', 

69 'noa': 'http://eida.gein.noa.gr', 

70 'resif': 'http://ws.resif.fr', 

71 'usp': 'http://seisrequest.iag.usp.br', 

72 'niep': 'http://eida-sc3.infp.ro' 

73} 

74 

75g_default_site = 'geofon' 

76 

77 

78g_default_query_args = { 

79 'station': { 

80 'starttime', 'endtime', 'startbefore', 'startafter', 'endbefore', 

81 'endafter', 'network', 'station', 'location', 'channel', 'minlatitude', 

82 'maxlatitude', 'minlongitude', 'maxlongitude', 'latitude', 'longitude', 

83 'minradius', 'maxradius', 'level', 'includerestricted', 

84 'includeavailability', 'updatedafter', 'matchtimeseries', 'format', 

85 'nodata'}, 

86 'dataselect': { 

87 'starttime', 'endtime', 'network', 'station', 'location', 'channel', 

88 'quality', 'minimumlength', 'longestonly', 'format', 'nodata'}, 

89 'event': { 

90 'starttime', 'endtime', 'minlatitude', 'maxlatitude', 'minlongitude', 

91 'maxlongitude', 'latitude', 'longitude', 'minradius', 'maxradius', 

92 'mindepth', 'maxdepth', 'minmagnitude', 'maxmagnitude', 'eventtype', 

93 'includeallorigins', 'includeallmagnitudes', 'includearrivals', 

94 'eventid', 'limit', 'offset', 'orderby', 'catalog', 'contributor', 

95 'updatedafter', 'format', 'nodata'}, 

96 'availability': { 

97 'starttime', 'endtime', 'network', 'station', 'location', 'channel', 

98 'quality', 'merge', 'orderby', 'limit', 'includerestricted', 'format', 

99 'nodata', 'mergegaps', 'show'}} 

100 

101 

102def doc_escape_slist(li): 

103 return ', '.join("``'%s'``" % s for s in li) 

104 

105 

106def doc_table_dict(d, khead, vhead, indent=''): 

107 keys, vals = zip(*sorted(d.items())) 

108 

109 lk = max(max(len(k) for k in keys), len(khead)) 

110 lv = max(max(len(v) for v in vals), len(vhead)) 

111 

112 hr = '=' * lk + ' ' + '=' * lv 

113 

114 lines = [ 

115 hr, 

116 '%s %s' % (khead.ljust(lk), vhead.ljust(lv)), 

117 hr] 

118 

119 for k, v in zip(keys, vals): 

120 lines.append('%s %s' % (k.ljust(lk), v.ljust(lv))) 

121 

122 lines.append(hr) 

123 return '\n'.join(indent + line for line in lines) 

124 

125 

126def strip_html(s): 

127 s = s.decode('utf-8') 

128 s = re.sub(r'<[^>]+>', '', s) 

129 s = re.sub(r'\r', '', s) 

130 s = re.sub(r'\s*\n', '\n', s) 

131 return s 

132 

133 

134def indent(s, ind=' '): 

135 return '\n'.join(ind + line for line in s.splitlines()) 

136 

137 

138def get_sites(): 

139 ''' 

140 Get sorted list of registered site names. 

141 ''' 

142 return sorted(g_site_abbr.keys()) 

143 

144 

145if config.config().fdsn_timeout is None: 

146 g_timeout = 20. 

147else: 

148 g_timeout = config.config().fdsn_timeout 

149 

150re_realm_from_auth_header = re.compile(r'(realm)\s*[:=]\s*"([^"]*)"?') 

151 

152 

153class CannotGetRealmFromAuthHeader(DownloadError): 

154 ''' 

155 Raised when failing to parse server response during authentication. 

156 ''' 

157 pass 

158 

159 

160class CannotGetCredentialsFromAuthRequest(DownloadError): 

161 ''' 

162 Raised when failing to parse server response during token authentication. 

163 ''' 

164 pass 

165 

166 

167def get_realm_from_auth_header(headers): 

168 realm = dict(re_realm_from_auth_header.findall( 

169 headers['WWW-Authenticate'])).get('realm', None) 

170 

171 if realm is None: 

172 raise CannotGetRealmFromAuthHeader('headers=%s' % str(headers)) 

173 

174 return realm 

175 

176 

177def sdatetime(t): 

178 return util.time_to_str(t, format='%Y-%m-%dT%H:%M:%S') 

179 

180 

181class EmptyResult(DownloadError): 

182 ''' 

183 Raised when an empty server response is retrieved. 

184 ''' 

185 def __init__(self, url): 

186 DownloadError.__init__(self) 

187 self._url = url 

188 

189 def __str__(self): 

190 return 'No results for request %s' % self._url 

191 

192 

193class RequestEntityTooLarge(DownloadError): 

194 ''' 

195 Raised when the server indicates that too much data was requested. 

196 ''' 

197 def __init__(self, url): 

198 DownloadError.__init__(self) 

199 self._url = url 

200 

201 def __str__(self): 

202 return 'Request entity too large: %s' % self._url 

203 

204 

205class InvalidRequest(DownloadError): 

206 ''' 

207 Raised when an invalid request would be sent / has been sent. 

208 ''' 

209 pass 

210 

211 

212class Timeout(DownloadError): 

213 ''' 

214 Raised when the server does not respond within the allowed timeout period. 

215 ''' 

216 pass 

217 

218 

219def _request( 

220 url, 

221 post=False, 

222 user=None, 

223 passwd=None, 

224 timeout=g_timeout, 

225 **kwargs): 

226 

227 url_values = urlencode(kwargs) 

228 if url_values: 

229 url += '?' + url_values 

230 

231 logger.debug('Accessing URL %s' % url) 

232 url_args = { 

233 'timeout': timeout 

234 } 

235 

236 if util.g_ssl_context: 

237 url_args['context'] = util.g_ssl_context 

238 

239 opener = None 

240 

241 req = Request(url) 

242 if post: 

243 if isinstance(post, newstr): 

244 post = post.encode('utf8') 

245 logger.debug('POST data: \n%s' % post.decode('utf8')) 

246 req.data = post 

247 

248 req.add_header('Accept', '*/*') 

249 

250 itry = 0 

251 while True: 

252 itry += 1 

253 try: 

254 urlopen_ = opener.open if opener else urlopen 

255 try: 

256 resp = urlopen_(req, **url_args) 

257 except TypeError: 

258 # context and cafile not avail before 3.4.3, 2.7.9 

259 url_args.pop('context', None) 

260 url_args.pop('cafile', None) 

261 resp = urlopen_(req, **url_args) 

262 

263 logger.debug('Response: %s' % resp.getcode()) 

264 if resp.getcode() == 204: 

265 raise EmptyResult(url) 

266 return resp 

267 

268 except HTTPError as e: 

269 if e.code == 413: 

270 raise RequestEntityTooLarge(url) 

271 

272 elif e.code == 401: 

273 headers = getattr(e, 'headers', e.hdrs) 

274 

275 realm = get_realm_from_auth_header(headers) 

276 

277 if itry == 1 and user is not None: 

278 auth_handler = HTTPDigestAuthHandler() 

279 auth_handler.add_password( 

280 realm=realm, 

281 uri=url, 

282 user=user, 

283 passwd=passwd or '') 

284 

285 opener = build_opener(auth_handler) 

286 continue 

287 else: 

288 raise DownloadError( 

289 'Authentication failed for realm "%s" when accessing ' 

290 'url "%s". Original error was: %s' % ( 

291 realm, url, str(e))) 

292 

293 else: 

294 raise DownloadError( 

295 'Error content returned by server (HTML stripped):\n%s\n' 

296 ' Original error was: %s' % ( 

297 indent( 

298 strip_html(e.read()), 

299 ' ! '), 

300 str(e))) 

301 

302 except socket.timeout: 

303 raise Timeout( 

304 'Timeout error. No response received within %i s. You ' 

305 'may want to retry with a longer timeout setting.' % timeout) 

306 

307 break 

308 

309 

310def fillurl(service, site, url, majorversion, method): 

311 return url % dict( 

312 site=g_site_abbr.get(site, site), 

313 service=service, 

314 majorversion=majorversion, 

315 method=method) 

316 

317 

318def fix_params(d): 

319 

320 params = dict(d) 

321 for k in ['starttime', 

322 'endtime', 

323 'startbefore', 

324 'startafter', 

325 'endbefore', 

326 'endafter', 

327 'updatedafter']: 

328 

329 if k in params: 

330 params[k] = sdatetime(params[k]) 

331 

332 if params.get('location', None) == '': 

333 params['location'] = '--' 

334 

335 for k in params: 

336 if isinstance(params[k], bool): 

337 params[k] = ['false', 'true'][bool(params[k])] 

338 

339 return params 

340 

341 

342def make_data_selection( 

343 stations, tmin, tmax, 

344 channel_prio=[['BHZ', 'HHZ'], 

345 ['BH1', 'BHN', 'HH1', 'HHN'], 

346 ['BH2', 'BHE', 'HH2', 'HHE']]): 

347 

348 selection = [] 

349 for station in stations: 

350 wanted = [] 

351 for group in channel_prio: 

352 gchannels = [] 

353 for channel in station.get_channels(): 

354 if channel.name in group: 

355 gchannels.append(channel) 

356 if gchannels: 

357 gchannels.sort(key=lambda a: group.index(a.name)) 

358 wanted.append(gchannels[0]) 

359 

360 if wanted: 

361 for channel in wanted: 

362 selection.append((station.network, station.station, 

363 station.location, channel.name, tmin, tmax)) 

364 

365 return selection 

366 

367 

368def station( 

369 site=g_default_site, 

370 url=g_url, 

371 majorversion=1, 

372 timeout=g_timeout, 

373 check=True, 

374 selection=None, 

375 parsed=True, 

376 **kwargs): 

377 

378 ''' 

379 Query FDSN web service for station metadata. 

380 

381 :param site: 

382 :ref:`Registered site name <registered-site-names>` or full base URL of 

383 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

384 :type site: str, optional 

385 :param url: 

386 URL template (default should work in 99% of cases). 

387 :type url: str, optional 

388 :param majorversion: 

389 Major version of the service to query (always ``1`` at the time of 

390 writing). 

391 :type majorversion: int, optional 

392 :param timeout: 

393 Network timeout in [s]. Global default timeout can be configured in 

394 Pyrocko's configuration file under ``fdsn_timeout``. 

395 :type timeout: float, optional 

396 :param check: 

397 If ``True`` arguments are checked against self-description (WADL) of 

398 the queried web service if available or FDSN specification. 

399 :type check: bool, optional 

400 :param selection: 

401 If given, selection to be queried as a list of tuples 

402 ``(network, station, location, channel, tmin, tmax)``. Useful for 

403 detailed queries. 

404 :type selection: list of tuples, optional 

405 :param parsed: 

406 If ``True`` parse received content into 

407 :py:class:`~pyrocko.io.stationxml.FDSNStationXML` 

408 object, otherwise return open file handle to raw data stream. 

409 :type parsed: bool, optional 

410 :param \\*\\*kwargs: 

411 Parameters passed to the server (see `FDSN web services specification 

412 <https://www.fdsn.org/webservices>`_). 

413 

414 :returns: 

415 See description of ``parsed`` argument above. 

416 

417 :raises: 

418 On failure, :py:exc:`~pyrocko.util.DownloadError` or one of its 

419 sub-types defined in the :py:mod:`~pyrocko.client.fdsn` module is 

420 raised. 

421 ''' 

422 

423 service = 'station' 

424 

425 if check: 

426 check_params(service, site, url, majorversion, timeout, **kwargs) 

427 

428 params = fix_params(kwargs) 

429 

430 url = fillurl(service, site, url, majorversion, 'query') 

431 if selection: 

432 lst = [] 

433 for k, v in params.items(): 

434 lst.append('%s=%s' % (k, v)) 

435 

436 for (network, station, location, channel, tmin, tmax) in selection: 

437 if location == '': 

438 location = '--' 

439 

440 lst.append(' '.join((network, station, location, channel, 

441 sdatetime(tmin), sdatetime(tmax)))) 

442 

443 post = '\n'.join(lst) 

444 params = dict(post=post.encode()) 

445 

446 if parsed: 

447 from pyrocko.io import stationxml 

448 format = kwargs.get('format', 'xml') 

449 if format == 'text': 

450 if kwargs.get('level', 'station') == 'channel': 

451 return stationxml.load_channel_table( 

452 stream=_request(url, timeout=timeout, **params)) 

453 else: 

454 raise InvalidRequest('if format="text" shall be parsed, ' 

455 'level="channel" is required') 

456 

457 elif format == 'xml': 

458 return stationxml.load_xml( 

459 stream=_request(url, timeout=timeout, **params)) 

460 else: 

461 raise InvalidRequest('format must be "xml" or "text"') 

462 else: 

463 return _request(url, timeout=timeout, **params) 

464 

465 

466def get_auth_credentials(service, site, url, majorversion, token, timeout): 

467 

468 url = fillurl(service, site, url, majorversion, 'auth') 

469 

470 f = _request(url, timeout=timeout, post=token) 

471 s = f.read().decode() 

472 try: 

473 user, passwd = s.strip().split(':') 

474 except ValueError: 

475 raise CannotGetCredentialsFromAuthRequest('data="%s"' % s) 

476 

477 return user, passwd 

478 

479 

480def dataselect( 

481 site=g_default_site, 

482 url=g_url, 

483 majorversion=1, 

484 timeout=g_timeout, 

485 check=True, 

486 user=None, 

487 passwd=None, 

488 token=None, 

489 selection=None, 

490 **kwargs): 

491 

492 ''' 

493 Query FDSN web service for time series data in miniSEED format. 

494 

495 :param site: 

496 :ref:`Registered site name <registered-site-names>` or full base URL of 

497 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

498 :type site: str, optional 

499 :param url: 

500 URL template (default should work in 99% of cases). 

501 :type url: str, optional 

502 :param majorversion: 

503 Major version of the service to query (always ``1`` at the time of 

504 writing). 

505 :type majorversion: int, optional 

506 :param timeout: 

507 Network timeout in [s]. Global default timeout can be configured in 

508 Pyrocko's configuration file under ``fdsn_timeout``. 

509 :type timeout: float, optional 

510 :param check: 

511 If ``True`` arguments are checked against self-description (WADL) of 

512 the queried web service if available or FDSN specification. 

513 :type check: bool, optional 

514 :param user: User name for user/password authentication. 

515 :type user: str, optional 

516 :param passwd: Password for user/password authentication. 

517 :type passwd: str, optional 

518 :param token: Token for `token authentication 

519 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_. 

520 :type token: str, optional 

521 :param selection: 

522 If given, selection to be queried as a list of tuples 

523 ``(network, station, location, channel, tmin, tmax)``. 

524 :type selection: list of tuples, optional 

525 :param \\*\\*kwargs: 

526 Parameters passed to the server (see `FDSN web services specification 

527 <https://www.fdsn.org/webservices>`_). 

528 

529 :returns: 

530 Open file-like object providing raw miniSEED data. 

531 ''' 

532 

533 service = 'dataselect' 

534 

535 if user or token: 

536 method = 'queryauth' 

537 else: 

538 method = 'query' 

539 

540 if token is not None: 

541 user, passwd = get_auth_credentials( 

542 service, site, url, majorversion, token, timeout) 

543 

544 if check: 

545 check_params(service, site, url, majorversion, timeout, **kwargs) 

546 

547 params = fix_params(kwargs) 

548 

549 url = fillurl(service, site, url, majorversion, method) 

550 if selection: 

551 lst = [] 

552 

553 for k, v in params.items(): 

554 lst.append('%s=%s' % (k, v)) 

555 

556 for (network, station, location, channel, tmin, tmax) in selection: 

557 if location == '': 

558 location = '--' 

559 

560 lst.append(' '.join((network, station, location, channel, 

561 sdatetime(tmin), sdatetime(tmax)))) 

562 

563 post = '\n'.join(lst) 

564 return _request( 

565 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout) 

566 else: 

567 return _request( 

568 url, user=user, passwd=passwd, timeout=timeout, **params) 

569 

570 

571def event( 

572 site=g_default_site, 

573 url=g_url, 

574 majorversion=1, 

575 timeout=g_timeout, 

576 check=True, 

577 user=None, 

578 passwd=None, 

579 token=None, 

580 parsed=False, 

581 **kwargs): 

582 

583 ''' 

584 Query FDSN web service for events. 

585 

586 :param site: 

587 :ref:`Registered site name <registered-site-names>` or full base URL of 

588 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

589 :type site: str, optional 

590 :param url: 

591 URL template (default should work in 99% of cases). 

592 :type url: str, optional 

593 :param majorversion: 

594 Major version of the service to query (always ``1`` at the time of 

595 writing). 

596 :type majorversion: int, optional 

597 :param timeout: 

598 Network timeout in [s]. Global default timeout can be configured in 

599 Pyrocko's configuration file under ``fdsn_timeout``. 

600 :type timeout: float, optional 

601 :param check: 

602 If ``True`` arguments are checked against self-description (WADL) of 

603 the queried web service if available or FDSN specification. 

604 :type check: bool, optional 

605 :param user: User name for user/password authentication. 

606 :type user: str, optional 

607 :param passwd: Password for user/password authentication. 

608 :type passwd: str, optional 

609 :param token: Token for `token authentication 

610 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_. 

611 :type token: str, optional 

612 :param parsed: 

613 If ``True`` parse received content into 

614 :py:class:`~pyrocko.io.quakeml.QuakeML` 

615 object, otherwise return open file handle to raw data stream. Note: 

616 by default unparsed data is retrieved, differently from the default 

617 behaviour of :py:func:`station` (for backward compatibility). 

618 :type parsed: bool, optional 

619 :param \\*\\*kwargs: 

620 Parameters passed to the server (see `FDSN web services specification 

621 <https://www.fdsn.org/webservices>`_). 

622 

623 :returns: 

624 See description of ``parsed`` argument above. 

625 ''' 

626 

627 service = 'event' 

628 

629 if user or token: 

630 method = 'queryauth' 

631 else: 

632 method = 'query' 

633 

634 if token is not None: 

635 user, passwd = get_auth_credentials( 

636 service, site, url, majorversion, token, timeout) 

637 

638 if check: 

639 check_params(service, site, url, majorversion, timeout, **kwargs) 

640 

641 params = fix_params(kwargs) 

642 

643 url = fillurl(service, site, url, majorversion, method) 

644 

645 fh = _request(url, user=user, passwd=passwd, timeout=timeout, **params) 

646 if parsed: 

647 from pyrocko.io import quakeml 

648 format = kwargs.get('format', 'xml') 

649 if format != 'xml': 

650 raise InvalidRequest( 

651 'If parsed=True is selected, format="xml" must be selected.') 

652 

653 return quakeml.QuakeML.load_xml(stream=fh) 

654 

655 else: 

656 return fh 

657 

658 

659def availability( 

660 method='query', 

661 site=g_default_site, 

662 url=g_url, 

663 majorversion=1, 

664 timeout=g_timeout, 

665 check=True, 

666 user=None, 

667 passwd=None, 

668 token=None, 

669 selection=None, 

670 **kwargs): 

671 

672 ''' 

673 Query FDSN web service for time series data availablity. 

674 

675 :param method: Availablility method to call: ``'query'``, or ``'extent'``. 

676 :param site: 

677 :ref:`Registered site name <registered-site-names>` or full base URL of 

678 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

679 :type site: str, optional 

680 :param url: 

681 URL template (default should work in 99% of cases). 

682 :type url: str, optional 

683 :param majorversion: 

684 Major version of the service to query (always ``1`` at the time of 

685 writing). 

686 :type majorversion: int, optional 

687 :param timeout: 

688 Network timeout in [s]. Global default timeout can be configured in 

689 Pyrocko's configuration file under ``fdsn_timeout``. 

690 :type timeout: float, optional 

691 :param check: 

692 If ``True`` arguments are checked against self-description (WADL) of 

693 the queried web service if available or FDSN specification. 

694 :type check: bool, optional 

695 :param user: User name for user/password authentication. 

696 :type user: str, optional 

697 :param passwd: Password for user/password authentication. 

698 :type passwd: str, optional 

699 :param token: Token for `token authentication 

700 <https://geofon.gfz-potsdam.de/waveform/archive/auth/auth-overview.php>`_. 

701 :type token: str, optional 

702 :param selection: 

703 If given, selection to be queried as a list of tuples 

704 ``(network, station, location, channel, tmin, tmax)``. 

705 :type selection: list of tuples, optional 

706 :param \\*\\*kwargs: 

707 Parameters passed to the server (see `FDSN web services specification 

708 <https://www.fdsn.org/webservices>`_). 

709 

710 :returns: 

711 Open file-like object providing raw response. 

712 ''' 

713 

714 service = 'availability' 

715 

716 assert method in ('query', 'extent') 

717 

718 if user or token: 

719 method += 'auth' 

720 

721 if token is not None: 

722 user, passwd = get_auth_credentials( 

723 service, site, url, majorversion, token, timeout) 

724 

725 if check: 

726 check_params(service, site, url, majorversion, timeout, **kwargs) 

727 

728 params = fix_params(kwargs) 

729 

730 url = fillurl(service, site, url, majorversion, method) 

731 if selection: 

732 lst = [] 

733 

734 for k, v in params.items(): 

735 lst.append('%s=%s' % (k, v)) 

736 

737 for (network, station, location, channel, tmin, tmax) in selection: 

738 if location == '': 

739 location = '--' 

740 

741 lst.append(' '.join((network, station, location, channel, 

742 sdatetime(tmin), sdatetime(tmax)))) 

743 

744 post = '\n'.join(lst) 

745 return _request( 

746 url, user=user, passwd=passwd, post=post.encode(), timeout=timeout) 

747 else: 

748 return _request( 

749 url, user=user, passwd=passwd, timeout=timeout, **params) 

750 

751 

752def check_params( 

753 service, 

754 site=g_default_site, 

755 url=g_url, 

756 majorversion=1, 

757 timeout=g_timeout, 

758 method='query', 

759 **kwargs): 

760 

761 ''' 

762 Check query parameters against self-description of web service. 

763 

764 Downloads WADL description of the given service and site and checks 

765 parameters if they are available. Queried WADLs are cached in memory. 

766 

767 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or 

768 ``'availability'``. 

769 :param site: 

770 :ref:`Registered site name <registered-site-names>` or full base URL of 

771 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

772 :type site: str, optional 

773 :param url: 

774 URL template (default should work in 99% of cases). 

775 :type url: str, optional 

776 :param majorversion: 

777 Major version of the service to query (always ``1`` at the time of 

778 writing). 

779 :type majorversion: int, optional 

780 :param timeout: 

781 Network timeout in [s]. Global default timeout can be configured in 

782 Pyrocko's configuration file under ``fdsn_timeout``. 

783 :type timeout: float, optional 

784 :param \\*\\*kwargs: 

785 Parameters that would be passed to the server (see `FDSN web services 

786 specification <https://www.fdsn.org/webservices>`_). 

787 

788 :raises: :py:exc:`ValueError` is raised if unsupported parameters are 

789 encountered. 

790 ''' 

791 

792 avail = supported_params_wadl( 

793 service, site, url, majorversion, timeout, method) 

794 

795 unavail = sorted(set(kwargs.keys()) - avail) 

796 if unavail: 

797 raise ValueError( 

798 'Unsupported parameter%s for service "%s" at site "%s": %s' % ( 

799 '' if len(unavail) == 1 else 's', 

800 service, 

801 site, 

802 ', '.join(unavail))) 

803 

804 

805def supported_params_wadl( 

806 service, 

807 site=g_default_site, 

808 url=g_url, 

809 majorversion=1, 

810 timeout=g_timeout, 

811 method='query'): 

812 

813 ''' 

814 Get query parameter names supported by a given FDSN site and service. 

815 

816 If no WADL is provided by the queried service, default parameter sets from 

817 the FDSN standard are returned. Queried WADLs are cached in memory. 

818 

819 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or 

820 ``'availability'``. 

821 :param site: 

822 :ref:`Registered site name <registered-site-names>` or full base URL of 

823 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

824 :type site: str, optional 

825 :param url: 

826 URL template (default should work in 99% of cases). 

827 :type url: str, optional 

828 :param majorversion: 

829 Major version of the service to query (always ``1`` at the time of 

830 writing). 

831 :type majorversion: int, optional 

832 :param timeout: 

833 Network timeout in [s]. Global default timeout can be configured in 

834 Pyrocko's configuration file under ``fdsn_timeout``. 

835 :type timeout: float, optional 

836 

837 :returns: Supported parameter names. 

838 :rtype: set of str 

839 ''' 

840 

841 wadl = cached_wadl(service, site, url, majorversion, timeout) 

842 

843 if wadl: 

844 url = fillurl(service, site, url, majorversion, method) 

845 return set(wadl.supported_param_names(url)) 

846 else: 

847 return g_default_query_args[service] 

848 

849 

850def patch_geonet_wadl(wadl): 

851 for r in wadl.resources_list: 

852 r.base = r.base.replace('1/station', 'station/1') 

853 r.base = r.base.replace('1/dataselect', 'dataselect/1') 

854 r.base = r.base.replace('1/event', 'event/1') 

855 

856 

857def wadl( 

858 service, 

859 site=g_default_site, 

860 url=g_url, 

861 majorversion=1, 

862 timeout=g_timeout): 

863 

864 ''' 

865 Retrieve self-description of a specific FDSN service. 

866 

867 :param service: ``'station'``, ``'dataselect'``, ``'event'`` or 

868 ``'availability'``. 

869 :param site: 

870 :ref:`Registered site name <registered-site-names>` or full base URL of 

871 the service (e.g. ``'https://geofon.gfz-potsdam.de'``). 

872 :type site: str, optional 

873 :param url: 

874 URL template (default should work in 99% of cases). 

875 :type url: str, optional 

876 :param majorversion: 

877 Major version of the service to query (always ``1`` at the time of 

878 writing). 

879 :type majorversion: int, optional 

880 :param timeout: 

881 Network timeout in [s]. Global default timeout can be configured in 

882 Pyrocko's configuration file under ``fdsn_timeout``. 

883 :type timeout: float, optional 

884 ''' 

885 

886 from pyrocko.client.wadl import load_xml 

887 

888 url = fillurl(service, site, url, majorversion, 'application.wadl') 

889 

890 wadl = load_xml(stream=_request(url, timeout=timeout)) 

891 

892 if site == 'geonet' or site.find('geonet.org.nz') != -1: 

893 patch_geonet_wadl(wadl) 

894 

895 return wadl 

896 

897 

898g_wadls = {} 

899 

900 

901def cached_wadl( 

902 service, 

903 site=g_default_site, 

904 url=g_url, 

905 majorversion=1, 

906 timeout=g_timeout): 

907 

908 ''' 

909 Get self-description of a specific FDSN service. 

910 

911 Same as :py:func:`wadl`, but results are cached in memory. 

912 ''' 

913 

914 k = (service, site, url, majorversion) 

915 if k not in g_wadls: 

916 try: 

917 g_wadls[k] = wadl(service, site, url, majorversion, timeout) 

918 

919 except Timeout: 

920 raise 

921 

922 except DownloadError: 

923 logger.info( 

924 'No service description (WADL) found for "%s" at site "%s".' 

925 % (service, site)) 

926 

927 g_wadls[k] = None 

928 

929 return g_wadls[k] 

930 

931 

932__doc__ %= doc_table_dict(g_site_abbr, 'Site name', 'URL', ' ') 

933 

934 

935if __name__ == '__main__': 

936 import sys 

937 

938 util.setup_logging('pyrocko.client.fdsn', 'info') 

939 

940 if len(sys.argv) == 1: 

941 sites = get_sites() 

942 else: 

943 sites = sys.argv[1:] 

944 

945 for site in sites: 

946 print('=== %s (%s) ===' % (site, g_site_abbr[site])) 

947 

948 for service in ['station', 'dataselect', 'event']: 

949 try: 

950 app = wadl(service, site=site, timeout=2.0) 

951 print(indent(str(app))) 

952 

953 except Timeout as e: 

954 logger.error(str(e)) 

955 print('%s: timeout' % (service,)) 

956 

957 except util.DownloadError as e: 

958 logger.error(str(e)) 

959 print('%s: no wadl' % (service,))