Skip to content

Commit a9910ee

Browse files
authored
gCMT Catalog accessor (#217)
* added gcmt accessor from ISC API. Follows the same logic than comcat and bsi readers. Created test for an event search and summary info from a request. * fix: reworked catalog accessor. Retrieving from IRIS ("https://service.iris.edu/fdsnws/event/1/") in text file, which is faster and safer. tests: fixed event list location
1 parent c19ce9f commit a9910ee

File tree

6 files changed

+448
-80
lines changed

6 files changed

+448
-80
lines changed

csep/__init__.py

+90-36
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@
6161
'__version__'
6262
]
6363

64-
def load_stochastic_event_sets(filename, type='csv', format='native', **kwargs):
64+
65+
def load_stochastic_event_sets(filename, type='csv', format='native',
66+
**kwargs):
6567
""" General function to load stochastic event sets
6668
6769
This function returns a generator to iterate through a collection of catalogs.
@@ -106,7 +108,8 @@ def load_stochastic_event_sets(filename, type='csv', format='native', **kwargs):
106108
raise ValueError('format must be either "native" or "csep!')
107109

108110

109-
def load_catalog(filename, type='csep-csv', format='native', loader=None, apply_filters=False, **kwargs):
111+
def load_catalog(filename, type='csep-csv', format='native', loader=None,
112+
apply_filters=False, **kwargs):
110113
""" General function to load single catalog
111114
112115
See corresponding class documentation for additional parameters.
@@ -122,9 +125,12 @@ def load_catalog(filename, type='csep-csv', format='native', loader=None, apply_
122125
Returns (:class:`~csep.core.catalogs.AbstractBaseCatalog`)
123126
"""
124127

125-
126-
if type not in ('ucerf3', 'csep-csv', 'zmap', 'jma-csv', 'ingv_horus', 'ingv_emrcmt', 'ndk') and loader is None:
127-
raise ValueError("type must be one of the following: ('ucerf3', 'csep-csv', 'zmap', 'jma-csv', 'ndk', 'ingv_horus', 'ingv_emrcmt').")
128+
if type not in (
129+
'ucerf3', 'csep-csv', 'zmap', 'jma-csv', 'ingv_horus',
130+
'ingv_emrcmt',
131+
'ndk') and loader is None:
132+
raise ValueError(
133+
"type must be one of the following: ('ucerf3', 'csep-csv', 'zmap', 'jma-csv', 'ndk', 'ingv_horus', 'ingv_emrcmt').")
128134

129135
# map to correct catalog class, at some point these could be abstracted into configuration file
130136
# this maps a human readable string to the correct catalog class and the correct loader function
@@ -167,7 +173,8 @@ def load_catalog(filename, type='csep-csv', format='native', loader=None, apply_
167173
if loader is None:
168174
loader = class_loader_mapping[type]['loader']
169175

170-
catalog = catalog_class.load_catalog(filename=filename, loader=loader, **kwargs)
176+
catalog = catalog_class.load_catalog(filename=filename, loader=loader,
177+
**kwargs)
171178

172179
# convert to csep format if needed
173180
if format == 'native':
@@ -213,12 +220,15 @@ def query_comcat(start_time, end_time, min_magnitude=2.50,
213220
# Timezone should be in UTC
214221
t0 = time.time()
215222
eventlist = readers._query_comcat(start_time=start_time, end_time=end_time,
216-
min_magnitude=min_magnitude,
217-
min_latitude=min_latitude, max_latitude=max_latitude,
218-
min_longitude=min_longitude, max_longitude=max_longitude,
219-
max_depth=max_depth)
223+
min_magnitude=min_magnitude,
224+
min_latitude=min_latitude,
225+
max_latitude=max_latitude,
226+
min_longitude=min_longitude,
227+
max_longitude=max_longitude,
228+
max_depth=max_depth)
220229
t1 = time.time()
221-
comcat = catalogs.CSEPCatalog(data=eventlist, date_accessed=utc_now_datetime(), **kwargs)
230+
comcat = catalogs.CSEPCatalog(data=eventlist,
231+
date_accessed=utc_now_datetime(), **kwargs)
222232
print("Fetched ComCat catalog in {} seconds.\n".format(t1 - t0))
223233

224234
if apply_filters:
@@ -229,9 +239,13 @@ def query_comcat(start_time, end_time, min_magnitude=2.50,
229239

230240
if verbose:
231241
print("Downloaded catalog from ComCat with following parameters")
232-
print("Start Date: {}\nEnd Date: {}".format(str(comcat.start_time), str(comcat.end_time)))
233-
print("Min Latitude: {} and Max Latitude: {}".format(comcat.min_latitude, comcat.max_latitude))
234-
print("Min Longitude: {} and Max Longitude: {}".format(comcat.min_longitude, comcat.max_longitude))
242+
print("Start Date: {}\nEnd Date: {}".format(str(comcat.start_time),
243+
str(comcat.end_time)))
244+
print(
245+
"Min Latitude: {} and Max Latitude: {}".format(comcat.min_latitude,
246+
comcat.max_latitude))
247+
print("Min Longitude: {} and Max Longitude: {}".format(
248+
comcat.min_longitude, comcat.max_longitude))
235249
print("Min Magnitude: {}".format(comcat.min_magnitude))
236250
print(f"Found {comcat.event_count} events in the ComCat catalog.")
237251

@@ -266,11 +280,14 @@ def query_bsi(start_time, end_time, min_magnitude=2.50,
266280
t0 = time.time()
267281
eventlist = readers._query_bsi(start_time=start_time, end_time=end_time,
268282
min_magnitude=min_magnitude,
269-
min_latitude=min_latitude, max_latitude=max_latitude,
270-
min_longitude=min_longitude, max_longitude=max_longitude,
283+
min_latitude=min_latitude,
284+
max_latitude=max_latitude,
285+
min_longitude=min_longitude,
286+
max_longitude=max_longitude,
271287
max_depth=max_depth)
272288
t1 = time.time()
273-
bsi = catalogs.CSEPCatalog(data=eventlist, date_accessed=utc_now_datetime(), **kwargs)
289+
bsi = catalogs.CSEPCatalog(data=eventlist,
290+
date_accessed=utc_now_datetime(), **kwargs)
274291
print("Fetched BSI catalog in {} seconds.\n".format(t1 - t0))
275292

276293
if apply_filters:
@@ -280,19 +297,25 @@ def query_bsi(start_time, end_time, min_magnitude=2.50,
280297
bsi = bsi.filter()
281298

282299
if verbose:
283-
print("Downloaded catalog from Bollettino Sismico Italiano (BSI) with following parameters")
284-
print("Start Date: {}\nEnd Date: {}".format(str(bsi.start_time), str(bsi.end_time)))
285-
print("Min Latitude: {} and Max Latitude: {}".format(bsi.min_latitude, bsi.max_latitude))
286-
print("Min Longitude: {} and Max Longitude: {}".format(bsi.min_longitude, bsi.max_longitude))
300+
print(
301+
"Downloaded catalog from Bollettino Sismico Italiano (BSI) with following parameters")
302+
print("Start Date: {}\nEnd Date: {}".format(str(bsi.start_time),
303+
str(bsi.end_time)))
304+
print("Min Latitude: {} and Max Latitude: {}".format(bsi.min_latitude,
305+
bsi.max_latitude))
306+
print(
307+
"Min Longitude: {} and Max Longitude: {}".format(bsi.min_longitude,
308+
bsi.max_longitude))
287309
print("Min Magnitude: {}".format(bsi.min_magnitude))
288310
print(f"Found {bsi.event_count} events in the BSI catalog.")
289311

290312
return bsi
291313

314+
292315
def query_gns(start_time, end_time, min_magnitude=2.950,
293-
min_latitude=-47, max_latitude=-34,
294-
min_longitude=164, max_longitude=180,
295-
max_depth=45.5,
316+
min_latitude=-47, max_latitude=-34,
317+
min_longitude=164, max_longitude=180,
318+
max_depth=45.5,
296319
verbose=True,
297320
apply_filters=False, **kwargs):
298321
"""
@@ -338,6 +361,29 @@ def query_gns(start_time, end_time, min_magnitude=2.950,
338361

339362
return gns
340363

364+
365+
def query_gcmt(start_time, end_time, min_magnitude=5.0,
366+
max_depth=None,
367+
catalog_id=None,
368+
min_latitude=None, max_latitude=None,
369+
min_longitude=None, max_longitude=None):
370+
371+
eventlist = readers._query_gcmt(start_time=start_time,
372+
end_time=end_time,
373+
min_magnitude=min_magnitude,
374+
min_latitude=min_latitude,
375+
max_latitude=max_latitude,
376+
min_longitude=min_longitude,
377+
max_longitude=max_longitude,
378+
max_depth=max_depth)
379+
380+
catalog = catalogs.CSEPCatalog(data=eventlist,
381+
name='gCMT',
382+
catalog_id=catalog_id,
383+
date_accessed=utc_now_datetime())
384+
return catalog
385+
386+
341387
def load_evaluation_result(fname):
342388
""" Load evaluation result stored as json file
343389
@@ -361,7 +407,8 @@ def load_evaluation_result(fname):
361407
evaluation_type = json_dict['type']
362408
except:
363409
evaluation_type = 'default'
364-
eval_result = evaluation_result_factory[evaluation_type].from_dict(json_dict)
410+
eval_result = evaluation_result_factory[evaluation_type].from_dict(
411+
json_dict)
365412
return eval_result
366413

367414

@@ -404,15 +451,18 @@ class with the region and magnitude members correctly assigned.
404451

405452
# sanity checks
406453
if not os.path.exists(fname):
407-
raise FileNotFoundError(f"Could not locate file {fname}. Unable to load forecast.")
454+
raise FileNotFoundError(
455+
f"Could not locate file {fname}. Unable to load forecast.")
408456
# sanity checks
409457
if loader is not None and not callable(loader):
410-
raise AttributeError("Loader must be callable. Unable to load forecast.")
458+
raise AttributeError(
459+
"Loader must be callable. Unable to load forecast.")
411460
extension = os.path.splitext(fname)[-1][1:]
412461
if extension not in forecast_loader_mapping.keys() and loader is None:
413-
raise AttributeError("File extension should be in ('dat','xml','h5','bin') if loader not provided.")
462+
raise AttributeError(
463+
"File extension should be in ('dat','xml','h5','bin') if loader not provided.")
414464

415-
if extension in ('xml','h5','bin'):
465+
if extension in ('xml', 'h5', 'bin'):
416466
raise NotImplementedError
417467

418468
# assign default loader
@@ -425,7 +475,8 @@ class with the region and magnitude members correctly assigned.
425475
return forecast
426476

427477

428-
def load_catalog_forecast(fname, catalog_loader=None, format='native', type='ascii', **kwargs):
478+
def load_catalog_forecast(fname, catalog_loader=None, format='native',
479+
type='ascii', **kwargs):
429480
""" General function to handle loading catalog forecasts.
430481
431482
Currently, just a simple wrapper, but can contain more complex logic in the future.
@@ -444,10 +495,12 @@ def load_catalog_forecast(fname, catalog_loader=None, format='native', type='asc
444495
"""
445496
# sanity checks
446497
if not os.path.exists(fname):
447-
raise FileNotFoundError(f"Could not locate file {fname}. Unable to load forecast.")
498+
raise FileNotFoundError(
499+
f"Could not locate file {fname}. Unable to load forecast.")
448500
# sanity checks
449501
if catalog_loader is not None and not callable(catalog_loader):
450-
raise AttributeError("Loader must be callable. Unable to load forecast.")
502+
raise AttributeError(
503+
"Loader must be callable. Unable to load forecast.")
451504
# factory methods for loading different types of catalogs
452505
catalog_loader_mapping = {
453506
'ascii': catalogs.CSEPCatalog.load_ascii_catalogs,
@@ -456,17 +509,18 @@ def load_catalog_forecast(fname, catalog_loader=None, format='native', type='asc
456509
if catalog_loader is None:
457510
catalog_loader = catalog_loader_mapping[type]
458511
# try and parse information from filename and send to forecast constructor
459-
if format == 'native' and type=='ascii':
512+
if format == 'native' and type == 'ascii':
460513
try:
461514
basename = str(os.path.basename(fname.rstrip('/')).split('.')[0])
462515
split_fname = basename.split('_')
463516
name = split_fname[0]
464-
start_time = strptime_to_utc_datetime(split_fname[1], format="%Y-%m-%dT%H-%M-%S-%f")
517+
start_time = strptime_to_utc_datetime(split_fname[1],
518+
format="%Y-%m-%dT%H-%M-%S-%f")
465519
# update kwargs
466520
_ = kwargs.setdefault('name', name)
467521
_ = kwargs.setdefault('start_time', start_time)
468522
except:
469523
pass
470524
# create observed_catalog forecast
471-
return CatalogForecast(filename=fname, loader=catalog_loader, catalog_format=format, catalog_type=type, **kwargs)
472-
525+
return CatalogForecast(filename=fname, loader=catalog_loader,
526+
catalog_format=format, catalog_type=type, **kwargs)

csep/utils/iris.py

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# python imports
2+
from datetime import datetime
3+
from urllib import request
4+
from urllib.parse import urlencode
5+
6+
# PyCSEP imports
7+
from csep.utils.time_utils import datetime_to_utc_epoch
8+
9+
HOST_CATALOG = "https://service.iris.edu/fdsnws/event/1/query?"
10+
TIMEOUT = 180
11+
12+
13+
def gcmt_search(format='text',
14+
starttime=None,
15+
endtime=None,
16+
updatedafter=None,
17+
minlatitude=None,
18+
maxlatitude=None,
19+
minlongitude=None,
20+
maxlongitude=None,
21+
latitude=None,
22+
longitude=None,
23+
maxradius=None,
24+
catalog='GCMT',
25+
contributor=None,
26+
maxdepth=1000,
27+
maxmagnitude=10.0,
28+
mindepth=-100,
29+
minmagnitude=0,
30+
offset=1,
31+
orderby='time-asc',
32+
host=None,
33+
verbose=False):
34+
"""Search the IRIS database for events matching input criteria.
35+
This search function is a wrapper around the ComCat Web API described here:
36+
https://service.iris.edu/fdsnws/event/1/
37+
38+
This function returns a list of SummaryEvent objects, described elsewhere in this package.
39+
Args:
40+
starttime (datetime):
41+
Python datetime - Limit to events on or after the specified start time.
42+
endtime (datetime):
43+
Python datetime - Limit to events on or before the specified end time.
44+
updatedafter (datetime):
45+
Python datetime - Limit to events updated after the specified time.
46+
minlatitude (float):
47+
Limit to events with a latitude larger than the specified minimum.
48+
maxlatitude (float):
49+
Limit to events with a latitude smaller than the specified maximum.
50+
minlongitude (float):
51+
Limit to events with a longitude larger than the specified minimum.
52+
maxlongitude (float):
53+
Limit to events with a longitude smaller than the specified maximum.
54+
latitude (float):
55+
Specify the latitude to be used for a radius search.
56+
longitude (float):
57+
Specify the longitude to be used for a radius search.
58+
maxradius (float):
59+
Limit to events within the specified maximum number of degrees
60+
from the geographic point defined by the latitude and longitude parameters.
61+
catalog (str):
62+
Limit to events from a specified catalog.
63+
contributor (str):
64+
Limit to events contributed by a specified contributor.
65+
maxdepth (float):
66+
Limit to events with depth less than the specified maximum.
67+
maxmagnitude (float):
68+
Limit to events with a magnitude smaller than the specified maximum.
69+
mindepth (float):
70+
Limit to events with depth more than the specified minimum.
71+
minmagnitude (float):
72+
Limit to events with a magnitude larger than the specified minimum.
73+
offset (int):
74+
Return results starting at the event count specified, starting at 1.
75+
orderby (str):
76+
Order the results. The allowed values are:
77+
- time order by origin descending time
78+
- time-asc order by origin ascending time
79+
- magnitude order by descending magnitude
80+
- magnitude-asc order by ascending magnitude
81+
host (str):
82+
Replace default ComCat host (earthquake.usgs.gov) with a custom host.
83+
Returns:
84+
list: List of SummaryEvent() objects.
85+
"""
86+
87+
# getting the inputargs must be the first line of the method!
88+
inputargs = locals().copy()
89+
newargs = {}
90+
91+
for key, value in inputargs.items():
92+
if value is True:
93+
newargs[key] = 'true'
94+
continue
95+
if value is False:
96+
newargs[key] = 'false'
97+
continue
98+
if value is None:
99+
continue
100+
newargs[key] = value
101+
102+
del newargs['verbose']
103+
104+
events = _search_gcmt(**newargs)
105+
106+
return events
107+
108+
109+
def _search_gcmt(**_newargs):
110+
"""
111+
Performs de-query at ISC API and returns event list and access date
112+
113+
"""
114+
paramstr = urlencode(_newargs)
115+
url = HOST_CATALOG + paramstr
116+
fh = request.urlopen(url, timeout=TIMEOUT)
117+
data = fh.read().decode('utf8').split('\n')
118+
fh.close()
119+
eventlist = []
120+
for line in data[1:]:
121+
line_ = line.split('|')
122+
if len(line_) != 1:
123+
id_ = line_[0]
124+
time_ = datetime.fromisoformat(line_[1])
125+
dt = datetime_to_utc_epoch(time_)
126+
lat = float(line_[2])
127+
lon = float(line_[3])
128+
depth = float(line_[4])
129+
mag = float(line_[10])
130+
eventlist.append((id_, dt, lat, lon, depth, mag))
131+
132+
return eventlist

0 commit comments

Comments
 (0)