1
+ import json
2
+ import os
3
+ import time
4
+
5
+
1
6
from csep .core import forecasts
2
7
from csep .core import catalogs
3
8
from csep .core import poisson_evaluations
4
9
from csep .core import catalog_evaluations
10
+ from csep .core import regions
5
11
from csep .core .repositories import (
6
12
load_json ,
7
13
write_json
8
14
)
9
- from csep .io import (
10
- load_stochastic_event_sets ,
11
- load_catalog ,
12
- query_comcat ,
13
- load_evaluation_result ,
14
- load_gridded_forecast ,
15
- load_catalog_forecast
16
- )
17
15
18
16
from csep .utils import datasets
17
+ from csep .utils import readers
18
+
19
+ from csep .core .forecasts import GriddedForecast , CatalogForecast
20
+ from csep .models import EvaluationResult , CatalogNumberTestResult
21
+ from csep .utils .time_utils import (
22
+ utc_now_datetime ,
23
+ strptime_to_utc_datetime ,
24
+ datetime_to_utc_epoch ,
25
+ epoch_time_to_utc_datetime ,
26
+ utc_now_epoch
27
+ )
19
28
20
29
# this defines what is imported on a `from csep import *`
21
30
__all__ = [
22
31
'load_json' ,
23
32
'write_json' ,
24
33
'catalogs' ,
25
34
'datasets' ,
35
+ 'regions' ,
26
36
'poisson_evaluations' ,
27
37
'catalog_evaluations' ,
28
38
'forecasts' ,
31
41
'query_comcat' ,
32
42
'load_evaluation_result' ,
33
43
'load_gridded_forecast' ,
34
- 'load_catalog_forecast'
35
- ]
44
+ 'load_catalog_forecast' ,
45
+ 'utc_now_datetime' ,
46
+ 'strptime_to_utc_datetime' ,
47
+ 'datetime_to_utc_epoch' ,
48
+ 'epoch_time_to_utc_datetime' ,
49
+ 'utc_now_epoch'
50
+ ]
51
+
52
+ def load_stochastic_event_sets (filename , type = 'csv' , format = 'native' , ** kwargs ):
53
+ """ General function to load stochastic event sets
54
+
55
+ This function returns a generator to iterate through a collection of catalogs.
56
+ To load a forecast and include metadata use :func:`csep.load_catalog_forecast`.
57
+
58
+ Args:
59
+ filename (str): name of file or directory where stochastic event sets live.
60
+ type (str): either 'ucerf3' or 'csep' depending on the type of observed_catalog to load
61
+ format (str): ('csep' or 'native') if native catalogs are not converted to csep format.
62
+ kwargs (dict): see the documentation of that class corresponding to the type you selected
63
+ for the kwargs options
64
+
65
+ Returns:
66
+ (generator): :class:`~csep.core.catalogs.AbstractBaseCatalog`
67
+
68
+ """
69
+ if type not in ('ucerf3' , 'csv' ):
70
+ raise ValueError ("type must be one of the following: (ucerf3)" )
71
+
72
+ # use mapping to dispatch to correct function
73
+ # in general, stochastic event sets are loaded with classmethods and single catalogs use the
74
+ # constructor
75
+ mapping = {'ucerf3' : catalogs .UCERF3Catalog .load_catalogs ,
76
+ 'csv' : catalogs .CSEPCatalog .load_ascii_catalogs }
77
+
78
+ # dispatch to proper loading function
79
+ result = mapping [type ](filename , ** kwargs )
80
+
81
+ # factory function to load catalogs from different classes
82
+ while True :
83
+ try :
84
+ catalog = next (result )
85
+ except StopIteration :
86
+ return
87
+ except Exception :
88
+ raise
89
+ if format == 'native' :
90
+ yield catalog
91
+ elif format == 'csep' :
92
+ yield catalog .get_csep_format ()
93
+ else :
94
+ raise ValueError ('format must be either "native" or "csep!' )
95
+
96
+
97
+ def load_catalog (filename , type = 'csep-csv' , format = 'native' , loader = None , ** kwargs ):
98
+ """ General function to load single catalog
99
+
100
+ See corresponding class documentation for additional parameters.
101
+
102
+ Args:
103
+ type (str): ('ucerf3', 'csep-csv', 'zmap', 'jma-csv', 'ndk') default is 'csep-csv'
104
+ format (str): ('native', 'csep') determines whether the catalog should be converted into the csep
105
+ formatted catalog or kept as native.
106
+
107
+ Returns (:class:`~csep.core.catalogs.AbstractBaseCatalog`)
108
+ """
109
+
110
+ if type not in ('ucerf3' , 'csep-csv' , 'zmap' , 'jma-csv' , 'ndk' ) and loader is None :
111
+ raise ValueError ("type must be one of the following: ('ucerf3', 'csep-csv', 'zmap', 'jma-csv', 'ndk')." )
112
+
113
+ # map to correct catalog class, at some point these could be abstracted into configuration file
114
+ # this maps a human readable string to the correct catalog class and the correct loader function
115
+ class_loader_mapping = {
116
+ 'ucerf3' : {
117
+ 'class' : catalogs .UCERF3Catalog ,
118
+ 'loader' : None
119
+ },
120
+ 'csep-csv' : {
121
+ 'class' : catalogs .CSEPCatalog ,
122
+ 'loader' : readers .csep_ascii
123
+ },
124
+ 'zmap' : {
125
+ 'class' : catalogs .CSEPCatalog ,
126
+ 'loader' : readers .zmap_ascii
127
+ },
128
+ 'jma-csv' : {
129
+ 'class' : catalogs .CSEPCatalog ,
130
+ 'loader' : readers .jma_csv ,
131
+ },
132
+ 'ndk' : {
133
+ 'class' : catalogs .CSEPCatalog ,
134
+ 'loader' : readers .ndk
135
+ }
136
+ }
137
+
138
+ # treat json files using the from_dict() member instead of constructor
139
+ catalog_class = class_loader_mapping [type ]['class' ]
140
+ if os .path .splitext (filename )[- 1 ][1 :] == 'json' :
141
+ catalog = catalog_class .load_json (filename , ** kwargs )
142
+ else :
143
+ if loader is None :
144
+ loader = class_loader_mapping [type ]['loader' ]
145
+
146
+ catalog = catalog_class .load_catalog (filename = filename , loader = loader , ** kwargs )
147
+
148
+ # convert to csep format if needed
149
+ if format == 'native' :
150
+ return_val = catalog
151
+ elif format == 'csep' :
152
+ return_val = catalog .get_csep_format ()
153
+ else :
154
+ raise ValueError ('format must be either "native" or "csep"' )
155
+ return return_val
156
+
157
+
158
+ def query_comcat (start_time , end_time , min_magnitude = 2.50 ,
159
+ min_latitude = 31.50 , max_latitude = 43.00 ,
160
+ min_longitude = - 125.40 , max_longitude = - 113.10 , verbose = True , ** kwargs ):
161
+ """
162
+ Access Comcat catalog through web service
163
+
164
+ Args:
165
+ start_time: datetime object of start of catalog
166
+ end_time: datetime object for end of catalog
167
+ min_magnitude: minimum magnitude to query
168
+ min_latitude: maximum magnitude to query
169
+ max_latitude: max latitude of bounding box
170
+ min_longitude: min latitude of bounding box
171
+ max_longitude: max longitude of bounding box
172
+ region: :class:`csep.core.regions.CartesianGrid2D
173
+ verbose (bool): print catalog summary statistics
174
+
175
+ Returns:
176
+ :class:`csep.core.catalogs.ComcatCatalog
177
+ """
178
+
179
+ # Timezone should be in UTC
180
+ t0 = time .time ()
181
+ eventlist = readers ._query_comcat (start_time = start_time , end_time = end_time ,
182
+ min_magnitude = min_magnitude ,
183
+ min_latitude = min_latitude , max_latitude = max_latitude ,
184
+ min_longitude = min_longitude , max_longitude = max_longitude )
185
+ t1 = time .time ()
186
+ comcat = catalogs .CSEPCatalog (data = eventlist , date_accessed = utc_now_datetime (), ** kwargs )
187
+ print ("Fetched ComCat catalog in {} seconds.\n " .format (t1 - t0 ))
188
+ if verbose :
189
+ print ("Downloaded catalog from ComCat with following parameters" )
190
+ print ("Start Date: {}\n End Date: {}" .format (str (comcat .start_time ), str (comcat .end_time )))
191
+ print ("Min Latitude: {} and Max Latitude: {}" .format (comcat .min_latitude , comcat .max_latitude ))
192
+ print ("Min Longitude: {} and Max Longitude: {}" .format (comcat .min_longitude , comcat .max_longitude ))
193
+ print ("Min Magnitude: {}" .format (comcat .min_magnitude ))
194
+ print (f"Found { comcat .event_count } events in the ComCat catalog." )
195
+ return comcat
196
+
197
+
198
+ def load_evaluation_result (fname ):
199
+ """ Load evaluation result stored as json file
200
+
201
+ Returns:
202
+ :class:`csep.core.evaluations.EvaluationResult`
203
+
204
+ """
205
+ # tries to return the correct class for the evaluation result. if it cannot find the type simply returns the basic result.
206
+ evaluation_result_factory = {
207
+ 'default' : EvaluationResult ,
208
+ 'CatalogNumberTestResult' : CatalogNumberTestResult
209
+ }
210
+ with open (fname , 'r' ) as json_file :
211
+ json_dict = json .load (json_file )
212
+ try :
213
+ evaluation_type = json_dict ['named_type' ]
214
+ except :
215
+ evaluation_type = 'default'
216
+ eval_result = evaluation_result_factory [evaluation_type ].from_dict (json_dict )
217
+ return eval_result
218
+
219
+
220
+ def load_gridded_forecast (fname , loader = None , ** kwargs ):
221
+ """ Loads grid based forecast from hard-disk.
222
+
223
+ The function loads the forecast provided with at the filepath defined by fname. The function attempts to understand
224
+ the file format based on the extension of the filepath. Optionally, if loader function is provided, that function
225
+ will be used to load the forecast. The loader function should return a :class:`csep.core.forecasts.GriddedForecast`
226
+ class with the region and magnitude members correctly assigned.
227
+
228
+ File extensions:
229
+ .dat -> CSEP ascii format
230
+ .xml -> CSEP xml format (not yet implemented)
231
+ .h5 -> CSEP hdf5 format (not yet implemented)
232
+ .bin -> CSEP binary format (not yet implemented)
233
+
234
+ Args:
235
+ fname (str): path of grid based forecast
236
+ loader (func): function to load forecast in bespoke format needs to return :class:`csep.core.forecasts.GriddedForecast`
237
+ and first argument should be required and the filename of the forecast to load
238
+ called as loader(func, **kwargs).
239
+
240
+ **kwargs: passed into loader function
241
+
242
+ Throws:
243
+ FileNotFoundError: when the file extension is not known and a loader is not provided.
244
+ AttributeError: if loader is provided and is not callable.
245
+
246
+ Returns:
247
+ :class:`csep.core.forecasts.GriddedForecast`
248
+ """
249
+ # mapping from file extension to loader function, new formats by default they need to be added here
250
+ forecast_loader_mapping = {
251
+ 'dat' : GriddedForecast .load_ascii ,
252
+ 'xml' : None ,
253
+ 'h5' : None ,
254
+ 'bin' : None
255
+ }
256
+
257
+ # sanity checks
258
+ if not os .path .exists (fname ):
259
+ raise FileNotFoundError (f"Could not locate file { fname } . Unable to load forecast." )
260
+ # sanity checks
261
+ if loader is not None and not callable (loader ):
262
+ raise AttributeError ("Loader must be callable. Unable to load forecast." )
263
+ extension = os .path .splitext (fname )[- 1 ][1 :]
264
+ if extension not in forecast_loader_mapping .keys () and loader is None :
265
+ raise AttributeError ("File extension should be in ('dat','xml','h5','bin') if loader not provided." )
266
+
267
+ if extension in ('xml' ,'h5' ,'bin' ):
268
+ raise NotImplementedError
269
+
270
+ # assign default loader
271
+ if loader is None :
272
+ loader = forecast_loader_mapping [extension ]
273
+ forecast = loader (fname , ** kwargs )
274
+ # final sanity check
275
+ if not isinstance (forecast , GriddedForecast ):
276
+ raise ValueError ("Forecast not instance of GriddedForecast" )
277
+ return forecast
278
+
279
+
280
+ def load_catalog_forecast (fname , catalog_loader = None , format = 'native' , type = 'ascii' , ** kwargs ):
281
+ """ General function to handle loading catalog forecasts.
282
+
283
+ Currently, just a simple wrapper, but can contain more complex logic in the future.
284
+
285
+ Args:
286
+ fname (str): pathname to the forecast file or directory containing the forecast files
287
+ catalog_loader (func): callable that can load catalogs, see load_stochastic_event_sets above.
288
+ format (str): either 'native' or 'csep'. if 'csep', will attempt to be returned into csep catalog format. used to convert between
289
+ observed_catalog type.
290
+ type (str): either 'ucerf3' or 'csep', determines the catalog format of the forecast. if loader is provided, then
291
+ this parameter is ignored.
292
+ **kwargs: other keyword arguments passed to the :class:`csep.core.forecasts.CatalogForecast`.
293
+
294
+ Returns:
295
+ :class:`csep.core.forecasts.CatalogForecast`
296
+ """
297
+ # sanity checks
298
+ if not os .path .exists (fname ):
299
+ raise FileNotFoundError (f"Could not locate file { fname } . Unable to load forecast." )
300
+ # sanity checks
301
+ if catalog_loader is not None and not callable (catalog_loader ):
302
+ raise AttributeError ("Loader must be callable. Unable to load forecast." )
303
+ # factory methods for loading different types of catalogs
304
+ catalog_loader_mapping = {
305
+ 'ascii' : catalogs .CSEPCatalog .load_ascii_catalogs ,
306
+ 'ucerf3' : catalogs .UCERF3Catalog .load_catalogs
307
+ }
308
+ if catalog_loader is None :
309
+ catalog_loader = catalog_loader_mapping [type ]
310
+ # try and parse information from filename and send to forecast constructor
311
+ if format == 'native' and type == 'ascii' :
312
+ try :
313
+ basename = str (os .path .basename (fname .rstrip ('/' )).split ('.' )[0 ])
314
+ split_fname = basename .split ('_' )
315
+ name = split_fname [0 ]
316
+ start_time = strptime_to_utc_datetime (split_fname [1 ], format = "%Y-%m-%dT%H-%M-%S-%f" )
317
+ # update kwargs
318
+ _ = kwargs .setdefault ('name' , name )
319
+ _ = kwargs .setdefault ('start_time' , start_time )
320
+ except :
321
+ pass
322
+ # create observed_catalog forecast
323
+ return CatalogForecast (filename = fname , loader = catalog_loader , catalog_format = format , catalog_type = type , ** kwargs )
324
+
0 commit comments