Skip to content

Commit f7bc290

Browse files
Merge branch 'develop' into feature/interp_util_func
2 parents 0274acd + 2736e62 commit f7bc290

File tree

5 files changed

+84
-37
lines changed

5 files changed

+84
-37
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Code freeze date: YYYY-MM-DD
1414

1515
### Changed
1616

17+
- In `climada.util.plot.geo_im_from_array`, NaNs are plotted in gray while cells with no centroid are not plotted [#929](https://github.com/CLIMADA-project/climada_python/pull/929)
18+
- Renamed `climada.util.plot.subplots_from_gdf` to `climada.util.plot.plot_from_gdf` [#929](https://github.com/CLIMADA-project/climada_python/pull/929)
19+
1720
### Fixed
1821

1922
### Deprecated
@@ -89,6 +92,8 @@ CLIMADA tutorials. [#872](https://github.com/CLIMADA-project/climada_python/pull
8992
- climada.hazard.centroids.centr.Centroids.to_default_crs
9093
- climada.hazard.centroids.centr.Centroids.write_csv
9194
- climada.hazard.centroids.centr.Centroids.write_excel
95+
- climada.hazard.local_return_period [#898](https://github.com/CLIMADA-project/climada_python/pull/898)
96+
- climada.util.plot.subplots_from_gdf [#898](https://github.com/CLIMADA-project/climada_python/pull/898)
9297

9398
### Deprecated
9499

climada/hazard/tc_tracks.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1509,7 +1509,6 @@ def to_geodataframe(self, as_points=False, split_lines_antimeridian=True):
15091509
return gdf
15101510

15111511
@staticmethod
1512-
@numba.jit(forceobj=True)
15131512
def _one_interp_data(track, time_step_h, land_geom=None):
15141513
"""Interpolate values of one track.
15151514

climada/util/plot.py

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,15 @@ def geo_im_from_array(array_sub, coord, var_name, title,
309309
if not proj:
310310
mid_lon = 0.5 * sum(extent[:2])
311311
proj = ccrs.PlateCarree(central_longitude=mid_lon)
312-
if 'vmin' not in kwargs:
313-
kwargs['vmin'] = np.nanmin(array_sub)
314-
if 'vmax' not in kwargs:
315-
kwargs['vmax'] = np.nanmax(array_sub)
312+
313+
if "norm" in kwargs:
314+
min_value = kwargs["norm"].vmin
315+
else:
316+
kwargs['vmin'] = kwargs.get("vmin", np.nanmin(array_sub))
317+
min_value = kwargs['vmin']
318+
kwargs['vmax'] = kwargs.get("vmax", np.nanmax(array_sub))
319+
min_value = min_value/2 if min_value > 0 else min_value-1
320+
316321
if axes is None:
317322
proj_plot = proj
318323
if isinstance(proj, ccrs.PlateCarree):
@@ -328,8 +333,10 @@ def geo_im_from_array(array_sub, coord, var_name, title,
328333
if not isinstance(axes, np.ndarray):
329334
axes_iter = np.array([[axes]])
330335

331-
if 'cmap' not in kwargs:
332-
kwargs['cmap'] = CMAP_RASTER
336+
# prepare colormap
337+
cmap = plt.get_cmap(kwargs.pop("cmap", CMAP_RASTER))
338+
cmap.set_bad("gainsboro") # For NaNs and infs
339+
cmap.set_under("white", alpha=0) # For values below vmin
333340

334341
# Generate each subplot
335342
for array_im, axis, tit, name in zip(list_arr, axes_iter.flatten(), list_tit, list_name):
@@ -340,8 +347,11 @@ def geo_im_from_array(array_sub, coord, var_name, title,
340347
grid_x, grid_y = np.mgrid[
341348
extent[0]: extent[1]: complex(0, RESOLUTION),
342349
extent[2]: extent[3]: complex(0, RESOLUTION)]
343-
grid_im = griddata((coord[:, 1], coord[:, 0]), array_im,
344-
(grid_x, grid_y))
350+
grid_im = griddata(
351+
(coord[:, 1], coord[:, 0]),
352+
array_im,
353+
(grid_x, grid_y),
354+
fill_value=min_value)
345355
else:
346356
grid_x = coord[:, 1].reshape((width, height)).transpose()
347357
grid_y = coord[:, 0].reshape((width, height)).transpose()
@@ -359,8 +369,14 @@ def geo_im_from_array(array_sub, coord, var_name, title,
359369
# Create colormesh, colorbar and labels in axis
360370
cbax = make_axes_locatable(axis).append_axes('right', size="6.5%",
361371
pad=0.1, axes_class=plt.Axes)
362-
img = axis.pcolormesh(grid_x - mid_lon, grid_y, np.squeeze(grid_im),
363-
transform=proj, **kwargs)
372+
img = axis.pcolormesh(
373+
grid_x - mid_lon,
374+
grid_y,
375+
np.squeeze(grid_im),
376+
transform=proj,
377+
cmap=cmap,
378+
**kwargs
379+
)
364380
cbar = plt.colorbar(img, cax=cbax, orientation='vertical')
365381
cbar.set_label(name)
366382
axis.set_title("\n".join(wrap(tit)))
@@ -876,7 +892,7 @@ def multibar_plot(ax, data, colors=None, total_width=0.8, single_width=1,
876892
if legend:
877893
ax.legend(bars, data.keys())
878894

879-
def subplots_from_gdf(
895+
def plot_from_gdf(
880896
gdf: gpd.GeoDataFrame,
881897
colorbar_name: str = None,
882898
title_subplots: callable = None,
@@ -917,7 +933,7 @@ def subplots_from_gdf(
917933
# check if inputs are correct types
918934
if not isinstance(gdf, gpd.GeoDataFrame):
919935
raise ValueError("gdf is not a GeoDataFrame")
920-
gdf = gdf[['geometry', *[col for col in gdf.columns if col != 'geometry']]]
936+
gdf_values = gdf.drop(columns='geometry').values.T
921937

922938
# read meta data for fig and axis labels
923939
if not isinstance(colorbar_name, str):
@@ -927,23 +943,32 @@ def subplots_from_gdf(
927943
print("Unknown subplot-title-generation function. Subplot titles will be column names.")
928944
title_subplots = lambda cols: [f"{col}" for col in cols]
929945

930-
# change default plot kwargs if plotting return periods
931-
if colorbar_name.strip().startswith('Return Period'):
932-
if 'cmap' not in kwargs.keys():
933-
kwargs.update({'cmap': 'viridis_r'})
934-
if 'norm' not in kwargs.keys():
935-
kwargs.update(
936-
{'norm': mpl.colors.LogNorm(
937-
vmin=gdf.values[:,1:].min(), vmax=gdf.values[:,1:].max()
938-
),
939-
'vmin': None, 'vmax': None}
940-
)
946+
# use log colorbar for return periods and impact
947+
if (
948+
colorbar_name.strip().startswith(('Return Period', 'Impact')) and
949+
'norm' not in kwargs.keys() and
950+
# check if there are no zeros values in gdf
951+
not np.any(gdf_values == 0) and
952+
# check if value range too small for logarithmic colorscale
953+
(np.log10(np.nanmax(gdf_values)) - np.log10(np.nanmin(gdf_values))) > 2
954+
):
955+
kwargs.update(
956+
{'norm': mpl.colors.LogNorm(
957+
vmin=np.nanmin(gdf_values), vmax=np.nanmax(gdf_values)
958+
),
959+
'vmin': None, 'vmax': None}
960+
)
961+
962+
# use inverted color bar for return periods
963+
if (colorbar_name.strip().startswith('Return Period') and
964+
'cmap' not in kwargs.keys()):
965+
kwargs.update({'cmap': 'viridis_r'})
941966

942967
axis = geo_im_from_array(
943-
gdf.values[:,1:].T,
968+
gdf_values,
944969
gdf.geometry.get_coordinates().values[:,::-1],
945970
colorbar_name,
946-
title_subplots(gdf.columns[1:]),
971+
title_subplots(np.delete(gdf.columns, np.where(gdf.columns == 'geometry'))),
947972
smooth=smooth,
948973
axes=axis,
949974
figsize=figsize,

climada/util/test/test_plot.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def test_geo_bin_from_array(self):
127127
plt.close()
128128

129129
def test_geo_im_from_array(self):
130-
values = np.array([1, 2.0, 5, 1])
130+
values = np.array([1, 2.0, 5, np.nan])
131131
coord = np.array([[-17, 178], [-10, 180], [-27, 175], [-16, 186]])
132132
var_name = 'test'
133133
title = 'test'
@@ -137,8 +137,8 @@ def test_geo_im_from_array(self):
137137
proj=projection, smooth=True, axes=None, figsize=(9, 13), cmap=cmap)
138138
self.assertEqual(var_name, ax.get_title())
139139
colorbar = next(x.colorbar for x in ax.collections if x.colorbar)
140-
self.assertAlmostEqual(np.max(values), colorbar.vmax)
141-
self.assertAlmostEqual(np.min(values), colorbar.vmin)
140+
self.assertAlmostEqual(np.nanmax(values), colorbar.vmax)
141+
self.assertAlmostEqual(np.nanmin(values), colorbar.vmin)
142142
self.assertEqual(cmap, ax.collections[0].cmap.name)
143143
plt.close()
144144

@@ -147,20 +147,38 @@ def test_geo_im_from_array(self):
147147
proj=projection, smooth=True, axes=None, figsize=(9, 13), cmap=cmap)
148148
self.assertEqual(var_name, ax.get_title())
149149
colorbar = next(x.colorbar for x in ax.collections if x.colorbar)
150-
self.assertAlmostEqual(np.max(values), colorbar.vmax)
151-
self.assertAlmostEqual(np.min(values), colorbar.vmin)
150+
self.assertAlmostEqual(np.nanmax(values), colorbar.vmax)
151+
self.assertAlmostEqual(np.nanmin(values), colorbar.vmin)
152152
self.assertEqual(cmap, ax.collections[0].cmap.name)
153153
plt.close()
154154

155-
def test_subplots_from_gdf(self):
155+
def test_plot_from_gdf_no_log(self):
156+
"""test plot_from_gdf() with linear color bar (because there is a 0 in data)"""
157+
return_periods = gpd.GeoDataFrame(
158+
data = ((2., 5.), (0., 6.), (None, 2.), (1., 1000.)),
159+
columns = ('10.0', '20.0')
160+
)
161+
return_periods['geometry'] = (Point(45., 26.), Point(46., 26.), Point(45., 27.), Point(46., 27.))
162+
colorbar_name = 'Return Periods (Years)'
163+
title_subplots = lambda cols: [f'Threshold Intensity: {col} m/s' for col in cols]
164+
(axis1, axis2) = u_plot.plot_from_gdf(
165+
return_periods,
166+
colorbar_name=colorbar_name,
167+
title_subplots=title_subplots)
168+
self.assertEqual('Threshold Intensity: 10.0 m/s', axis1.get_title())
169+
self.assertEqual('Threshold Intensity: 20.0 m/s', axis2.get_title())
170+
plt.close()
171+
172+
def test_plot_from_gdf_log(self):
173+
"""test plot_from_gdf() with log color bar)"""
156174
return_periods = gpd.GeoDataFrame(
157-
data = ((2., 5.), (3., 6.), (None, 2.), (1., 7.)),
175+
data = ((2., 5.), (3., 6.), (None, 2.), (1., 1000.)),
158176
columns = ('10.0', '20.0')
159177
)
160178
return_periods['geometry'] = (Point(45., 26.), Point(46., 26.), Point(45., 27.), Point(46., 27.))
161179
colorbar_name = 'Return Periods (Years)'
162180
title_subplots = lambda cols: [f'Threshold Intensity: {col} m/s' for col in cols]
163-
(axis1, axis2) = u_plot.subplots_from_gdf(
181+
(axis1, axis2) = u_plot.plot_from_gdf(
164182
return_periods,
165183
colorbar_name=colorbar_name,
166184
title_subplots=title_subplots)

doc/tutorial/climada_hazard_Hazard.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@
577577
"<a id='Part5'></a> \n",
578578
"## Part 5: Visualize Hazards\n",
579579
"\n",
580-
"There are three different plot functions: `plot_intensity()`, `plot_fraction()`, and `plot_rp_intensity()`. Depending on the inputs, different properties can be visualized. Check the documentation of the functions. Using the function `local_return_period()` and the util function `subplots_from_gdf()`, one can plot local return periods for specific hazard intensities."
580+
"There are three different plot functions: `plot_intensity()`, `plot_fraction()`, and `plot_rp_intensity()`. Depending on the inputs, different properties can be visualized. Check the documentation of the functions. Using the function `local_return_period()` and the util function `plot_from_gdf()`, one can plot local return periods for specific hazard intensities."
581581
]
582582
},
583583
{
@@ -759,8 +759,8 @@
759759
"\n",
760760
"# 5. tropical cyclone return period maps for the threshold intensities [30, 40]\n",
761761
"return_periods, label, column_label = haz_tc_fl.local_return_period([30, 40])\n",
762-
"from climada.util.plot import subplots_from_gdf\n",
763-
"subplots_from_gdf(return_periods, colorbar_name=label, title_subplots=column_label)\n",
762+
"from climada.util.plot import plot_from_gdf\n",
763+
"plot_from_gdf(return_periods, colorbar_name=label, title_subplots=column_label)\n",
764764
"\n",
765765
"# 6. intensities of all the events in centroid with id 50\n",
766766
"haz_tc_fl.plot_intensity(centr=50)\n",

0 commit comments

Comments
 (0)