requake 0.4__tar.gz → 0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {requake-0.4 → requake-0.5}/CHANGELOG.md +18 -2
  2. {requake-0.4 → requake-0.5}/PKG-INFO +36 -4
  3. {requake-0.4 → requake-0.5}/README.md +32 -3
  4. {requake-0.4 → requake-0.5}/requake/_version.py +3 -3
  5. {requake-0.4 → requake-0.5}/requake/catalog/catalog.py +66 -0
  6. {requake-0.4 → requake-0.5}/requake/catalog/read_catalog.py +64 -10
  7. {requake-0.4 → requake-0.5}/requake/catalog/read_catalog_from_csv.py +11 -1
  8. {requake-0.4 → requake-0.5}/requake/config/configspec.conf +10 -0
  9. {requake-0.4 → requake-0.5}/requake/config/parse_arguments.py +14 -19
  10. {requake-0.4 → requake-0.5}/requake/config/rq_setup.py +50 -10
  11. {requake-0.4 → requake-0.5}/requake/config/utils.py +40 -0
  12. {requake-0.4 → requake-0.5}/requake/families/families.py +7 -0
  13. {requake-0.4 → requake-0.5}/requake/families/print_families.py +15 -2
  14. requake-0.5/requake/main.py +69 -0
  15. {requake-0.4 → requake-0.5}/requake/plot/map_families.py +13 -28
  16. requake-0.5/requake/plot/plot_slip.py +119 -0
  17. requake-0.5/requake/plot/plot_timespans.py +132 -0
  18. requake-0.5/requake/plot/plot_utils.py +148 -0
  19. {requake-0.4 → requake-0.5}/requake/scan/scan_catalog.py +7 -3
  20. {requake-0.4 → requake-0.5}/requake/waveforms/station_metadata.py +3 -3
  21. {requake-0.4 → requake-0.5}/requake/waveforms/waveforms.py +7 -2
  22. {requake-0.4 → requake-0.5}/requake.egg-info/PKG-INFO +36 -4
  23. {requake-0.4 → requake-0.5}/requake.egg-info/SOURCES.txt +2 -0
  24. requake-0.5/requake.egg-info/entry_points.txt +2 -0
  25. {requake-0.4 → requake-0.5}/setup.py +9 -2
  26. requake-0.4/requake/plot/plot_slip.py +0 -121
  27. requake-0.4/requake/plot/plot_timespans.py +0 -145
  28. requake-0.4/requake.egg-info/entry_points.txt +0 -2
  29. {requake-0.4 → requake-0.5}/CONTRIBUTORS.txt +0 -0
  30. {requake-0.4 → requake-0.5}/LICENSE.txt +0 -0
  31. {requake-0.4 → requake-0.5}/MANIFEST.in +0 -0
  32. {requake-0.4 → requake-0.5}/requake/__init__.py +0 -0
  33. {requake-0.4 → requake-0.5}/requake/catalog/__init__.py +0 -0
  34. {requake-0.4 → requake-0.5}/requake/catalog/read_catalog_from_fdsnws.py +0 -0
  35. {requake-0.4 → requake-0.5}/requake/catalog/read_catalog_from_quakeml.py +0 -0
  36. {requake-0.4 → requake-0.5}/requake/config/__init__.py +0 -0
  37. {requake-0.4 → requake-0.5}/requake/config/config.py +0 -0
  38. {requake-0.4 → requake-0.5}/requake/config/configobj/__init__.py +0 -0
  39. {requake-0.4 → requake-0.5}/requake/config/configobj/_version.py +0 -0
  40. {requake-0.4 → requake-0.5}/requake/config/configobj/validate.py +0 -0
  41. {requake-0.4 → requake-0.5}/requake/families/__init__.py +0 -0
  42. {requake-0.4 → requake-0.5}/requake/families/build_families.py +0 -0
  43. {requake-0.4 → requake-0.5}/requake/families/build_templates.py +0 -0
  44. {requake-0.4 → requake-0.5}/requake/families/flag_family.py +0 -0
  45. {requake-0.4 → requake-0.5}/requake/formulas/__init__.py +0 -0
  46. {requake-0.4 → requake-0.5}/requake/formulas/conversion.py +0 -0
  47. {requake-0.4 → requake-0.5}/requake/formulas/slip.py +0 -0
  48. {requake-0.4 → requake-0.5}/requake/plot/__init__.py +0 -0
  49. {requake-0.4 → requake-0.5}/requake/plot/cached_tiler.py +0 -0
  50. {requake-0.4 → requake-0.5}/requake/plot/map_tiles.py +0 -0
  51. {requake-0.4 → requake-0.5}/requake/plot/plot_families.py +0 -0
  52. {requake-0.4 → requake-0.5}/requake/plot/plot_pair.py +0 -0
  53. {requake-0.4 → requake-0.5}/requake/scan/__init__.py +0 -0
  54. {requake-0.4 → requake-0.5}/requake/scan/scan_templates.py +0 -0
  55. {requake-0.4 → requake-0.5}/requake/waveforms/__init__.py +0 -0
  56. {requake-0.4 → requake-0.5}/requake/waveforms/arrivals.py +0 -0
  57. {requake-0.4 → requake-0.5}/requake.egg-info/dependency_links.txt +0 -0
  58. {requake-0.4 → requake-0.5}/requake.egg-info/requires.txt +0 -0
  59. {requake-0.4 → requake-0.5}/requake.egg-info/top_level.txt +0 -0
  60. {requake-0.4 → requake-0.5}/setup.cfg +0 -0
  61. {requake-0.4 → requake-0.5}/versioneer.py +0 -0
@@ -1,6 +1,22 @@
1
- # Requake - Repeating earthquakes search and analysis
1
+ # Requake Changelog
2
2
 
3
- (c) 2021-2024 Claudio Satriano <satriano@ipgp.fr>
3
+ Copyright (c) 2021-2024 Claudio Satriano <satriano@ipgp.fr>
4
+
5
+ ## v0.5 - 2024-04-23
6
+
7
+ - New config options: `station_metadata_path` and `waveform_data_path` to
8
+ read station metadata and waveform data from files. Supports any metadata
9
+ format supported by ObsPy and SDS (SeisComp Data Structure) waveform
10
+ archives.
11
+ - Filter catalog files on reading using the criteria in the config file
12
+ - Improved time axes in `plot_timespans` and `plot_slip` for short time
13
+ intervals
14
+ - `print_families`: autoset duration units based on the average duration
15
+ - `plot_timespans`: default sorting changed to `family_number`
16
+
17
+ ## v0.4.1 - 2024-03-11
18
+
19
+ - Bugfix: `requake` executable was not installed
4
20
 
5
21
  ## v0.4 - 2024-03-11
6
22
 
@@ -1,11 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: requake
3
- Version: 0.4
3
+ Version: 0.5
4
4
  Summary: Repeating earthquakes search and analysis
5
- Home-page: http://www.ipgp.fr/~satriano
5
+ Home-page: https://requake.seismicsource.org
6
6
  Author: Claudio Satriano
7
7
  Author-email: satriano@ipgp.fr
8
8
  License: GNU General Public License v3 or later (GPLv3+)
9
+ Project-URL: Homepage, https://requake.seismicsource.org
10
+ Project-URL: Source, https://github.com/SeismicSource/requake
11
+ Project-URL: Documentation, https://requake.readthedocs.io
9
12
  Platform: OS Independent
10
13
  Classifier: Development Status :: 4 - Beta
11
14
  Classifier: Environment :: Console
@@ -30,6 +33,8 @@ Requires-Dist: argcomplete
30
33
  Requires-Dist: tqdm
31
34
  Requires-Dist: tabulate
32
35
 
36
+ <img src="imgs/Requake_logo.svg" width="400">
37
+
33
38
  # Requake
34
39
 
35
40
  Repeating earthquakes search and analysis.
@@ -37,6 +42,8 @@ Repeating earthquakes search and analysis.
37
42
  [![changelog-badge]][changelog-link]
38
43
  [![PyPI-badge]][PyPI-link]
39
44
  [![license-badge]][license-link]
45
+ [![docs-badge]][docs-link]
46
+ [![DOI-badge]][DOI-link]
40
47
 
41
48
  Copyright (c) 2021-2024 Claudio Satriano <satriano@ipgp.fr>
42
49
 
@@ -47,8 +54,8 @@ Requake is a command line tool to search and analyse repeating earthquakes.
47
54
  It can either scan an existing earthquake catalog to search for similar events,
48
55
  or perform template matching on a continuous waveform stream.
49
56
 
50
- Catalogs and waveforms are downloaded using standard
51
- [FDSN web services](https://www.fdsn.org/webservices/).
57
+ Catalogs and waveforms can be read from local files or downloaded using
58
+ standard [FDSN web services](https://www.fdsn.org/webservices/).
52
59
 
53
60
  Requake is written in Python and uses [ObsPy](https://obspy.org) as backend.
54
61
 
@@ -181,10 +188,35 @@ parallelization will be implemented.
181
188
 
182
189
  - `requake build_families` is fast™.
183
190
 
191
+ ## How to Cite
192
+
193
+ If you used Requake for a scientific paper, please cite it as:
194
+
195
+ > Satriano, C. (2024). Requake: Repeating earthquakes search and analysis (X.Y).
196
+ > [doi: 10.5281/ZENODO.10832204]
197
+
198
+ Please replace `X.Y` with the Requake version number you used.
199
+
200
+ You can also cite the following abstract presented at the
201
+ 2016 AGU Fall Meeting:
202
+
203
+ > Satriano, C., Doucet, A. & Bouin, M.-P. (2021).
204
+ > Probing the creep rate along the Lesser Antilles Subduction Zone through repeating earthquakes.
205
+ > In AGU Fall Meeting Abstracts
206
+ > (Vol. 2021, pp. T25A-0167), [bibcode: 2021AGUFM.T25A0167S]
207
+
184
208
  <!-- Badges and project links -->
185
209
  [PyPI-badge]: http://img.shields.io/pypi/v/requake.svg
186
210
  [PyPI-link]: https://pypi.python.org/pypi/requake
187
211
  [license-badge]: https://img.shields.io/badge/license-GPLv3-green
188
212
  [license-link]: https://www.gnu.org/licenses/gpl-3.0.html
213
+ [docs-badge]: https://readthedocs.org/projects/requake/badge/?version=latest
214
+ [docs-link]: https://requake.readthedocs.io/en/latest/?badge=latest
189
215
  [changelog-badge]: https://img.shields.io/badge/Changelog-136CB6.svg
190
216
  [changelog-link]: https://github.com/SeismicSource/requake/blob/main/CHANGELOG.md
217
+ [DOI-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.10832204.svg
218
+ [DOI-link]: https://doi.org/10.5281/zenodo.10832204
219
+
220
+ <!-- References -->
221
+ [doi: 10.5281/ZENODO.10832204]: https://doi.org/10.5281/ZENODO.10832204
222
+ [bibcode: 2021AGUFM.T25A0167S]: https://ui.adsabs.harvard.edu/abs/2021AGUFM.T25A0167S
@@ -1,3 +1,5 @@
1
+ <img src="imgs/Requake_logo.svg" width="400">
2
+
1
3
  # Requake
2
4
 
3
5
  Repeating earthquakes search and analysis.
@@ -5,6 +7,8 @@ Repeating earthquakes search and analysis.
5
7
  [![changelog-badge]][changelog-link]
6
8
  [![PyPI-badge]][PyPI-link]
7
9
  [![license-badge]][license-link]
10
+ [![docs-badge]][docs-link]
11
+ [![DOI-badge]][DOI-link]
8
12
 
9
13
  Copyright (c) 2021-2024 Claudio Satriano <satriano@ipgp.fr>
10
14
 
@@ -15,8 +19,8 @@ Requake is a command line tool to search and analyse repeating earthquakes.
15
19
  It can either scan an existing earthquake catalog to search for similar events,
16
20
  or perform template matching on a continuous waveform stream.
17
21
 
18
- Catalogs and waveforms are downloaded using standard
19
- [FDSN web services](https://www.fdsn.org/webservices/).
22
+ Catalogs and waveforms can be read from local files or downloaded using
23
+ standard [FDSN web services](https://www.fdsn.org/webservices/).
20
24
 
21
25
  Requake is written in Python and uses [ObsPy](https://obspy.org) as backend.
22
26
 
@@ -149,10 +153,35 @@ parallelization will be implemented.
149
153
 
150
154
  - `requake build_families` is fast™.
151
155
 
156
+ ## How to Cite
157
+
158
+ If you used Requake for a scientific paper, please cite it as:
159
+
160
+ > Satriano, C. (2024). Requake: Repeating earthquakes search and analysis (X.Y).
161
+ > [doi: 10.5281/ZENODO.10832204]
162
+
163
+ Please replace `X.Y` with the Requake version number you used.
164
+
165
+ You can also cite the following abstract presented at the
166
+ 2016 AGU Fall Meeting:
167
+
168
+ > Satriano, C., Doucet, A. & Bouin, M.-P. (2021).
169
+ > Probing the creep rate along the Lesser Antilles Subduction Zone through repeating earthquakes.
170
+ > In AGU Fall Meeting Abstracts
171
+ > (Vol. 2021, pp. T25A-0167), [bibcode: 2021AGUFM.T25A0167S]
172
+
152
173
  <!-- Badges and project links -->
153
174
  [PyPI-badge]: http://img.shields.io/pypi/v/requake.svg
154
175
  [PyPI-link]: https://pypi.python.org/pypi/requake
155
176
  [license-badge]: https://img.shields.io/badge/license-GPLv3-green
156
177
  [license-link]: https://www.gnu.org/licenses/gpl-3.0.html
178
+ [docs-badge]: https://readthedocs.org/projects/requake/badge/?version=latest
179
+ [docs-link]: https://requake.readthedocs.io/en/latest/?badge=latest
157
180
  [changelog-badge]: https://img.shields.io/badge/Changelog-136CB6.svg
158
- [changelog-link]: https://github.com/SeismicSource/requake/blob/main/CHANGELOG.md
181
+ [changelog-link]: https://github.com/SeismicSource/requake/blob/main/CHANGELOG.md
182
+ [DOI-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.10832204.svg
183
+ [DOI-link]: https://doi.org/10.5281/zenodo.10832204
184
+
185
+ <!-- References -->
186
+ [doi: 10.5281/ZENODO.10832204]: https://doi.org/10.5281/ZENODO.10832204
187
+ [bibcode: 2021AGUFM.T25A0167S]: https://ui.adsabs.harvard.edu/abs/2021AGUFM.T25A0167S
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-03-11T14:52:50+0100",
11
+ "date": "2024-04-23T09:52:34+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "d3df3ac972a345394d7a1b05dee98495ec8e3f2f",
15
- "version": "0.4"
14
+ "full-revisionid": "5223a069ca428a69bba654b59dc8bc7b8860cbdd",
15
+ "version": "0.5"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -182,6 +182,72 @@ class RequakeCatalog(list):
182
182
  fp.write(ev.fdsn_text() + '\n')
183
183
 
184
184
 
185
+ def filter(
186
+ self, starttime=None, endtime=None,
187
+ minlatitude=None, maxlatitude=None,
188
+ minlongitude=None, maxlongitude=None,
189
+ mindepth=None, maxdepth=None,
190
+ minmagnitude=None, maxmagnitude=None):
191
+ """
192
+ Filter the catalog, based on the specified criteria.
193
+
194
+ Returns a new catalog.
195
+
196
+ :param starttime: start time
197
+ :type starttime: obspy.UTCDateTime
198
+ :param endtime: end time
199
+ :type endtime: obspy.UTCDateTime
200
+ :param minlatitude: minimum latitude
201
+ :type minlatitude: float
202
+ :param maxlatitude: maximum latitude
203
+ :type maxlatitude: float
204
+ :param minlongitude: minimum longitude
205
+ :type minlongitude: float
206
+ :param maxlongitude: maximum longitude
207
+ :type maxlongitude: float
208
+ :param mindepth: minimum depth (in km)
209
+ :type mindepth: float
210
+ :param maxdepth: maximum depth (in km)
211
+ :type maxdepth: float
212
+ :param minmagnitude: minimum magnitude
213
+ :type minmagnitude: float
214
+ :param maxmagnitude: maximum magnitude
215
+ :type maxmagnitude: float
216
+
217
+ :return: a new catalog
218
+ :rtype: RequakeCatalog
219
+ """
220
+ outcat = RequakeCatalog()
221
+ for ev in self:
222
+ if ev.orig_time is not None:
223
+ if starttime is not None and ev.orig_time < starttime:
224
+ continue
225
+ if endtime is not None and ev.orig_time > endtime:
226
+ continue
227
+ if ev.lat is not None:
228
+ if minlatitude is not None and ev.lat < minlatitude:
229
+ continue
230
+ if maxlatitude is not None and ev.lat > maxlatitude:
231
+ continue
232
+ if ev.lon is not None:
233
+ if minlongitude is not None and ev.lon < minlongitude:
234
+ continue
235
+ if maxlongitude is not None and ev.lon > maxlongitude:
236
+ continue
237
+ if ev.depth is not None:
238
+ if mindepth is not None and ev.depth < mindepth:
239
+ continue
240
+ if maxdepth is not None and ev.depth > maxdepth:
241
+ continue
242
+ if ev.mag is not None:
243
+ if minmagnitude is not None and ev.mag < minmagnitude:
244
+ continue
245
+ if maxmagnitude is not None and ev.mag > maxmagnitude:
246
+ continue
247
+ outcat.append(ev)
248
+ return outcat
249
+
250
+
185
251
  def read_stored_catalog(config):
186
252
  """
187
253
  Read the catalog stored in the output directory.
@@ -9,6 +9,7 @@ Read an event catalog from web services or from a file.
9
9
  GNU General Public License v3.0 or later
10
10
  (https://www.gnu.org/licenses/gpl-3.0-standalone.html)
11
11
  """
12
+ import os
12
13
  import logging
13
14
  import contextlib
14
15
  from ..catalog.catalog import RequakeCatalog
@@ -34,8 +35,14 @@ def _read_catalog_from_file(config):
34
35
  :raises ValueError: if the file format is not supported
35
36
  """
36
37
  catalog_file = config.args.catalog_file
38
+ # return ValueError if the file is empty
39
+ if os.stat(catalog_file).st_size == 0:
40
+ raise ValueError('Empty file')
41
+ # return ValueError if the file is a directory
42
+ if os.path.isdir(catalog_file):
43
+ raise ValueError('Is a directory')
37
44
  # try to read the catalog as a QuakeML file
38
- with contextlib.suppress(TypeError):
45
+ with contextlib.suppress(TypeError, IndexError, ValueError):
39
46
  return read_catalog_from_quakeml(catalog_file)
40
47
  # try to read the catalog as a FDSN text file
41
48
  with contextlib.suppress(ValueError):
@@ -47,6 +54,45 @@ def _read_catalog_from_file(config):
47
54
  return read_catalog_from_csv(catalog_file)
48
55
 
49
56
 
57
+ def _filter_catalog(catalog, config):
58
+ """
59
+ Filter an event catalog, based on the criteria specified
60
+ in the configuration.
61
+
62
+ :param catalog: Event catalog.
63
+ :type catalog: requake.catalog.RequakeCatalog
64
+ :param config: Configuration object.
65
+ :type config: requake.rq_setup.RequakeConfig
66
+ :return: Filtered event catalog.
67
+ :rtype: requake.catalog.RequakeCatalog
68
+ """
69
+ lon_min = config.catalog_lon_min
70
+ lon_max = config.catalog_lon_max
71
+ lat_min = config.catalog_lat_min
72
+ lat_max = config.catalog_lat_max
73
+ depth_min = config.catalog_depth_min
74
+ depth_max = config.catalog_depth_max
75
+ mag_min = config.catalog_mag_min
76
+ mag_max = config.catalog_mag_max
77
+ outcat = catalog
78
+ for start_time, end_time in zip(config.catalog_start_times,
79
+ config.catalog_end_times):
80
+ outcat = outcat.filter(
81
+ starttime=start_time, endtime=end_time,
82
+ minlatitude=lat_min, maxlatitude=lat_max,
83
+ minlongitude=lon_min, maxlongitude=lon_max,
84
+ mindepth=depth_min, maxdepth=depth_max,
85
+ minmagnitude=mag_min, maxmagnitude=mag_max
86
+ )
87
+ nevents_in = len(catalog)
88
+ nevents_out = len(outcat)
89
+ logger.info(
90
+ 'Catalog filtered based on configuration file: '
91
+ f'{nevents_out} events out of {nevents_in}'
92
+ )
93
+ return outcat
94
+
95
+
50
96
  def read_catalog(config):
51
97
  """
52
98
  Read an event catalog from web services or from a file.
@@ -59,34 +105,42 @@ def read_catalog(config):
59
105
  :rtype: requake.catalog.RequakeCatalog
60
106
  """
61
107
  catalog = RequakeCatalog()
62
- out_cat_file = config.scan_catalog_file
108
+ output_cat_file = config.scan_catalog_file
63
109
  nevs_read = 0
64
110
  if config.args.append:
65
111
  with contextlib.suppress(FileNotFoundError):
66
112
  catalog = RequakeCatalog()
67
- catalog.read(out_cat_file)
113
+ catalog.read(output_cat_file)
68
114
  nevs_read = len(catalog)
69
- logger.info(f'{nevs_read} events read from "{out_cat_file}"')
115
+ logger.info(f'{nevs_read} events read from "{output_cat_file}"')
70
116
  logger.info('Reading catalog...')
71
- in_cat_file = config.args.catalog_file
72
- if in_cat_file is not None:
117
+ input_cat_file = config.args.catalog_file
118
+ if input_cat_file is not None:
73
119
  try:
74
120
  catalog += _read_catalog_from_file(config)
121
+ # Filter catalog based on configuration
122
+ catalog = _filter_catalog(catalog, config)
75
123
  except FileNotFoundError:
76
- logger.error(f'File "{in_cat_file}" not found')
124
+ logger.error(f'File "{input_cat_file}" not found')
77
125
  rq_exit(1)
78
126
  except ValueError as m:
79
- logger.error(f'Error reading catalog file "{in_cat_file}": {m}')
127
+ logger.error(f'Error reading catalog file "{input_cat_file}": {m}')
80
128
  rq_exit(1)
81
129
  else:
82
130
  catalog += read_catalog_from_fdsnws(config)
83
131
  if not catalog:
84
132
  logger.error('No event read')
85
133
  rq_exit(1)
134
+ # Deduplicate catalog
135
+ len_before = len(catalog)
86
136
  catalog.deduplicate()
137
+ len_after = len(catalog)
138
+ nevs_dedup = len_before - len_after
139
+ if nevs_dedup > 0:
140
+ logger.info(f'{nevs_dedup} duplicate events removed')
87
141
  # Sort catalog in increasing time order
88
142
  catalog.sort()
89
143
  # Write catalog to output file
90
- catalog.write(out_cat_file)
144
+ catalog.write(output_cat_file)
91
145
  nevs_written = len(catalog) - nevs_read
92
- logger.info(f'{nevs_written} events written to "{out_cat_file}"')
146
+ logger.info(f'{nevs_written} events written to "{output_cat_file}"')
@@ -183,7 +183,17 @@ def read_catalog_from_csv(filename):
183
183
  year=year, month=month, day=day,
184
184
  hour=hour, minute=minute, second=0) + seconds
185
185
  else:
186
- orig_time = UTCDateTime(row[fields['orig_time']])
186
+ try:
187
+ orig_time = UTCDateTime(row[fields['orig_time']])
188
+ except ValueError:
189
+ # one last try: check if the time is in the format
190
+ # YYYYMMDD.hhmmss.
191
+ # Replace the dot with a space, pad with zeros
192
+ # and try again
193
+ orig_time = UTCDateTime(
194
+ row[fields['orig_time']]
195
+ .replace('.', ' ').ljust(15, '0')
196
+ )
187
197
  row[None] = None
188
198
  ev = RequakeEvent()
189
199
  ev.orig_time = orig_time
@@ -4,9 +4,19 @@
4
4
  ## FDSN station webservice URL to retrieve station metadata.
5
5
  ## Use a full URL or a ObsPy shortcut (e.g., IRIS, RESIF, GEOFON, ...)
6
6
  fdsn_station_url = string(default=http://example.com)
7
+ ## Alternatively, you can provide the path to a local file with station
8
+ ## metadata in any format supported by ObsPy (note that some formats may not
9
+ ## contain station coordinates, which are required for the scan,
10
+ ## see https://docs.obspy.org/packages/obspy.core.inventory.html).
11
+ ## If both fdsn_station_url and stationxml_file are provided, the
12
+ ## local file will be used.
13
+ station_metadata_path = string(default=None)
7
14
  ## FDSN dataselect webservice URL to retrieve waveforms.
8
15
  ## Use a full URL or a ObsPy shortcut (e.g., IRIS, RESIF, GEOFON, ...)
9
16
  fdsn_dataselect_url = string(default=http://example.com)
17
+ ## Alternatively, you can provide the path to a local SDS waveform archive
18
+ ## (see https://docs.obspy.org/packages/autogen/obspy.clients.filesystem.sds.html)
19
+ waveform_data_path = string(default=None)
10
20
 
11
21
  #### Catalog-based scan
12
22
  ### The following parameters are for a catalog-based scan:
@@ -132,7 +132,7 @@ def parse_arguments(progname='requake'):
132
132
  '-l', '--longerthan', type=str, default=0, metavar='DURATION',
133
133
  help='only use families lasting longer than this value. '
134
134
  'You can specify DURATION in days (e.g., 100d) '
135
- 'or in years (e.g., 2.5y).'
135
+ 'or in years (e.g., 2.5y)'
136
136
  )
137
137
  # ---
138
138
  # --- minevents
@@ -141,7 +141,7 @@ def parse_arguments(progname='requake'):
141
141
  minevents = argparse.ArgumentParser(add_help=False)
142
142
  minevents.add_argument(
143
143
  '-m', '--minevents', type=int, default=0, metavar='NEVENTS',
144
- help='only use families with at least NEVENTS events.'
144
+ help='only use families with at least NEVENTS events'
145
145
  )
146
146
  # ---
147
147
  # --- familynumbers
@@ -153,7 +153,7 @@ def parse_arguments(progname='requake'):
153
153
  help='family numbers to use. It can be a single number, '
154
154
  'a comma-separated list (ex.: 1,4,8), '
155
155
  'a hyphen-separated number range (ex.: 2-12), or "all" '
156
- '(default: "all"). '
156
+ '(default: %(default)s)'
157
157
  )
158
158
  # ---
159
159
  # --- traceid
@@ -162,7 +162,7 @@ def parse_arguments(progname='requake'):
162
162
  traceid = argparse.ArgumentParser(add_help=False)
163
163
  traceid.add_argument(
164
164
  '-t', '--traceid', type=str, default=None,
165
- help='use this traceid instead of the default one for the family.'
165
+ help='use this traceid instead of the default one for the family'
166
166
  )
167
167
  # ---
168
168
  # --- print_families
@@ -173,8 +173,8 @@ def parse_arguments(progname='requake'):
173
173
  )
174
174
  printfamilies.add_argument(
175
175
  '-f', '--format', type=str, default='simple',
176
- help='format for the output table.'
177
- 'Choose between "simple", "markdown" and "csv"'
176
+ choices=['simple', 'markdown', 'csv'],
177
+ help='format for the output table (default: %(default)s)'
178
178
  )
179
179
  # ---
180
180
  # --- plot_families
@@ -185,11 +185,11 @@ def parse_arguments(progname='requake'):
185
185
  )
186
186
  plotfamilies.add_argument(
187
187
  '-s', '--starttime', type=float, default=None,
188
- help='start time, in seconds relative to trace start, for the plot.'
188
+ help='start time, in seconds relative to trace start, for the plot'
189
189
  )
190
190
  plotfamilies.add_argument(
191
191
  '-e', '--endtime', type=float, default=None,
192
- help='end time, in seconds relative to trace start, for the plot.'
192
+ help='end time, in seconds relative to trace start, for the plot'
193
193
  )
194
194
  # ---
195
195
  # --- plot_timespans
@@ -199,15 +199,12 @@ def parse_arguments(progname='requake'):
199
199
  help='plot family timespans'
200
200
  )
201
201
  timespans.add_argument(
202
- '-s', '--sortby', type=str, default=None,
202
+ '-s', '--sortby', type=str, default='family_number',
203
203
  choices=[
204
204
  'time', 'latitude', 'longitude', 'depth', 'distance_from',
205
205
  'family_number'
206
206
  ],
207
- help='quantity to sort families by on y-axis. '
208
- 'Possible values are: time, latitude, longitude, depth, '
209
- 'distance_from, family_number. If not specified, the config '
210
- 'value "sort_families_by" will be used.'
207
+ help='quantity to sort families by on y-axis (default: %(default)s)'
211
208
  )
212
209
  # ---
213
210
  # --- plot_slip
@@ -229,19 +226,17 @@ def parse_arguments(progname='requake'):
229
226
  'stamen_terrain', 'satellite', 'street', 'hillshade',
230
227
  'hillshade_dark', 'ocean'
231
228
  ],
232
- help='style of map to plot. Possible values are: '
233
- 'stamen_terrain, satellite, street, hillshade, hillshade_dark, '
234
- 'ocean'
229
+ help='style of map to plot (default: %(default)s)'
235
230
  )
236
231
  mapfamilies.add_argument(
237
232
  '-k', '--apikey', type=str, default=None,
238
- help='API key for Stamen Terrain map tiles (default: None). '
233
+ help='API key for Stamen Terrain map tiles (default: %(default)s). '
239
234
  'You can get a free API key at https://stadiamaps.com/'
240
235
  )
241
236
  mapfamilies.add_argument(
242
237
  '-z', '--zoom', type=int, default=None,
243
238
  help='zoom level for the map tiles (default: None). Note that '
244
- 'certain map tiles might not be available at all zoom levels.'
239
+ 'certain map tiles might not be available at all zoom levels'
245
240
  )
246
241
  # ---
247
242
  # --- flag_family
@@ -254,7 +249,7 @@ def parse_arguments(progname='requake'):
254
249
  flagfamily.add_argument(
255
250
  'is_valid',
256
251
  help='"true" (or "t") to flag family as valid, '
257
- '"false" (or "f") to flag family as not valid.'
252
+ '"false" (or "f") to flag family as not valid'
258
253
  )
259
254
  # ---
260
255
  # --- build_templates
@@ -17,7 +17,10 @@ import logging
17
17
  import signal
18
18
  import tqdm
19
19
  from obspy import UTCDateTime
20
- from obspy.clients.fdsn import Client
20
+ from obspy import read_inventory
21
+ from obspy.clients.filesystem.sds import Client as SDSClient
22
+ from obspy.clients.fdsn import Client as FDSNClient
23
+ from obspy.clients.fdsn.header import FDSNNoServiceException
21
24
  from .._version import get_versions
22
25
  from .config import Config
23
26
  from .utils import (
@@ -104,14 +107,51 @@ def _setup_logging(config, progname, action_name):
104
107
  logger.debug(' '.join(sys.argv))
105
108
 
106
109
 
107
- def _connect_fdsn_station_dataselect(config):
108
- """Connect to FDSN station and dataselect services."""
109
- config.fdsn_station_client = Client(config.fdsn_station_url)
110
- logger.info(f'Connected to FDSN station server: {config.fdsn_station_url}')
111
- config.fdsn_dataselect_client = Client(config.fdsn_dataselect_url)
110
+ def _connect_station_dataselect(config):
111
+ """
112
+ Connect to station and dataselect services.
113
+
114
+ Those can be either FDSN web services or local files.
115
+ """
116
+ if config.station_metadata_path is not None:
117
+ config.inventory = read_inventory(config.station_metadata_path)
118
+ config.station_client = None
119
+ logger.info(
120
+ 'Reading station metadata from local file: '
121
+ f'{config.station_metadata_path}'
122
+ )
123
+ else:
124
+ config.station_client = FDSNClient(config.fdsn_station_url)
125
+ logger.info(
126
+ f'Connected to FDSN station server: {config.fdsn_station_url}'
127
+ )
128
+ if config.waveform_data_path is not None:
129
+ _connect_sds(config)
130
+ else:
131
+ config.dataselect_client = FDSNClient(config.fdsn_dataselect_url)
132
+ logger.info(
133
+ 'Connected to FDSN dataselect server: '
134
+ f'{config.fdsn_dataselect_url}'
135
+ )
136
+
137
+
138
+ def _connect_sds(config):
139
+ """
140
+ Connect to a local SeisComP Data Structure (SDS) archive.
141
+ """
142
+ _client = SDSClient(config.waveform_data_path)
143
+ all_nslc = _client.get_all_nslc()
144
+ if not all_nslc:
145
+ raise FileNotFoundError(
146
+ f'No SDS archive found in {config.waveform_data_path}'
147
+ )
148
+ config.dataselect_client = _client
112
149
  logger.info(
113
- f'Connected to FDSN dataselect server: {config.fdsn_dataselect_url}'
150
+ 'Reading waveform data from local SDS archive: '
151
+ f'{config.waveform_data_path}'
114
152
  )
153
+ all_nslc_str = '\n'.join('.'.join(nslc) for nslc in all_nslc)
154
+ logger.info(f'Found the following NSLC codes:\n{all_nslc_str}')
115
155
 
116
156
 
117
157
  def _parse_catalog_options(config):
@@ -140,7 +180,7 @@ def _connect_fdsn_catalog(config):
140
180
  """Connect to FDSN catalog services."""
141
181
  config.catalog_fdsn_event_clients = []
142
182
  for url in config.catalog_fdsn_event_urls:
143
- config.catalog_fdsn_event_clients.append(Client(url))
183
+ config.catalog_fdsn_event_clients.append(FDSNClient(url))
144
184
  logger.info(f'Connected to FDSN event server: {url}')
145
185
 
146
186
 
@@ -205,10 +245,10 @@ def configure(args):
205
245
  )
206
246
  try:
207
247
  if args.action in actions_needing_fdsn_station_dataselect:
208
- _connect_fdsn_station_dataselect(config)
248
+ _connect_station_dataselect(config)
209
249
  if args.action == 'read_catalog' and not args.catalog_file:
210
250
  _connect_fdsn_catalog(config)
211
- except Exception as m:
251
+ except (FileNotFoundError, ValueError, FDSNNoServiceException) as m:
212
252
  logger.error(m)
213
253
  rq_exit(1)
214
254
  # Template times must be UTCDateTime objects
@@ -12,6 +12,8 @@ Utility functions for Requake.
12
12
  import os
13
13
  import sys
14
14
  import locale
15
+ import traceback
16
+ from .. import __version__
15
17
  from .configobj import ConfigObj
16
18
  from .configobj.validate import Validator
17
19
  locale.setlocale(locale.LC_ALL, '')
@@ -129,3 +131,41 @@ def validate_config(config_obj):
129
131
  sys.exit(1)
130
132
  if not test:
131
133
  err_exit('No configuration value present!')
134
+
135
+
136
+ def manage_uncaught_exception(exception):
137
+ """
138
+ Manage an uncaught exception.
139
+
140
+ :param exception: Exception object.
141
+ :type exception: Exception
142
+ """
143
+ sys.stderr.write("""
144
+ # BEGIN TRACEBACK #############################################################
145
+ """)
146
+ sys.stderr.write('\n')
147
+ traceback.print_exc()
148
+ sys.stderr.write("""
149
+ # END TRACEBACK ###############################################################
150
+ """)
151
+ sys.stderr.write("""
152
+
153
+ Congratulations, you've found a bug in Requake! 🐞
154
+
155
+ Please report it on https://github.com/SeismicSource/requake/issues
156
+ or by email to satriano@ipgp.fr.
157
+
158
+ Include the following information in your report:
159
+
160
+ """)
161
+ sys.stderr.write(f' Requake version: {__version__}\n')
162
+ sys.stderr.write(f' Python version: {sys.version}\n')
163
+ sys.stderr.write(f' Platform: {sys.platform}\n')
164
+ sys.stderr.write(f' Command line: {" ".join(sys.argv)}\n')
165
+ sys.stderr.write(f' Error message: {str(exception)}\n')
166
+ sys.stderr.write('\n')
167
+ sys.stderr.write(
168
+ 'Also, please copy and paste the traceback above in your '
169
+ 'report.\n\n')
170
+ sys.stderr.write('Thank you for your help!\n\n')
171
+ sys.exit(1)