Skip to content

Commit 55f3abd

Browse files
authored
feat: Added query_bsi (#201)
* feat: Added query_bsi, based on comcat query functions. Can be called from csep.__init__, wrapping the readers._query_bsi. This is based in csep.utils.comcat.search functions. mods: (i) Exception catchs for Hostname mismatch in SSL verification (the bsi does not go through https). (ii) in comcat and bsi, ids are set different. SummaryEvent now handles this with try/except. (iii) bsi returns datetimes as utc strings, instead of timestamp as comcat. * docs: added query_bsi to api reference and mentioned it in tutorial/catalog_filtering styl: modified query_comcat docstring * fix: added maxdepth to query_comcat and query_bsi wrappers at top and bottom level. default=1000. Maybe these functions should have the arguments to completely define an experiment catalog (perhaps just max_mag and its ready)
1 parent 920b366 commit 55f3abd

File tree

8 files changed

+268
-12
lines changed

8 files changed

+268
-12
lines changed

csep/__init__.py

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ def load_catalog(filename, type='csep-csv', format='native', loader=None, apply_
188188

189189
def query_comcat(start_time, end_time, min_magnitude=2.50,
190190
min_latitude=31.50, max_latitude=43.00,
191-
min_longitude=-125.40, max_longitude=-113.10, verbose=True,
191+
min_longitude=-125.40, max_longitude=-113.10,
192+
max_depth=1000,
193+
verbose=True,
192194
apply_filters=False, **kwargs):
193195
"""
194196
Access Comcat catalog through web service
@@ -201,19 +203,20 @@ def query_comcat(start_time, end_time, min_magnitude=2.50,
201203
max_latitude: max latitude of bounding box
202204
min_longitude: min latitude of bounding box
203205
max_longitude: max longitude of bounding box
204-
region: :class:`csep.core.regions.CartesianGrid2D
206+
max_depth: maximum depth of the bounding box
205207
verbose (bool): print catalog summary statistics
206208
207209
Returns:
208-
:class:`csep.core.catalogs.ComcatCatalog
210+
:class:`csep.core.catalogs.CSEPCatalog
209211
"""
210212

211213
# Timezone should be in UTC
212214
t0 = time.time()
213215
eventlist = readers._query_comcat(start_time=start_time, end_time=end_time,
214216
min_magnitude=min_magnitude,
215217
min_latitude=min_latitude, max_latitude=max_latitude,
216-
min_longitude=min_longitude, max_longitude=max_longitude)
218+
min_longitude=min_longitude, max_longitude=max_longitude,
219+
max_depth=max_depth)
217220
t1 = time.time()
218221
comcat = catalogs.CSEPCatalog(data=eventlist, date_accessed=utc_now_datetime(), **kwargs)
219222
print("Fetched ComCat catalog in {} seconds.\n".format(t1 - t0))
@@ -234,6 +237,59 @@ def query_comcat(start_time, end_time, min_magnitude=2.50,
234237

235238
return comcat
236239

240+
241+
def query_bsi(start_time, end_time, min_magnitude=2.50,
242+
min_latitude=32.0, max_latitude=50.0,
243+
min_longitude=2.0, max_longitude=21.0,
244+
max_depth=1000,
245+
verbose=True,
246+
apply_filters=False, **kwargs):
247+
"""
248+
Access BSI catalog through web service
249+
250+
Args:
251+
start_time: datetime object of start of catalog
252+
end_time: datetime object for end of catalog
253+
min_magnitude: minimum magnitude to query
254+
min_latitude: maximum magnitude to query
255+
max_latitude: max latitude of bounding box
256+
min_longitude: min latitude of bounding box
257+
max_longitude: max longitude of bounding box
258+
max_depth: maximum depth of the bounding box
259+
verbose (bool): print catalog summary statistics
260+
261+
Returns:
262+
:class:`csep.core.catalogs.CSEPCatalog
263+
"""
264+
265+
# Timezone should be in UTC
266+
t0 = time.time()
267+
eventlist = readers._query_bsi(start_time=start_time, end_time=end_time,
268+
min_magnitude=min_magnitude,
269+
min_latitude=min_latitude, max_latitude=max_latitude,
270+
min_longitude=min_longitude, max_longitude=max_longitude,
271+
max_depth=max_depth)
272+
t1 = time.time()
273+
bsi = catalogs.CSEPCatalog(data=eventlist, date_accessed=utc_now_datetime(), **kwargs)
274+
print("Fetched BSI catalog in {} seconds.\n".format(t1 - t0))
275+
276+
if apply_filters:
277+
try:
278+
bsi = bsi.filter().filter_spatial()
279+
except CSEPCatalogException:
280+
bsi = bsi.filter()
281+
282+
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))
287+
print("Min Magnitude: {}".format(bsi.min_magnitude))
288+
print(f"Found {bsi.event_count} events in the BSI catalog.")
289+
290+
return bsi
291+
292+
237293
def load_evaluation_result(fname):
238294
""" Load evaluation result stored as json file
239295

csep/utils/comcat.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# python imports
2-
from datetime import datetime, timedelta
2+
from datetime import datetime, timedelta, timezone
33
from urllib import request
4-
from urllib.error import HTTPError
4+
from urllib.error import HTTPError, URLError
55
from urllib.parse import urlparse, urlencode
6+
import ssl
67
import json
78
import time
89
from collections import OrderedDict
@@ -293,6 +294,39 @@ def _search(**newargs):
293294
except Exception as msg:
294295
raise Exception(
295296
'Error downloading data from url %s. "%s".' % (url, msg))
297+
298+
except ssl.SSLCertVerificationError as SSLe:
299+
# Fails to verify SSL certificate, when there is a hostname mismatch
300+
if SSLe.verify_code == 62:
301+
try:
302+
context = ssl._create_unverified_context()
303+
fh = request.urlopen(url, timeout=TIMEOUT, context=context)
304+
data = fh.read().decode('utf8')
305+
fh.close()
306+
jdict = json.loads(data)
307+
events = []
308+
for feature in jdict['features']:
309+
events.append(SummaryEvent(feature))
310+
except Exception as msg:
311+
raise Exception(
312+
'Error downloading data from url %s. "%s".' % (url, msg))
313+
314+
except URLError as URLe:
315+
# Fails to verify SSL certificate, when there is a hostname mismatch
316+
if isinstance(URLe.reason, ssl.SSLCertVerificationError) and URLe.reason.verify_code == 62:
317+
try:
318+
context = ssl._create_unverified_context()
319+
fh = request.urlopen(url, timeout=TIMEOUT, context=context)
320+
data = fh.read().decode('utf8')
321+
fh.close()
322+
jdict = json.loads(data)
323+
events = []
324+
for feature in jdict['features']:
325+
events.append(SummaryEvent(feature))
326+
except Exception as msg:
327+
raise Exception(
328+
'Error downloading data from url %s. "%s".' % (url, msg))
329+
296330
except Exception as msg:
297331
raise Exception(
298332
'Error downloading data from url %s. "%s".' % (url, msg))
@@ -358,7 +392,11 @@ def id(self):
358392
Returns:
359393
str: Authoritative origin ID.
360394
"""
361-
return self._jdict['id']
395+
## comcat has an id key in each feature, whereas bsi has eventId within the properties dict
396+
try:
397+
return self._jdict['id']
398+
except:
399+
return self._jdict['properties']['eventId']
362400

363401
@property
364402
def time(self):
@@ -367,6 +405,10 @@ def time(self):
367405
datetime: Authoritative origin time.
368406
"""
369407
time_in_msec = self._jdict['properties']['time']
408+
# Comcat gives the event time in a ms timestamp, whereas bsi in datetime isoformat
409+
if isinstance(time_in_msec, str):
410+
event_dtime = datetime.fromisoformat(time_in_msec).replace(tzinfo=timezone.utc)
411+
time_in_msec = event_dtime.timestamp() * 1000
370412
time_in_sec = time_in_msec // 1000
371413
msec = time_in_msec - (time_in_sec * 1000)
372414
dtime = datetime.utcfromtimestamp(time_in_sec)

csep/utils/readers.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -655,8 +655,9 @@ def jma_csv(fname):
655655
return events
656656

657657
def _query_comcat(start_time, end_time, min_magnitude=2.50,
658-
min_latitude=31.50, max_latitude=43.00,
659-
min_longitude=-125.40, max_longitude=-113.10, extra_comcat_params=None):
658+
min_latitude=31.50, max_latitude=43.00,
659+
min_longitude=-125.40, max_longitude=-113.10,
660+
max_depth=1000, extra_comcat_params=None):
660661
"""
661662
Return eventlist from ComCat web service.
662663
@@ -668,6 +669,7 @@ def _query_comcat(start_time, end_time, min_magnitude=2.50,
668669
max_latitude (float): maximum latitude of query
669670
min_longitude (float): minimum longitude of query
670671
max_longitude (float): maximum longitude of query
672+
max_depth (float): maximum depth of query
671673
extra_comcat_params (dict): additional parameters to pass to comcat search function
672674
673675
Returns:
@@ -679,10 +681,30 @@ def _query_comcat(start_time, end_time, min_magnitude=2.50,
679681
eventlist = search(minmagnitude=min_magnitude,
680682
minlatitude=min_latitude, maxlatitude=max_latitude,
681683
minlongitude=min_longitude, maxlongitude=max_longitude,
682-
starttime=start_time, endtime=end_time, **extra_comcat_params)
684+
starttime=start_time, endtime=end_time,
685+
maxdepth=max_depth, **extra_comcat_params)
683686

684687
return eventlist
685688

689+
def _query_bsi(start_time, end_time, min_magnitude=2.50,
690+
min_latitude=32.0, max_latitude=50.0,
691+
min_longitude=2.0, max_longitude=21.0,
692+
max_depth=1000, extra_bsi_params=None):
693+
"""
694+
Queries INGV Bulletino Sismico Italiano, revised version.
695+
:return: csep.core.Catalog object
696+
"""
697+
extra_bsi_params = extra_bsi_params or {}
698+
bsi_host = 'webservices.rm.ingv.it'
699+
extra_bsi_params.update({'host': bsi_host, 'limit': 15000, 'offset': 0})
700+
# get eventlist from Comcat
701+
eventlist = search(minmagnitude=min_magnitude,
702+
minlatitude=min_latitude, maxlatitude=max_latitude,
703+
minlongitude=min_longitude, maxlongitude=max_longitude,
704+
maxdepth=max_depth,
705+
starttime=start_time, endtime=end_time, **extra_bsi_params)
706+
707+
return eventlist
686708
def _parse_datetime_to_zmap(date, time):
687709
""" Helping function to return datetime in zmap format.
688710

docs/reference/api_reference.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Loading catalogs and forecasts
1616
load_stochastic_event_sets
1717
load_catalog
1818
query_comcat
19+
query_bsi
1920
load_gridded_forecast
2021
load_catalog_forecast
2122

examples/tutorials/catalog_filtering.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030
# Load catalog
3131
# ------------
3232
#
33-
# PyCSEP provides access to the ComCat web API using :func:`csep.query_comcat`. This function requires a
34-
# :class:`datetime.datetime` to specify the start and end dates.
33+
# PyCSEP provides access to the ComCat web API using :func:`csep.query_comcat` and to the Bollettino Sismico Italiano
34+
# API using :func:`csep.query_bsi`. These functions require a :class:`datetime.datetime` to specify the start and end
35+
# dates.
3536

3637
start_time = csep.utils.time_utils.strptime_to_utc_datetime('2019-01-01 00:00:00.0')
3738
end_time = csep.utils.time_utils.utc_now_datetime()

tests/artifacts/BSI/vcr_search.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Connection:
6+
- close
7+
Host:
8+
- webservices.rm.ingv.it
9+
User-Agent:
10+
- Python-urllib/3.10
11+
method: GET
12+
uri: https://webservices.rm.ingv.it/fdsnws/event/1/query?format=geojson&starttime=2009-04-06T00%3A00%3A00&endtime=2009-04-07T00%3A00%3A00&limit=15000&maxdepth=1000&maxmagnitude=10.0&mindepth=-100&minmagnitude=5.5&offset=0&orderby=time-asc&eventtype=earthquake
13+
response:
14+
body:
15+
string: '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"eventId":1895389,"originId":755599,"time":"2009-04-06T01:32:40.400000","author":"BULLETIN-SISPICK","magType":"Mw","mag":6.1,"magAuthor":"--","type":"earthquake","place":"2
16+
km SW L''Aquila (AQ)","version":1000,"geojson_creationTime":"2022-09-21T12:18:03"},"geometry":{"type":"Point","coordinates":[13.38,42.342,8.3]}}]}'
17+
headers:
18+
Access-Control-Allow-Origin:
19+
- '*'
20+
Cache-Control:
21+
- public, max-age=60
22+
Connection:
23+
- close
24+
Content-Type:
25+
- application/json
26+
Date:
27+
- Wed, 21 Sep 2022 12:18:03 GMT
28+
Server:
29+
- nginx
30+
Transfer-Encoding:
31+
- chunked
32+
Vary:
33+
- Accept-Encoding
34+
- Accept-Encoding
35+
X-Cache-Status:
36+
- EXPIRED
37+
X-RateLimit-Limit:
38+
- '10'
39+
X-RateLimit-Reset:
40+
- '1'
41+
X-UA-Compatible:
42+
- IE=Edge
43+
status:
44+
code: 200
45+
message: OK
46+
version: 1

tests/artifacts/BSI/vcr_summary.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Connection:
6+
- close
7+
Host:
8+
- webservices.rm.ingv.it
9+
User-Agent:
10+
- Python-urllib/3.10
11+
method: GET
12+
uri: https://webservices.rm.ingv.it/fdsnws/event/1/query?format=geojson&starttime=2009-04-06T00%3A00%3A00&endtime=2009-04-07T00%3A00%3A00&limit=15000&maxdepth=1000&maxmagnitude=10.0&mindepth=-100&minmagnitude=5.5&offset=0&orderby=time-asc&eventtype=earthquake
13+
response:
14+
body:
15+
string: '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"eventId":1895389,"originId":755599,"time":"2009-04-06T01:32:40.400000","author":"BULLETIN-SISPICK","magType":"Mw","mag":6.1,"magAuthor":"--","type":"earthquake","place":"2
16+
km SW L''Aquila (AQ)","version":1000,"geojson_creationTime":"2022-09-21T12:14:55"},"geometry":{"type":"Point","coordinates":[13.38,42.342,8.3]}}]}'
17+
headers:
18+
Access-Control-Allow-Origin:
19+
- '*'
20+
Cache-Control:
21+
- public, max-age=60
22+
Connection:
23+
- close
24+
Content-Type:
25+
- application/json
26+
Date:
27+
- Wed, 21 Sep 2022 12:14:55 GMT
28+
Server:
29+
- nginx
30+
Transfer-Encoding:
31+
- chunked
32+
Vary:
33+
- Accept-Encoding
34+
- Accept-Encoding
35+
X-Cache-Status:
36+
- MISS
37+
X-RateLimit-Limit:
38+
- '10'
39+
X-RateLimit-Reset:
40+
- '1'
41+
X-UA-Compatible:
42+
- IE=Edge
43+
status:
44+
code: 200
45+
message: OK
46+
version: 1

tests/test_bsi.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from datetime import datetime
2+
import os.path
3+
import vcr
4+
from csep.utils.comcat import search
5+
6+
HOST = 'webservices.rm.ingv.it'
7+
8+
9+
def get_datadir():
10+
root_dir = os.path.dirname(os.path.abspath(__file__))
11+
data_dir = os.path.join(root_dir, 'artifacts', 'BSI')
12+
return data_dir
13+
14+
15+
def test_search():
16+
datadir = get_datadir()
17+
tape_file = os.path.join(datadir, 'vcr_search.yaml')
18+
with vcr.use_cassette(tape_file):
19+
# L'Aquila
20+
eventlist = search(starttime=datetime(2009, 4, 6, 0, 0, 0),
21+
endtime=datetime(2009, 4, 7, 0, 0, 0),
22+
minmagnitude=5.5, host=HOST, limit=15000, offset=0)
23+
event = eventlist[0]
24+
assert event.id == 1895389
25+
26+
27+
def test_summary():
28+
datadir = get_datadir()
29+
tape_file = os.path.join(datadir, 'vcr_summary.yaml')
30+
with vcr.use_cassette(tape_file):
31+
eventlist = search(starttime=datetime(2009, 4, 6, 0, 0, 0),
32+
endtime=datetime(2009, 4, 7, 0, 0, 0),
33+
minmagnitude=5.5, host=HOST, limit=15000, offset=0)
34+
event = eventlist[0]
35+
cmp = '1895389 2009-04-06 01:32:40.400000 (42.342,13.380) 8.3 km M6.1'
36+
assert str(event) == cmp
37+
assert event.id == 1895389
38+
assert event.time == datetime(2009, 4, 6, 1, 32, 40, 400000)
39+
assert event.latitude == 42.342
40+
assert event.longitude == 13.380
41+
assert event.depth == 8.3
42+
assert event.magnitude == 6.1

0 commit comments

Comments
 (0)