requake 0.7.2__tar.gz → 0.8__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.
- {requake-0.7.2 → requake-0.8}/CHANGELOG.md +39 -10
- {requake-0.7.2 → requake-0.8}/PKG-INFO +11 -2
- {requake-0.7.2 → requake-0.8}/README.md +9 -0
- {requake-0.7.2 → requake-0.8}/requake/_version.py +3 -3
- {requake-0.7.2 → requake-0.8}/requake/catalog/catalog.py +48 -87
- {requake-0.7.2 → requake-0.8}/requake/catalog/print_catalog.py +1 -3
- {requake-0.7.2 → requake-0.8}/requake/catalog/read_catalog.py +27 -11
- {requake-0.7.2 → requake-0.8}/requake/catalog/read_catalog_from_csv.py +5 -7
- {requake-0.7.2 → requake-0.8}/requake/config/__init__.py +5 -1
- requake-0.8/requake/config/config.py +125 -0
- requake-0.8/requake/config/configobj/_version.py +6 -0
- {requake-0.7.2 → requake-0.8}/requake/config/generic_printer.py +1 -1
- {requake-0.7.2 → requake-0.8}/requake/config/parse_arguments.py +14 -15
- {requake-0.7.2 → requake-0.8}/requake/config/rq_setup.py +9 -23
- {requake-0.7.2 → requake-0.8}/requake/config/utils.py +22 -7
- requake-0.8/requake/database/__init__.py +12 -0
- requake-0.8/requake/database/catalog.py +136 -0
- requake-0.8/requake/database/db.py +158 -0
- requake-0.8/requake/database/families.py +174 -0
- requake-0.8/requake/database/pairs.py +180 -0
- requake-0.8/requake/database/templates.py +175 -0
- {requake-0.7.2 → requake-0.8}/requake/families/__init__.py +3 -2
- {requake-0.7.2 → requake-0.8}/requake/families/build_families.py +19 -26
- {requake-0.7.2 → requake-0.8}/requake/families/build_templates.py +1 -3
- {requake-0.7.2 → requake-0.8}/requake/families/families.py +21 -71
- requake-0.8/requake/families/flag_family.py +31 -0
- {requake-0.7.2 → requake-0.8}/requake/families/pairs.py +14 -44
- {requake-0.7.2 → requake-0.8}/requake/families/print_families.py +3 -5
- {requake-0.7.2 → requake-0.8}/requake/families/print_pairs.py +1 -3
- {requake-0.7.2 → requake-0.8}/requake/formulas/conversion.py +2 -4
- {requake-0.7.2 → requake-0.8}/requake/formulas/moment.py +2 -2
- {requake-0.7.2 → requake-0.8}/requake/formulas/slip.py +6 -6
- {requake-0.7.2 → requake-0.8}/requake/main.py +1 -1
- {requake-0.7.2 → requake-0.8}/requake/plot/cached_tiler.py +1 -1
- {requake-0.7.2 → requake-0.8}/requake/plot/colormaps.py +0 -1
- {requake-0.7.2 → requake-0.8}/requake/plot/map_families.py +1 -3
- {requake-0.7.2 → requake-0.8}/requake/plot/map_tiles.py +25 -25
- {requake-0.7.2 → requake-0.8}/requake/plot/plot_cumulative.py +5 -11
- {requake-0.7.2 → requake-0.8}/requake/plot/plot_families.py +23 -20
- {requake-0.7.2 → requake-0.8}/requake/plot/plot_pair.py +1 -3
- {requake-0.7.2 → requake-0.8}/requake/plot/plot_timespans.py +2 -6
- {requake-0.7.2 → requake-0.8}/requake/plot/plot_utils.py +11 -12
- {requake-0.7.2 → requake-0.8}/requake/scan/scan_catalog.py +29 -31
- {requake-0.7.2 → requake-0.8}/requake/scan/scan_templates.py +36 -47
- {requake-0.7.2 → requake-0.8}/requake/waveforms/waveform_pair.py +1 -0
- {requake-0.7.2 → requake-0.8}/requake/waveforms/waveforms.py +9 -12
- {requake-0.7.2 → requake-0.8}/requake.egg-info/PKG-INFO +11 -2
- {requake-0.7.2 → requake-0.8}/requake.egg-info/SOURCES.txt +6 -0
- {requake-0.7.2 → requake-0.8}/setup.py +1 -1
- requake-0.7.2/requake/config/config.py +0 -31
- requake-0.7.2/requake/config/configobj/_version.py +0 -2
- requake-0.7.2/requake/families/flag_family.py +0 -46
- {requake-0.7.2 → requake-0.8}/CONTRIBUTORS.txt +0 -0
- {requake-0.7.2 → requake-0.8}/LICENSE.txt +0 -0
- {requake-0.7.2 → requake-0.8}/MANIFEST.in +0 -0
- {requake-0.7.2 → requake-0.8}/pyproject.toml +0 -0
- {requake-0.7.2 → requake-0.8}/requake/__init__.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/catalog/__init__.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/catalog/read_catalog_from_fdsnws.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/catalog/read_catalog_from_quakeml.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/config/configobj/LICENSE +0 -0
- {requake-0.7.2 → requake-0.8}/requake/config/configobj/__init__.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/config/configobj/validate.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/config/configspec.conf +0 -0
- {requake-0.7.2 → requake-0.8}/requake/formulas/__init__.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/plot/__init__.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/scan/__init__.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/waveforms/__init__.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/waveforms/arrivals.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake/waveforms/station_metadata.py +0 -0
- {requake-0.7.2 → requake-0.8}/requake.egg-info/dependency_links.txt +0 -0
- {requake-0.7.2 → requake-0.8}/requake.egg-info/entry_points.txt +0 -0
- {requake-0.7.2 → requake-0.8}/requake.egg-info/requires.txt +0 -0
- {requake-0.7.2 → requake-0.8}/requake.egg-info/top_level.txt +0 -0
- {requake-0.7.2 → requake-0.8}/setup.cfg +0 -0
|
@@ -2,17 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2021-2026 Claudio Satriano <satriano@ipgp.fr>
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## [0.8] - 2026-05-22
|
|
6
|
+
|
|
7
|
+
This release introduces a major storage change.
|
|
8
|
+
|
|
9
|
+
Requake now stores scan-related data (catalog, event pairs, and families) in a
|
|
10
|
+
SQLite database instead of CSV files. This improves storage efficiency and
|
|
11
|
+
data access performance, especially for large catalogs.
|
|
12
|
+
|
|
13
|
+
This change is not backward compatible at the data format level. The
|
|
14
|
+
high-level behavior of the command-line interface remains unchanged.
|
|
15
|
+
|
|
16
|
+
## [0.7.3] - 2026-04-14
|
|
6
17
|
|
|
7
18
|
This release is intended to validate the new release workflow.
|
|
8
19
|
It introduces no new features or bug fixes.
|
|
9
20
|
|
|
10
|
-
##
|
|
21
|
+
## [0.7.2] - 2026-04-13
|
|
11
22
|
|
|
12
23
|
This release is intended to validate the new release workflow.
|
|
13
24
|
It introduces no new features or bug fixes.
|
|
14
25
|
|
|
15
|
-
##
|
|
26
|
+
## [0.7.1] - 2026-04-13
|
|
27
|
+
|
|
28
|
+
This release is intended to validate the new release workflow.
|
|
29
|
+
It introduces no new features or bug fixes.
|
|
30
|
+
|
|
31
|
+
## [0.7] - 2026-04-10
|
|
16
32
|
|
|
17
33
|
This version requires at least Python 3.9.
|
|
18
34
|
|
|
@@ -52,7 +68,7 @@ to the latest version.
|
|
|
52
68
|
- warn if an invalid time format is found
|
|
53
69
|
- Colored terminal output for warnings and errors
|
|
54
70
|
|
|
55
|
-
##
|
|
71
|
+
## [0.6] - 2024-05-04
|
|
56
72
|
|
|
57
73
|
Note: you might want to run `requake update_config` to update your config file
|
|
58
74
|
to the latest version.
|
|
@@ -69,7 +85,7 @@ to the latest version.
|
|
|
69
85
|
- New verb `update_config`: update an existing config file to the latest
|
|
70
86
|
version
|
|
71
87
|
|
|
72
|
-
##
|
|
88
|
+
## [0.5] - 2024-04-23
|
|
73
89
|
|
|
74
90
|
- New config options: `station_metadata_path` and `waveform_data_path` to
|
|
75
91
|
read station metadata and waveform data from files. Supports any metadata
|
|
@@ -81,11 +97,11 @@ to the latest version.
|
|
|
81
97
|
- `print_families`: autoset duration units based on the average duration
|
|
82
98
|
- `plot_timespans`: default sorting changed to `family_number`
|
|
83
99
|
|
|
84
|
-
##
|
|
100
|
+
## [0.4.1] - 2024-03-11
|
|
85
101
|
|
|
86
102
|
- Bugfix: `requake` executable was not installed
|
|
87
103
|
|
|
88
|
-
##
|
|
104
|
+
## [0.4] - 2024-03-11
|
|
89
105
|
|
|
90
106
|
- Package license changed to GPL-3.0 or later
|
|
91
107
|
- New verb: `read_catalog`: read a catalog from web services or from a file
|
|
@@ -109,7 +125,7 @@ to the latest version.
|
|
|
109
125
|
- Support for events with no location
|
|
110
126
|
- Bugfix: `plot_timespans`: correctly plot x time axis when sort by `time`
|
|
111
127
|
|
|
112
|
-
##
|
|
128
|
+
## [0.3] - 2021-11-08
|
|
113
129
|
|
|
114
130
|
- New verb: `build_templates`
|
|
115
131
|
- New option: `cc_allow_negative` to search for anticorrelated events
|
|
@@ -123,7 +139,7 @@ to the latest version.
|
|
|
123
139
|
- also shift theoretical arrivals when aligning traces
|
|
124
140
|
- Bugfix: last family was not read from family file
|
|
125
141
|
|
|
126
|
-
##
|
|
142
|
+
## [0.2] - 2021-09-24
|
|
127
143
|
|
|
128
144
|
Mostly a bugfix release with a slightly improved trace plotting.
|
|
129
145
|
|
|
@@ -131,6 +147,19 @@ Mostly a bugfix release with a slightly improved trace plotting.
|
|
|
131
147
|
- Bugfix: detrend traces before filtering
|
|
132
148
|
- `plot_families`: print number of events and station-event distance
|
|
133
149
|
|
|
134
|
-
##
|
|
150
|
+
## [0.1] - 2021-09-16
|
|
135
151
|
|
|
136
152
|
- Initial release, not yet feature complete
|
|
153
|
+
|
|
154
|
+
[0.8]: https://github.com/SeismicSource/requake/compare/v0.7.3...v0.8
|
|
155
|
+
[0.7.3]: https://github.com/SeismicSource/requake/compare/v0.7.2...v0.7.3
|
|
156
|
+
[0.7.2]: https://github.com/SeismicSource/requake/compare/v0.7.1...v0.7.2
|
|
157
|
+
[0.7.1]: https://github.com/SeismicSource/requake/compare/v0.7...v0.7.1
|
|
158
|
+
[0.7]: https://github.com/SeismicSource/requake/compare/v0.6...v0.7
|
|
159
|
+
[0.6]: https://github.com/SeismicSource/requake/compare/v0.5...v0.6
|
|
160
|
+
[0.5]: https://github.com/SeismicSource/requake/compare/v0.4.1...v0.5
|
|
161
|
+
[0.4.1]: https://github.com/SeismicSource/requake/compare/v0.4...v0.4.1
|
|
162
|
+
[0.4]: https://github.com/SeismicSource/requake/compare/v0.3...v0.4
|
|
163
|
+
[0.3]: https://github.com/SeismicSource/requake/compare/v0.2...v0.3
|
|
164
|
+
[0.2]: https://github.com/SeismicSource/requake/compare/v0.1...v0.2
|
|
165
|
+
[0.1]: https://github.com/SeismicSource/requake/releases/tag/v0.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: requake
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8
|
|
4
4
|
Summary: Repeating earthquakes search and analysis
|
|
5
5
|
Author-email: Claudio Satriano <satriano@ipgp.fr>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -37,7 +37,7 @@ Dynamic: description
|
|
|
37
37
|
Dynamic: description-content-type
|
|
38
38
|
Dynamic: license-file
|
|
39
39
|
|
|
40
|
-
<img src="https://cdn.jsdelivr.net/gh/SeismicSource/requake@
|
|
40
|
+
<img src="https://cdn.jsdelivr.net/gh/SeismicSource/requake@e3ac4cce14f782d62a5f22f432a5391a2ccacfe7/imgs/Requake_logo.svg" width="400">
|
|
41
41
|
|
|
42
42
|
# Requake
|
|
43
43
|
|
|
@@ -140,6 +140,15 @@ You can keep your local Requake repository updated by running `git pull`
|
|
|
140
140
|
from times to times. Thanks to `pip`'s "editable mode", you don't need to
|
|
141
141
|
reinstall Requake after each update.
|
|
142
142
|
|
|
143
|
+
If you want the repository style rules to run locally before each commit,
|
|
144
|
+
enable the included git hook once per clone:
|
|
145
|
+
|
|
146
|
+
scripts/setup_local_hooks.sh
|
|
147
|
+
|
|
148
|
+
You can also run the local style checks manually with:
|
|
149
|
+
|
|
150
|
+
scripts/check_style.sh
|
|
151
|
+
|
|
143
152
|
## Running
|
|
144
153
|
|
|
145
154
|
### Command line arguments
|
|
@@ -101,6 +101,15 @@ You can keep your local Requake repository updated by running `git pull`
|
|
|
101
101
|
from times to times. Thanks to `pip`'s "editable mode", you don't need to
|
|
102
102
|
reinstall Requake after each update.
|
|
103
103
|
|
|
104
|
+
If you want the repository style rules to run locally before each commit,
|
|
105
|
+
enable the included git hook once per clone:
|
|
106
|
+
|
|
107
|
+
scripts/setup_local_hooks.sh
|
|
108
|
+
|
|
109
|
+
You can also run the local style checks manually with:
|
|
110
|
+
|
|
111
|
+
scripts/check_style.sh
|
|
112
|
+
|
|
104
113
|
## Running
|
|
105
114
|
|
|
106
115
|
### Command line arguments
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2026-
|
|
11
|
+
"date": "2026-05-22T08:53:09+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.
|
|
14
|
+
"full-revisionid": "e3ac4cce14f782d62a5f22f432a5391a2ccacfe7",
|
|
15
|
+
"version": "0.8"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -28,6 +28,7 @@ class RequakeEvent():
|
|
|
28
28
|
depth=None, mag_type=None, mag=None, author=None,
|
|
29
29
|
catalog=None, contributor=None, contributor_id=None,
|
|
30
30
|
mag_author=None, location_name=None, trace_id=None):
|
|
31
|
+
"""Initialize an event container."""
|
|
31
32
|
self.evid = evid
|
|
32
33
|
self.orig_time = orig_time
|
|
33
34
|
self.lon = lon
|
|
@@ -45,24 +46,31 @@ class RequakeEvent():
|
|
|
45
46
|
self.correlations = {}
|
|
46
47
|
|
|
47
48
|
def __eq__(self, other):
|
|
49
|
+
"""Return True when two events have the same identity."""
|
|
48
50
|
return self.evid == other.evid and self.trace_id == other.trace_id
|
|
49
51
|
|
|
50
52
|
def __gt__(self, other):
|
|
53
|
+
"""Compare events by origin time."""
|
|
51
54
|
return self.orig_time > other.orig_time
|
|
52
55
|
|
|
53
56
|
def __ge__(self, other):
|
|
57
|
+
"""Compare events by origin time."""
|
|
54
58
|
return self.orig_time >= other.orig_time
|
|
55
59
|
|
|
56
60
|
def __lt__(self, other):
|
|
61
|
+
"""Compare events by origin time."""
|
|
57
62
|
return self.orig_time < other.orig_time
|
|
58
63
|
|
|
59
64
|
def __le__(self, other):
|
|
65
|
+
"""Compare events by origin time."""
|
|
60
66
|
return self.orig_time <= other.orig_time
|
|
61
67
|
|
|
62
68
|
def __hash__(self):
|
|
69
|
+
"""Return the event hash."""
|
|
63
70
|
return self.evid.__hash__()
|
|
64
71
|
|
|
65
72
|
def __str__(self):
|
|
73
|
+
"""Return a compact string representation of the event."""
|
|
66
74
|
return (
|
|
67
75
|
f'{self.evid} {self.orig_time} '
|
|
68
76
|
f'{self.lon} {self.lat} {self.depth} {self.mag_type} {self.mag}'
|
|
@@ -148,46 +156,12 @@ class RequakeCatalog(list):
|
|
|
148
156
|
"""
|
|
149
157
|
self[:] = sorted(self, key=lambda ev: ev.orig_time)
|
|
150
158
|
|
|
151
|
-
def read(self, filename):
|
|
152
|
-
"""
|
|
153
|
-
Read catalog from FDSN text file format.
|
|
154
|
-
|
|
155
|
-
Skips events already in the catalog.
|
|
156
|
-
|
|
157
|
-
:param filename: input filename
|
|
158
|
-
:type filename: str
|
|
159
|
-
|
|
160
|
-
:raises FileNotFoundError: if filename does not exist
|
|
161
|
-
:raises ValueError: if line is not in FDSN text file format
|
|
162
|
-
"""
|
|
163
|
-
with open(filename, 'r', encoding='utf8') as fp:
|
|
164
|
-
for line in fp:
|
|
165
|
-
if not line:
|
|
166
|
-
continue
|
|
167
|
-
if line[0] == '#':
|
|
168
|
-
continue
|
|
169
|
-
ev = RequakeEvent()
|
|
170
|
-
ev.from_fdsn_text(line)
|
|
171
|
-
self.append(ev)
|
|
172
|
-
self.deduplicate()
|
|
173
|
-
|
|
174
|
-
def write(self, filename):
|
|
175
|
-
"""
|
|
176
|
-
Write catalog in FDSN text file format.
|
|
177
|
-
|
|
178
|
-
:param filename: output filename
|
|
179
|
-
:type filename: str
|
|
180
|
-
"""
|
|
181
|
-
with open(filename, 'w', encoding='utf8') as fp:
|
|
182
|
-
for ev in self:
|
|
183
|
-
fp.write(ev.fdsn_text() + '\n')
|
|
184
|
-
|
|
185
159
|
def filter(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
160
|
+
self, starttime=None, endtime=None,
|
|
161
|
+
minlatitude=None, maxlatitude=None,
|
|
162
|
+
minlongitude=None, maxlongitude=None,
|
|
163
|
+
mindepth=None, maxdepth=None,
|
|
164
|
+
minmagnitude=None, maxmagnitude=None
|
|
191
165
|
):
|
|
192
166
|
"""
|
|
193
167
|
Filter the catalog, based on the specified criteria.
|
|
@@ -220,60 +194,47 @@ class RequakeCatalog(list):
|
|
|
220
194
|
"""
|
|
221
195
|
outcat = RequakeCatalog()
|
|
222
196
|
for ev in self:
|
|
223
|
-
if ev.orig_time
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if ev.lon is not None:
|
|
234
|
-
if minlongitude is not None and ev.lon < minlongitude:
|
|
235
|
-
continue
|
|
236
|
-
if maxlongitude is not None and ev.lon > maxlongitude:
|
|
237
|
-
continue
|
|
238
|
-
if ev.depth is not None:
|
|
239
|
-
if mindepth is not None and ev.depth < mindepth:
|
|
240
|
-
continue
|
|
241
|
-
if maxdepth is not None and ev.depth > maxdepth:
|
|
242
|
-
continue
|
|
243
|
-
if ev.mag is not None:
|
|
244
|
-
if minmagnitude is not None and ev.mag < minmagnitude:
|
|
245
|
-
continue
|
|
246
|
-
if maxmagnitude is not None and ev.mag > maxmagnitude:
|
|
247
|
-
continue
|
|
197
|
+
if _out_of_range(ev.orig_time, starttime, endtime):
|
|
198
|
+
continue
|
|
199
|
+
if _out_of_range(ev.lat, minlatitude, maxlatitude):
|
|
200
|
+
continue
|
|
201
|
+
if _out_of_range(ev.lon, minlongitude, maxlongitude):
|
|
202
|
+
continue
|
|
203
|
+
if _out_of_range(ev.depth, mindepth, maxdepth):
|
|
204
|
+
continue
|
|
205
|
+
if _out_of_range(ev.mag, minmagnitude, maxmagnitude):
|
|
206
|
+
continue
|
|
248
207
|
outcat.append(ev)
|
|
249
208
|
return outcat
|
|
250
209
|
|
|
251
210
|
|
|
211
|
+
def _out_of_range(value, minimum, maximum):
|
|
212
|
+
"""Return True when a value falls outside the requested bounds."""
|
|
213
|
+
if value is None:
|
|
214
|
+
return False
|
|
215
|
+
return (
|
|
216
|
+
(minimum is not None and value < minimum)
|
|
217
|
+
or (maximum is not None and value > maximum)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
252
221
|
def read_stored_catalog():
|
|
253
222
|
"""
|
|
254
|
-
Read the catalog stored in the output
|
|
223
|
+
Read the catalog stored in the output database.
|
|
255
224
|
|
|
256
225
|
:return: Catalog object.
|
|
257
226
|
:rtype: RequakeCatalog
|
|
258
227
|
|
|
259
|
-
:raises ValueError: if
|
|
260
|
-
:raises FileNotFoundError: if
|
|
228
|
+
:raises ValueError: if the stored catalog is empty
|
|
229
|
+
:raises FileNotFoundError: if the output database is not found
|
|
261
230
|
"""
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
except ValueError as err:
|
|
270
|
-
raise ValueError(
|
|
271
|
-
f'Error reading catalog file {config.scan_catalog_file}: {err}'
|
|
272
|
-
) from err
|
|
273
|
-
except (FileNotFoundError, NotADirectoryError) as err:
|
|
274
|
-
raise FileNotFoundError(
|
|
275
|
-
f'Catalog file {config.scan_catalog_file} not found'
|
|
276
|
-
) from err
|
|
231
|
+
from ..database.catalog import read_catalog as read_catalog_from_db
|
|
232
|
+
|
|
233
|
+
cat = read_catalog_from_db(config)
|
|
234
|
+
cat.sort()
|
|
235
|
+
if not cat:
|
|
236
|
+
raise ValueError('Empty catalog')
|
|
237
|
+
return cat
|
|
277
238
|
|
|
278
239
|
|
|
279
240
|
def fix_non_locatable_events(catalog):
|
|
@@ -308,9 +269,9 @@ def _base26(val):
|
|
|
308
269
|
:rtype: str
|
|
309
270
|
"""
|
|
310
271
|
chars = [
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
272
|
+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
|
|
273
|
+
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
|
274
|
+
'u', 'v', 'w', 'x', 'y', 'z'
|
|
314
275
|
]
|
|
315
276
|
base = len(chars)
|
|
316
277
|
ret = ''
|
|
@@ -337,7 +298,7 @@ def generate_evid(orig_time):
|
|
|
337
298
|
orig_year = UTCDateTime(year=year, month=1, day=1)
|
|
338
299
|
val = int(orig_time - orig_year)
|
|
339
300
|
# normalize val between 0 (aaaaaa) and 26**6-1 (zzzzzz)
|
|
340
|
-
maxval = 366*24*3600 # max number of seconds in leap year
|
|
341
|
-
normval = int(val/maxval * (26**6-1))
|
|
301
|
+
maxval = 366 * 24 * 3600 # max number of seconds in leap year
|
|
302
|
+
normval = int(val / maxval * (26 ** 6 - 1))
|
|
342
303
|
ret = _base26(normval)
|
|
343
304
|
return f'{prefix}{year}{ret}'
|
|
@@ -16,9 +16,7 @@ logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def print_catalog():
|
|
19
|
-
"""
|
|
20
|
-
Print the event catalog to screen.
|
|
21
|
-
"""
|
|
19
|
+
"""Print the event catalog to screen."""
|
|
22
20
|
try:
|
|
23
21
|
catalog = read_stored_catalog()
|
|
24
22
|
except (ValueError, FileNotFoundError) as msg:
|
|
@@ -13,7 +13,9 @@ import os
|
|
|
13
13
|
import logging
|
|
14
14
|
import contextlib
|
|
15
15
|
from ..config import config, rq_exit
|
|
16
|
-
from .
|
|
16
|
+
from ..database.db import get_db_path
|
|
17
|
+
from ..database.catalog import write_catalog as write_stored_catalog
|
|
18
|
+
from .catalog import RequakeEvent, RequakeCatalog
|
|
17
19
|
from .read_catalog_from_fdsnws import read_catalog_from_fdsnws
|
|
18
20
|
from .read_catalog_from_quakeml import read_catalog_from_quakeml
|
|
19
21
|
from .read_catalog_from_csv import read_catalog_from_csv
|
|
@@ -45,7 +47,16 @@ def _read_catalog_from_file():
|
|
|
45
47
|
# try to read the catalog as a FDSN text file
|
|
46
48
|
with contextlib.suppress(ValueError):
|
|
47
49
|
cat = RequakeCatalog()
|
|
48
|
-
|
|
50
|
+
with open(catalog_file, 'r', encoding='utf8') as fp:
|
|
51
|
+
for line in fp:
|
|
52
|
+
if not line:
|
|
53
|
+
continue
|
|
54
|
+
if line[0] == '#':
|
|
55
|
+
continue
|
|
56
|
+
ev = RequakeEvent()
|
|
57
|
+
ev.from_fdsn_text(line)
|
|
58
|
+
cat.append(ev)
|
|
59
|
+
cat.deduplicate()
|
|
49
60
|
return cat
|
|
50
61
|
# try to read the catalog as a CSV file
|
|
51
62
|
# raises ValueError in case of failure
|
|
@@ -54,8 +65,7 @@ def _read_catalog_from_file():
|
|
|
54
65
|
|
|
55
66
|
def _filter_catalog(catalog):
|
|
56
67
|
"""
|
|
57
|
-
Filter an event catalog
|
|
58
|
-
in the configuration.
|
|
68
|
+
Filter an event catalog using the configured selection criteria.
|
|
59
69
|
|
|
60
70
|
:param catalog: Event catalog.
|
|
61
71
|
:type catalog: requake.catalog.RequakeCatalog
|
|
@@ -99,14 +109,17 @@ def read_catalog():
|
|
|
99
109
|
:rtype: requake.catalog.RequakeCatalog
|
|
100
110
|
"""
|
|
101
111
|
catalog = RequakeCatalog()
|
|
102
|
-
output_cat_file = config.scan_catalog_file
|
|
103
112
|
nevs_read = 0
|
|
104
113
|
if config.args.append:
|
|
105
114
|
with contextlib.suppress(FileNotFoundError):
|
|
106
|
-
catalog
|
|
107
|
-
|
|
115
|
+
from .catalog import read_stored_catalog
|
|
116
|
+
|
|
117
|
+
catalog = read_stored_catalog()
|
|
108
118
|
nevs_read = len(catalog)
|
|
109
|
-
logger.info(
|
|
119
|
+
logger.info(
|
|
120
|
+
f'{nevs_read} events read from db file '
|
|
121
|
+
f'{get_db_path(config)}'
|
|
122
|
+
)
|
|
110
123
|
logger.info('Reading catalog...')
|
|
111
124
|
input_cat_file = config.args.catalog_file
|
|
112
125
|
if input_cat_file is not None:
|
|
@@ -136,7 +149,10 @@ def read_catalog():
|
|
|
136
149
|
logger.info(f'{nevs_dedup} duplicate events removed')
|
|
137
150
|
# Sort catalog in increasing time order
|
|
138
151
|
catalog.sort()
|
|
139
|
-
# Write catalog to output
|
|
140
|
-
catalog
|
|
152
|
+
# Write catalog to the output database
|
|
153
|
+
write_stored_catalog(catalog, config)
|
|
141
154
|
nevs_written = len(catalog) - nevs_read
|
|
142
|
-
logger.info(
|
|
155
|
+
logger.info(
|
|
156
|
+
f'{nevs_written} events written to db file '
|
|
157
|
+
f'{get_db_path(config)}'
|
|
158
|
+
)
|
|
@@ -19,8 +19,7 @@ logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
|
|
|
19
19
|
|
|
20
20
|
def _guess_field_names(input_fields):
|
|
21
21
|
"""
|
|
22
|
-
Guess
|
|
23
|
-
depth, magnitude and magnitude type.
|
|
22
|
+
Guess field names for origin time, location, depth, and magnitude.
|
|
24
23
|
|
|
25
24
|
:param input_fields: list of field names
|
|
26
25
|
:type input_fields: list of str
|
|
@@ -80,9 +79,8 @@ def _guess_field_names(input_fields):
|
|
|
80
79
|
# make a list of duplicated fields, which have been matched more than once
|
|
81
80
|
duplicated_fields = [
|
|
82
81
|
(key, value, output_field_scores[key])
|
|
83
|
-
for key, value in output_fields.items()
|
|
84
|
-
value is not None and
|
|
85
|
-
list(output_fields.values()).count(value) > 1
|
|
82
|
+
for key, value in output_fields.items()
|
|
83
|
+
if value is not None and list(output_fields.values()).count(value) > 1
|
|
86
84
|
]
|
|
87
85
|
# if there are duplicated fields, keep the one with the highest score
|
|
88
86
|
for _key, value, score in duplicated_fields:
|
|
@@ -167,7 +165,7 @@ def read_catalog_from_csv(filename):
|
|
|
167
165
|
nrows -= 1 # first row is the header
|
|
168
166
|
cat = RequakeCatalog()
|
|
169
167
|
for n, row in enumerate(reader):
|
|
170
|
-
print(f'reading row {n+1}/{nrows}\r', end='')
|
|
168
|
+
print(f'reading row {n + 1}/{nrows}\r', end='')
|
|
171
169
|
if fields['orig_time'] is None:
|
|
172
170
|
# try build a date-time field from year, month, day, hour,
|
|
173
171
|
# minute and seconds fields
|
|
@@ -195,7 +193,7 @@ def read_catalog_from_csv(filename):
|
|
|
195
193
|
)
|
|
196
194
|
except ValueError:
|
|
197
195
|
logger.error(
|
|
198
|
-
f'Unable to parse origin time at row {n+2}: '
|
|
196
|
+
f'Unable to parse origin time at row {n + 2}: '
|
|
199
197
|
f'"{orig_time_str}"')
|
|
200
198
|
continue
|
|
201
199
|
row[None] = None
|
|
@@ -14,4 +14,8 @@ from .generic_printer import generic_printer # noqa
|
|
|
14
14
|
# The config object is created in config.py and needs to be populated
|
|
15
15
|
# when using requake from command line, this is done by the configure()
|
|
16
16
|
# function in rq_setup.py
|
|
17
|
-
from .config import
|
|
17
|
+
from .config import ( # noqa
|
|
18
|
+
config,
|
|
19
|
+
to_picklable_config_dict,
|
|
20
|
+
from_picklable_config_dict,
|
|
21
|
+
)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# -*- coding: utf8 -*-
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
"""
|
|
4
|
+
Config class for Requake.
|
|
5
|
+
|
|
6
|
+
:copyright:
|
|
7
|
+
2021-2026 Claudio Satriano <satriano@ipgp.fr>
|
|
8
|
+
:license:
|
|
9
|
+
GNU General Public License v3.0 or later
|
|
10
|
+
(https://www.gnu.org/licenses/gpl-3.0-standalone.html)
|
|
11
|
+
"""
|
|
12
|
+
from argparse import Namespace
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Runtime objects that are useful in-process but should not be sent to
|
|
16
|
+
# worker processes via ProcessPoolExecutor.
|
|
17
|
+
RUNTIME_ONLY_KEYS = frozenset({
|
|
18
|
+
'station_client',
|
|
19
|
+
'dataselect_client',
|
|
20
|
+
'catalog_fdsn_event_clients',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Config(dict):
|
|
25
|
+
"""A class to access config values with dot notation."""
|
|
26
|
+
|
|
27
|
+
def __setitem__(self, key, value):
|
|
28
|
+
"""Make Config keys accessible as attributes."""
|
|
29
|
+
super().__setattr__(key, value)
|
|
30
|
+
super().__setitem__(key, value)
|
|
31
|
+
|
|
32
|
+
def __getattr__(self, key):
|
|
33
|
+
"""Make Config keys accessible as attributes."""
|
|
34
|
+
try:
|
|
35
|
+
return self.__getitem__(key)
|
|
36
|
+
except KeyError as err:
|
|
37
|
+
raise AttributeError(err) from err
|
|
38
|
+
|
|
39
|
+
__setattr__ = __setitem__
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def to_picklable_config_dict(
|
|
43
|
+
cfg,
|
|
44
|
+
drop_inventory=False,
|
|
45
|
+
extra_excluded_keys=None,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Build a pickle-safe config snapshot for multiprocessing.
|
|
49
|
+
|
|
50
|
+
This helper strips known runtime-only objects (for example ObsPy clients
|
|
51
|
+
containing SSL contexts) and converts ``args`` from ``argparse.Namespace``
|
|
52
|
+
to a plain ``dict``.
|
|
53
|
+
|
|
54
|
+
Typical usage with ``concurrent.futures.ProcessPoolExecutor``::
|
|
55
|
+
|
|
56
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
57
|
+
from requake.config import (
|
|
58
|
+
config,
|
|
59
|
+
to_picklable_config_dict,
|
|
60
|
+
from_picklable_config_dict,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
cfg_dict = to_picklable_config_dict(config)
|
|
64
|
+
|
|
65
|
+
def worker(cfg_dict, event_id):
|
|
66
|
+
cfg = from_picklable_config_dict(cfg_dict)
|
|
67
|
+
# Rebuild only what is needed in each process.
|
|
68
|
+
# Re-create network clients here, not in the parent process.
|
|
69
|
+
return cfg['cc_min'], event_id
|
|
70
|
+
|
|
71
|
+
def _worker_call(args):
|
|
72
|
+
cfg, evid = args
|
|
73
|
+
return worker(cfg, evid)
|
|
74
|
+
|
|
75
|
+
with ProcessPoolExecutor() as pool:
|
|
76
|
+
worker_args = ((cfg_dict, evid) for evid in event_ids)
|
|
77
|
+
list(pool.map(_worker_call, worker_args))
|
|
78
|
+
|
|
79
|
+
:param cfg: Requake config-like mapping.
|
|
80
|
+
:type cfg: Mapping
|
|
81
|
+
:param drop_inventory: Exclude ``inventory`` from the snapshot.
|
|
82
|
+
:type drop_inventory: bool
|
|
83
|
+
:param extra_excluded_keys: Additional keys to remove from the snapshot.
|
|
84
|
+
:type extra_excluded_keys: iterable or None
|
|
85
|
+
:return: A pickle-safe shallow copy of the configuration.
|
|
86
|
+
:rtype: dict
|
|
87
|
+
"""
|
|
88
|
+
cfg_dict = dict(cfg)
|
|
89
|
+
excluded_keys = set(RUNTIME_ONLY_KEYS)
|
|
90
|
+
if drop_inventory:
|
|
91
|
+
excluded_keys.add('inventory')
|
|
92
|
+
if extra_excluded_keys is not None:
|
|
93
|
+
excluded_keys.update(extra_excluded_keys)
|
|
94
|
+
for key in excluded_keys:
|
|
95
|
+
cfg_dict.pop(key, None)
|
|
96
|
+
|
|
97
|
+
args = cfg_dict.get('args')
|
|
98
|
+
if isinstance(args, Namespace):
|
|
99
|
+
cfg_dict['args'] = vars(args).copy()
|
|
100
|
+
|
|
101
|
+
return cfg_dict
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def from_picklable_config_dict(cfg_dict):
|
|
105
|
+
"""
|
|
106
|
+
Rebuild a ``Config`` instance from a pickle-safe snapshot.
|
|
107
|
+
|
|
108
|
+
This helper is the counterpart of ``to_picklable_config_dict()`` and is
|
|
109
|
+
intended for worker processes receiving a serialized config payload.
|
|
110
|
+
|
|
111
|
+
If ``args`` is a dict, it is converted back to ``argparse.Namespace``.
|
|
112
|
+
|
|
113
|
+
:param cfg_dict: Pickle-safe config snapshot.
|
|
114
|
+
:type cfg_dict: Mapping
|
|
115
|
+
:return: Reconstructed config object.
|
|
116
|
+
:rtype: Config
|
|
117
|
+
"""
|
|
118
|
+
cfg = Config()
|
|
119
|
+
cfg.update(dict(cfg_dict))
|
|
120
|
+
args = cfg.get('args')
|
|
121
|
+
if isinstance(args, dict):
|
|
122
|
+
cfg['args'] = Namespace(**args)
|
|
123
|
+
return cfg
|
|
124
|
+
|
|
125
|
+
config = Config() # noqa
|
|
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__.rsplit('.', maxsplit=1)[-1])
|
|
|
21
21
|
|
|
22
22
|
def generic_printer(rows, headers_fmt, print_headers=True):
|
|
23
23
|
"""
|
|
24
|
-
|
|
24
|
+
Print rows using the configured output format.
|
|
25
25
|
|
|
26
26
|
:param rows: Rows to print.
|
|
27
27
|
:type rows: list of rows
|