Skip to content

Commit c19ce9f

Browse files
authored
Added NZ catalog reader (#213)
* Added NZ catalog reader * Added new geonet test * Added new yaml file
1 parent 3fe35ad commit c19ce9f

File tree

7 files changed

+404
-2
lines changed

7 files changed

+404
-2
lines changed

csep/__init__.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,54 @@ def query_bsi(start_time, end_time, min_magnitude=2.50,
289289

290290
return bsi
291291

292+
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,
296+
verbose=True,
297+
apply_filters=False, **kwargs):
298+
"""
299+
Access GNS Science catalog through web service
300+
301+
Args:
302+
start_time: datetime object of start of catalog
303+
end_time: datetime object for end of catalog
304+
min_magnitude: minimum magnitude to query
305+
min_latitude: maximum magnitude to query
306+
max_latitude: max latitude of bounding box
307+
min_longitude: min latitude of bounding box
308+
max_longitude: max longitude of bounding box
309+
max_depth: maximum depth of the bounding box
310+
verbose (bool): print catalog summary statistics
311+
312+
Returns:
313+
:class:`csep.core.catalogs.CSEPCatalog
314+
"""
315+
316+
# Timezone should be in UTC
317+
t0 = time.time()
318+
eventlist = readers._query_gns(start_time=start_time, end_time=end_time,
319+
min_magnitude=min_magnitude,
320+
min_latitude=min_latitude, max_latitude=max_latitude,
321+
min_longitude=min_longitude, max_longitude=max_longitude,
322+
max_depth=max_depth)
323+
t1 = time.time()
324+
gns = catalogs.CSEPCatalog(data=eventlist, date_accessed=utc_now_datetime())
325+
if apply_filters:
326+
try:
327+
gns = gns.filter().filter_spatial()
328+
except CSEPCatalogException:
329+
gns = gns.filter()
330+
331+
if verbose:
332+
print("Downloaded catalog from GNS Science NZ (GNS) with following parameters")
333+
print("Start Date: {}\nEnd Date: {}".format(str(gns.start_time), str(gns.end_time)))
334+
print("Min Latitude: {} and Max Latitude: {}".format(gns.min_latitude, gns.max_latitude))
335+
print("Min Longitude: {} and Max Longitude: {}".format(gns.min_longitude, gns.max_longitude))
336+
print("Min Magnitude: {}".format(gns.min_magnitude))
337+
print(f"Found {gns.event_count} events in the gns catalog.")
338+
339+
return gns
292340

293341
def load_evaluation_result(fname):
294342
""" Load evaluation result stored as json file

csep/core/catalogs.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
from csep.utils.stats import min_or_none, max_or_none
1616
from csep.utils.calc import discretize
1717
from csep.core.regions import CartesianGrid2D
18-
from csep.utils.comcat import SummaryEvent
18+
import csep.utils.comcat as comcat
19+
import csep.utils.geonet as geonet
1920
from csep.core.exceptions import CSEPSchedulerException, CSEPCatalogException, CSEPIOException
2021
from csep.utils.calc import bin1d_vec
2122
from csep.utils.constants import CSEP_MW_BINS
@@ -286,7 +287,7 @@ def _get_catalog_as_ndarray(self):
286287
if isinstance(self.catalog[0], (list, tuple)):
287288
for i, event in enumerate(self.catalog):
288289
catalog[i] = tuple(event)
289-
elif isinstance(self.catalog[0], SummaryEvent):
290+
elif isinstance(self.catalog[0], (comcat.SummaryEvent, geonet.SummaryEvent)):
290291
for i, event in enumerate(self.catalog):
291292
catalog[i] = (event.id, datetime_to_utc_epoch(event.time),
292293
event.latitude, event.longitude, event.depth, event.magnitude)

csep/utils/geonet.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
2+
# python imports
3+
from datetime import datetime, timedelta
4+
from urllib import request
5+
from urllib.parse import urlencode
6+
import json
7+
8+
9+
10+
class SummaryEvent(object):
11+
"""Wrapper around summary feature as returned by GeoNet GeoJSON search results.
12+
"""
13+
14+
def __init__(self, feature):
15+
"""Instantiate a SummaryEvent object with a feature.
16+
See summary documentation here:
17+
https://api.geonet.org.nz/#quakes
18+
Args:
19+
feature (dict): GeoJSON feature as described at above URL.
20+
"""
21+
self._jdict = feature.copy()
22+
@property
23+
def url(self):
24+
"""GeoNet URL.
25+
Returns:
26+
str: GeoNet URL
27+
"""
28+
url_template= "https://www.geonet.org.nz/earthquake/"
29+
return url_template + self._jdict['properties']['publicid']
30+
31+
@property
32+
def latitude(self):
33+
"""Authoritative origin latitude.
34+
Returns:
35+
float: Authoritative origin latitude.
36+
"""
37+
return self._jdict['geometry']['coordinates'][1]
38+
39+
@property
40+
def longitude(self):
41+
"""Authoritative origin longitude.
42+
Returns:
43+
float: Authoritative origin longitude.
44+
"""
45+
return self._jdict['geometry']['coordinates'][0]
46+
47+
@property
48+
def depth(self):
49+
"""Authoritative origin depth.
50+
Returns:
51+
float: Authoritative origin depth.
52+
"""
53+
return self._jdict['properties']['depth']
54+
55+
@property
56+
def id(self):
57+
"""Authoritative origin ID.
58+
Returns:
59+
str: Authoritative origin ID.
60+
"""
61+
## Geonet has eventId or publicid within the properties dict
62+
try:
63+
return self._jdict['properties']['publicid']
64+
except:
65+
return self._jdict['properties']['eventId']
66+
67+
@property
68+
def time(self):
69+
"""Authoritative origin time.
70+
Returns:
71+
datetime: Authoritative origin time.
72+
"""
73+
from obspy import UTCDateTime
74+
time_in_msec = self._jdict['properties']['origintime']
75+
# Convert the times
76+
if isinstance(time_in_msec, str):
77+
event_dtime = UTCDateTime(time_in_msec)
78+
time_in_msec = event_dtime.timestamp * 1000
79+
time_in_sec = time_in_msec // 1000
80+
msec = time_in_msec - (time_in_sec * 1000)
81+
dtime = datetime.utcfromtimestamp(time_in_sec)
82+
dt = timedelta(milliseconds=msec)
83+
dtime = dtime + dt
84+
return dtime
85+
86+
@property
87+
def magnitude(self):
88+
"""Authoritative origin magnitude.
89+
Returns:
90+
float: Authoritative origin magnitude.
91+
"""
92+
return self._jdict['properties']['magnitude']
93+
94+
def __repr__(self):
95+
tpl = (self.id, str(self.time), self.latitude,
96+
self.longitude, self.depth, self.magnitude)
97+
return '%s %s (%.3f,%.3f) %.1f km M%.1f' % tpl
98+
99+
100+
def gns_search(
101+
starttime=None,
102+
endtime=None,
103+
minlatitude=-47,
104+
maxlatitude=-34,
105+
minlongitude=164,
106+
maxlongitude=180,
107+
minmagnitude=2.95,
108+
maxmagnitude=None,
109+
maxdepth=45.5,
110+
mindepth=None):
111+
112+
"""Search the Geonet database for events matching input criteria.
113+
This search function is a wrapper around the Geonet Web API described here:
114+
https://quakesearch.geonet.org.nz/
115+
116+
Note:
117+
Geonet has limited search parameters compered to ComCat search parameters,
118+
hence the need for a new function
119+
Args:
120+
starttime (datetime):
121+
Python datetime - Limit to events on or after the specified start time.
122+
endtime (datetime):
123+
Python datetime - Limit to events on or before the specified end time.
124+
minlatitude (float):
125+
Limit to events with a latitude larger than the specified minimum.
126+
maxlatitude (float):
127+
Limit to events with a latitude smaller than the specified maximum.
128+
minlongitude (float):
129+
Limit to events with a longitude larger than the specified minimum.
130+
maxlongitude (float):
131+
Limit to events with a longitude smaller than the specified maximum.
132+
maxdepth (float):
133+
Limit to events with depth less than the specified maximum.
134+
maxmagnitude (float):
135+
Limit to events with a magnitude smaller than the specified maximum.
136+
mindepth (float):
137+
Limit to events with depth more than the specified minimum.
138+
minmagnitude (float):
139+
Limit to events with a magnitude larger than the specified minimum.
140+
Returns:
141+
list: List of dictionary with event info.
142+
"""
143+
# getting the inputargs must be the first line of the method!
144+
145+
TIMEFMT = '%Y-%m-%dT%H:%M:%S'
146+
TIMEOUT = 120 # how long do we wait for a url to return?
147+
try:
148+
newargs = {}
149+
newargs["bbox"] = f'{minlongitude},{minlatitude},{maxlongitude},{maxlatitude}'
150+
newargs["minmag"] = f'{minmagnitude}'
151+
newargs["maxdepth"] = f'{maxdepth}'
152+
newargs["startdate"] = starttime.strftime(TIMEFMT)
153+
newargs["enddate"] = endtime.strftime(TIMEFMT)
154+
if maxmagnitude is not None:
155+
newargs["maxmag"] = f'{maxmagnitude}'
156+
if mindepth is not None:
157+
newargs["mindepth"] = f'{mindepth}'
158+
159+
160+
paramstr = urlencode(newargs)
161+
template = "https://quakesearch.geonet.org.nz/geojson?"
162+
url = template + '&' + paramstr
163+
# print(url)
164+
try:
165+
fh = request.urlopen(url, timeout=TIMEOUT)
166+
data = fh.read().decode('utf8')
167+
fh.close()
168+
jdict = json.loads(data)
169+
events = []
170+
for feature in jdict['features']:
171+
events.append(SummaryEvent(feature))
172+
except Exception as msg:
173+
raise Exception(
174+
'Error downloading data from url %s. "%s".' % (url, msg))
175+
176+
return events
177+
except ValueError as e:
178+
if len(e.args) > 0 and 'Invalid isoformat string' in e.args[0]:
179+
print("Check the input date format. It should follow YYYY-MM-DD \
180+
and is should not be empty")

csep/utils/readers.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# PyCSEP imports
1414
from csep.utils.time_utils import strptime_to_utc_datetime, strptime_to_utc_epoch, datetime_to_utc_epoch
1515
from csep.utils.comcat import search
16+
from csep.utils.geonet import gns_search
1617
from csep.core.regions import QuadtreeGrid2D
1718
from csep.core.exceptions import CSEPIOException
1819

@@ -705,6 +706,30 @@ def _query_bsi(start_time, end_time, min_magnitude=2.50,
705706
starttime=start_time, endtime=end_time, **extra_bsi_params)
706707

707708
return eventlist
709+
710+
# Adding GNS catalog reader
711+
def _query_gns(start_time, end_time, min_magnitude=2.950,
712+
min_latitude=-47, max_latitude=-34,
713+
min_longitude=164, max_longitude=180,
714+
max_depth=45.5, extra_gns_params=None):
715+
"""
716+
Queries GNS catalog.
717+
:return: csep.core.Catalog object
718+
"""
719+
extra_gns_params = extra_gns_params or {}
720+
geonet_host = 'service.geonet.org.nz'
721+
extra_gns_params.update({'host': geonet_host, 'limit': 15000, 'offset': 0})
722+
# get eventlist from Comcat
723+
eventlist = gns_search(minmagnitude=min_magnitude,
724+
minlatitude=min_latitude,
725+
maxlatitude=max_latitude,
726+
minlongitude=min_longitude,
727+
maxlongitude=max_longitude,
728+
maxdepth=max_depth,
729+
starttime=start_time,
730+
endtime=end_time)
731+
return eventlist
732+
708733
def _parse_datetime_to_zmap(date, time):
709734
""" Helping function to return datetime in zmap format.
710735
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Connection:
6+
- close
7+
Host:
8+
- quakesearch.geonet.org.nz
9+
User-Agent:
10+
- Python-urllib/3.7
11+
method: GET
12+
uri: https://quakesearch.geonet.org.nz/geojson?&bbox=164%2C-47%2C180%2C-34&minmag=4&maxdepth=45.5&startdate=2023-02-01T00%3A00%3A00&enddate=2023-02-10T00%3A00%3A00
13+
response:
14+
body:
15+
string: '{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[175.7054138,-37.58729935]},"properties":{"publicid":"2023p087955","eventtype":"earthquake","origintime":"2023-02-02T13:02:43.493Z","modificationtime":"2023-02-17T00:21:34.988Z","depth":5.955107212,"magnitude":4.795154627,"magnitudetype":"MLv","evaluationmethod":"LOCSAT","evaluationstatus":"confirmed","evaluationmode":"manual","earthmodel":"iasp91","usedphasecount":35,"usedstationcount":25,"minimumdistance":0.04634241387,"azimuthalgap":62.78924561,"magnitudeuncertainty":0.2653529139,"originerror":0.3295216371,"magnitudestationcount":110}}]}'
16+
headers:
17+
Accept-Ranges:
18+
- bytes
19+
Access-Control-Allow-Methods:
20+
- GET
21+
Access-Control-Allow-Origin:
22+
- '*'
23+
Age:
24+
- '0'
25+
Connection:
26+
- close
27+
Content-Length:
28+
- '645'
29+
Content-Security-Policy:
30+
- 'base-uri ''none''; frame-ancestors ''self''; object-src ''none''; default-src
31+
''none''; script-src ''self''; connect-src ''self'' https://*.geonet.org.nz
32+
https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com;
33+
frame-src ''self'' https://www.youtube.com https://www.google.com; form-action
34+
''self'' https://*.geonet.org.nz; img-src ''self'' *.geonet.org.nz data: https://*.google-analytics.com
35+
https://*.googletagmanager.com; font-src ''self'' https://fonts.gstatic.com;
36+
style-src ''self'';'
37+
Content-Type:
38+
- application/vnd.geo+json
39+
Date:
40+
- Tue, 28 Mar 2023 16:20:00 GMT
41+
Referrer-Policy:
42+
- no-referrer
43+
Strict-Transport-Security:
44+
- max-age=63072000
45+
Vary:
46+
- Accept-Encoding
47+
X-Content-Type-Options:
48+
- nosniff
49+
X-Frame-Options:
50+
- DENY
51+
X-Xss-Protection:
52+
- 1; mode=block
53+
status:
54+
code: 200
55+
message: OK
56+
version: 1

0 commit comments

Comments
 (0)