Skip to content

Commit 9ff2316

Browse files
authored
changes to catalogs and added defaults for plot functions. closes #99 and closes #95. (#100)
- added defaults for plot catalog to make the basic plot look prettier - added defaults for forecast plots - forecast.plot() properly returns the figure axes - added function to compute tight_bbox around region - added option region_border to plot tight bbox around regions in catalog plots - added option region_border to plot tight boox around region in forecast plots - added start of plots.rst for more in-depth documentation on plotting - fixes the catalog evaluation example. closes #99
1 parent c6f01f3 commit 9ff2316

File tree

7 files changed

+129
-16
lines changed

7 files changed

+129
-16
lines changed

csep/core/catalogs.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -845,14 +845,33 @@ def plot(self, ax=None, show=False, extent=None, set_global=False, plot_args=Non
845845
"""
846846
# no mutable function arguments
847847

848+
plot_args_default = {
849+
'basemap': 'ESRI_terrain',
850+
'markersize': 2,
851+
'markercolor': 'red',
852+
'alpha': 0.3,
853+
'mag_scale': 7,
854+
'legend': True,
855+
'grid_labels': True,
856+
'legend_loc': 3,
857+
'figsize': (8, 8),
858+
'title': self.name,
859+
'mag_ticks': [4.0, 5.0, 6.0, 7.0]
860+
}
861+
# Plot the region border (if it exists) by default
862+
try:
863+
# This will throw error if catalog does not have region
864+
_ = self.region.num_nodes
865+
plot_args_default['region_border'] = True
866+
except AttributeError:
867+
pass
848868

849869
plot_args = plot_args or {}
850-
plot_args.setdefault('figsize', (10, 10))
851-
plot_args.setdefault('title', self.name)
870+
plot_args_default.update(plot_args)
852871

853872
# this call requires internet connection and basemap
854873
ax = plot_catalog(self, ax=ax,show=show, extent=extent,
855-
set_global=set_global, plot_args=plot_args)
874+
set_global=set_global, plot_args=plot_args_default)
856875
return ax
857876

858877

csep/core/forecasts.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -673,10 +673,20 @@ def get_expected_rates(self, verbose=False, return_skipped=False):
673673
else:
674674
return self.expected_rates
675675

676-
def plot(self, **kwargs):
676+
def plot(self, plot_args = None, **kwargs):
677+
plot_args = plot_args or {}
677678
if self.expected_rates is None:
678679
self.get_expected_rates()
679-
self.expected_rates.plot(**kwargs)
680+
args_dict = {'title': self.name,
681+
'grid_labels': True,
682+
'grid': True,
683+
'borders': True,
684+
'feature_lw': 0.5,
685+
'basemap': 'ESRI_terrain',
686+
}
687+
args_dict.update(plot_args)
688+
ax = self.expected_rates.plot(**kwargs, plot_args=args_dict)
689+
return ax
680690

681691
def get_dataframe(self):
682692
"""Return a single dataframe with all of the events from all of the catalogs."""

csep/core/regions.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import pyproj
1212

1313
# PyCSEP imports
14-
from csep.utils.calc import bin1d_vec, cleaner_range
14+
from csep.utils.calc import bin1d_vec, cleaner_range, first_nonnan, last_nonnan
1515
from csep.utils.scaling_relationships import WellsAndCoppersmith
1616

1717
def california_relm_collection_region(dh_scale=1, magnitudes=None, name="relm-california-collection"):
@@ -730,3 +730,26 @@ def _build_bitmask_vec(self):
730730

731731
return a, xs, ys
732732

733+
def tight_bbox(self):
734+
# creates tight bounding box around the region, probably a faster way to do this.
735+
ny, nx = self.idx_map.shape
736+
asc = []
737+
desc = []
738+
for j in range(ny):
739+
row = self.idx_map[j, :]
740+
argmin = first_nonnan(row)
741+
argmax = last_nonnan(row)
742+
# points are stored clockwise
743+
poly_min = self.polygons[int(row[argmin])].points
744+
asc.insert(0, poly_min[0])
745+
asc.insert(0, poly_min[1])
746+
poly_max = self.polygons[int(row[argmax])].points
747+
desc.append(poly_max[3])
748+
desc.append(poly_max[2])
749+
# close the loop
750+
poly = np.array(asc + desc)
751+
sorted_idx = np.sort(np.unique(poly, return_index=True, axis=0)[1], kind='stable')
752+
unique_poly = poly[sorted_idx]
753+
unique_poly = np.append(unique_poly, [unique_poly[0, :]], axis=0)
754+
return unique_poly
755+

csep/utils/calc.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,13 @@ def cleaner_range(start, end, h):
213213
start = numpy.floor(const * start)
214214
end = numpy.floor(const * end)
215215
d = const * h
216-
return numpy.arange(start, end + d / 2, d) / const
216+
return numpy.arange(start, end + d / 2, d) / const
217+
218+
def first_nonnan(arr, axis=0, invalid_val=-1):
219+
mask = arr==arr
220+
return numpy.where(mask.any(axis=axis), mask.argmax(axis=axis), invalid_val)
221+
222+
def last_nonnan(arr, axis=0, invalid_val=-1):
223+
mask = arr==arr
224+
val = arr.shape[axis] - numpy.flip(mask, axis=axis).argmax(axis=axis) - 1
225+
return numpy.where(mask.any(axis=axis), val, invalid_val)

csep/utils/plots.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -652,11 +652,6 @@ def plot_catalog(catalog, ax=None, show=False, extent=None, set_global=False, pl
652652
653653
"""
654654
# Get spatial information for plotting
655-
bbox = catalog.get_bbox()
656-
if extent is None:
657-
dh = (bbox[1] - bbox[0])/20.
658-
dv = (bbox[3] - bbox[2]) / 20.
659-
extent = [bbox[0] - dh, bbox[1]+dh, bbox[2] -dv, bbox[3] + dv]
660655

661656
# Retrieve plot arguments
662657
plot_args = plot_args or {}
@@ -674,6 +669,7 @@ def plot_catalog(catalog, ax=None, show=False, extent=None, set_global=False, pl
674669
legend_loc = plot_args.get('legend_loc', 1)
675670
mag_ticks = plot_args.get('mag_ticks', False)
676671
labelspacing = plot_args.get('labelspacing', 1)
672+
region_border = plot_args.get('region_border', True)
677673
# cartopy properties
678674
projection = plot_args.get('projection', ccrs.PlateCarree(central_longitude=0.0))
679675
grid = plot_args.get('grid', True)
@@ -685,8 +681,19 @@ def plot_catalog(catalog, ax=None, show=False, extent=None, set_global=False, pl
685681
linecolor = plot_args.get('linecolor', 'black')
686682

687683

688-
# Instantiage GeoAxes object
684+
bbox = catalog.get_bbox()
685+
if plot_args['region_border']:
686+
try:
687+
bbox = catalog.region.get_bbox()
688+
except AttributeError:
689+
pass
689690

691+
if extent is None:
692+
dh = (bbox[1] - bbox[0])/20.
693+
dv = (bbox[3] - bbox[2]) / 20.
694+
extent = [bbox[0] - dh, bbox[1]+dh, bbox[2] -dv, bbox[3] + dv]
695+
696+
# Instantiage GeoAxes object
690697
if ax is None:
691698
fig = pyplot.figure(figsize=figsize)
692699
ax = fig.add_subplot(111, projection=projection)
@@ -727,6 +734,13 @@ def size_map(markersize, values, scale):
727734
loc=legend_loc, title=r"Magnitudes",title_fontsize=16,
728735
labelspacing=labelspacing, handletextpad=5, framealpha=False)
729736

737+
if region_border:
738+
try:
739+
pts = catalog.region.tight_bbox()
740+
ax.plot(pts[:, 0], pts[:, 1], lw=1, color='black')
741+
except AttributeError:
742+
print("unable to get tight bbox")
743+
730744
# Gridline options
731745
if grid:
732746
gl = ax.gridlines(draw_labels=grid_labels, alpha=0.5)
@@ -801,6 +815,7 @@ def plot_spatial_dataset(gridded, region, ax=None, show=False, extent=None, set_
801815
borders = plot_args.get('borders', False)
802816
linewidth = plot_args.get('linewidth', True)
803817
linecolor = plot_args.get('linecolor', 'black')
818+
region_border = plot_args.get('region_border', True)
804819
# color bar properties
805820
cmap = plot_args.get('cmap', None)
806821
clabel = plot_args.get('clabel', '')
@@ -857,6 +872,10 @@ def plot_spatial_dataset(gridded, region, ax=None, show=False, extent=None, set_
857872
gl.xformatter = LONGITUDE_FORMATTER
858873
gl.yformatter = LATITUDE_FORMATTER
859874

875+
if region_border:
876+
pts = region.tight_bbox()
877+
ax.plot(pts[:,0], pts[:,1], lw=1, color='black')
878+
860879
# matplotlib figure options
861880
ax.set_title(title, y=1.06)
862881
if filename is not None:

docs/concepts/plots.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.. _plots-reference:
2+
3+
#####
4+
Plots
5+
#####
6+
7+
PyCSEP provides several functions to produce commonly used plots, such as an earthquake forecast or the evaluation catalog
8+
or perhaps a combination of the two.
9+
10+
.. contents:: Table of Contents
11+
:local:
12+
:depth: 2
13+
14+
************
15+
Introduction
16+
************
17+
18+
19+
20+
**************
21+
Plot arguments
22+
**************
23+
24+
***************
25+
Available plots
26+
***************
27+

examples/tutorials/catalog_forecast_evaluation.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
# Forecasts should define a time horizon in which they are valid. The choice is flexible for catalog-based forecasts, because
3434
# the catalogs can be filtered to accommodate multiple end-times. Conceptually, these should be separate forecasts.
3535

36-
start_time = time_utils.strptime_to_utc_datetime("1992-06-28 11:57:34.14")
37-
end_time = time_utils.strptime_to_utc_datetime("1992-07-28 11:57:34.14")
36+
start_time = time_utils.strptime_to_utc_datetime("1992-06-28 11:57:35.0")
37+
end_time = time_utils.strptime_to_utc_datetime("1992-07-28 11:57:35.0")
3838

3939
####################################################################################################################################
4040
# Define spatial and magnitude regions
@@ -72,7 +72,8 @@
7272

7373
forecast = csep.load_catalog_forecast(datasets.ucerf3_ascii_format_landers_fname,
7474
start_time = start_time, end_time = end_time,
75-
region = space_magnitude_region)
75+
region = space_magnitude_region,
76+
apply_filters = True)
7677

7778
# Assign filters to forecast
7879
forecast.filters = [f'origin_time >= {forecast.start_epoch}', f'origin_time < {forecast.end_epoch}']
@@ -92,7 +93,12 @@
9293
comcat_catalog = csep.query_comcat(start_time, end_time, min_magnitude=forecast.min_magnitude)
9394

9495
# Filter observed catalog using the same region as the forecast
96+
9597
comcat_catalog = comcat_catalog.filter_spatial(forecast.region)
98+
print(comcat_catalog)
99+
100+
# Plot the catalog
101+
comcat_catalog.plot()
96102

97103
####################################################################################################################################
98104
# Perform number test

0 commit comments

Comments
 (0)