Skip to content

Commit

Permalink
Fix cropping cross 0 deg
Browse files Browse the repository at this point in the history
  • Loading branch information
fzhu2e committed Sep 10, 2023
1 parent d611553 commit 711ec40
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 19 deletions.
32 changes: 25 additions & 7 deletions cfr/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,13 +550,21 @@ def plot(self, **kwargs):

kwargs['title'] = f'{self.da.name}, {date_str}'

if len(set(np.diff(self.da.lon))) > 1:
# the case when longitudes cross 0 degree
fd_plot = self.wrap_lon(mode='180')
kwargs['central_longitude'] = 0
else:
fd_plot = self

_kwargs.update(kwargs)
if len(self.da.dims) == 3:
vals = self.da.values[0]
elif len(self.da.dims) == 2:
vals = self.da.values

if len(fd_plot.da.dims) == 3:
vals = fd_plot.da.values[0]
elif len(fd_plot.da.dims) == 2:
vals = fd_plot.da.values

fig, ax = visual.plot_field_map(vals, self.da.lat, self.da.lon, **_kwargs)
fig, ax = visual.plot_field_map(vals, fd_plot.da.lat, fd_plot.da.lon, **_kwargs)

return fig, ax

Expand Down Expand Up @@ -655,6 +663,8 @@ def regrid(self, lats, lons, periodic_lon=False):
def crop(self, lat_min=-90, lat_max=90, lon_min=0, lon_max=360):
''' Crop the climate field based on the range of latitude and longitude.
Note that in cases when the crop range is crossing the 0 degree of longitude, `lon_min` should be less than 0.
Args:
lat_min (float): the lower bound of latitude to crop.
lat_max (float): the upper bound of latitude to crop.
Expand All @@ -663,7 +673,13 @@ def crop(self, lat_min=-90, lat_max=90, lon_min=0, lon_max=360):
'''

mask_lat = (self.da.lat >= lat_min) & (self.da.lat <= lat_max)
mask_lon = (self.da.lon >= lon_min) & (self.da.lon <= lon_max)
if lon_min >= 0:
mask_lon = (self.da.lon >= lon_min) & (self.da.lon <= lon_max)
else:
# the case when longitudes in mode [-180, 180]
lon_min = np.mod(lon_min, 360)
lon_max = np.mod(lon_max, 360)
mask_lon = (self.da.lon >= lon_min) | (self.da.lon <= lon_max)

da = self.da.sel({
'lat': self.da.lat[mask_lat],
Expand All @@ -682,7 +698,9 @@ def geo_mean(self, lat_min=-90, lat_max=90, lon_min=0, lon_max=360):
lon_min (float): the lower bound of longitude for the calculation.
lon_max (float): the upper bound of longitude for the calculation.
'''
m = utils.geo_mean(self.da, lat_min=lat_min, lat_max=lat_max, lon_min=lon_min, lon_max=lon_max)
fdc = self.crop(lat_min=lat_min, lat_max=lat_max, lon_min=lon_min, lon_max=lon_max)
wgts = np.cos(np.deg2rad(fdc.da['lat']))
m = fdc.da.weighted(wgts).mean(('lon', 'lat'))
ts = EnsTS(time=m['time'].values, value=m.values)
return ts

Expand Down
8 changes: 8 additions & 0 deletions cfr/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,14 @@ def coefficient_efficiency(ref, test, valid=None):
return CE

def geo_mean(da, lat_min=-90, lat_max=90, lon_min=0, lon_max=360, lat_name='lat', lon_name='lon'):
''' Calculate the geographical mean value of the climate field.
Args:
lat_min (float): the lower bound of latitude for the calculation.
lat_max (float): the upper bound of latitude for the calculation.
lon_min (float): the lower bound of longitude for the calculation.
lon_max (float): the upper bound of longitude for the calculation.
'''
# calculation
mask_lat = (da[lat_name] >= lat_min) & (da[lat_name] <= lat_max)
mask_lon = (da[lon_name] >= lon_min) & (da[lon_name] <= lon_max)
Expand Down
23 changes: 13 additions & 10 deletions cfr/visual.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def plot_field_map(field_var, lat, lon, levels=50, add_cyclic_point=True,
site_markersize=50, site_color=sns.xkcd_rgb['amber'],
projection='Robinson', transform=ccrs.PlateCarree(),
proj_args=None, latlon_range=None, central_longitude=180,
lon_ticks=[60, 120, 180, 240, 300], lat_ticks=[-90, -45, 0, 45, 90],
lon_ticks=[0, 60, 120, 180, 240, 300], lat_ticks=[-90, 45, 0, 45, 90],
land_color=sns.xkcd_rgb['light grey'], ocean_color=sns.xkcd_rgb['light grey'],
land_zorder=None, ocean_zorder=None, signif_values=None, signif_range=[0.05, 9999], hatch='..',
clim=None, cmap=None, cmap_under=None, cmap_over=None, cmap_bad=None, extend='both', mode=None, add_gridlines=False,
Expand Down Expand Up @@ -324,7 +324,6 @@ def plot_field_map(field_var, lat, lon, levels=50, add_cyclic_point=True,
ax (object, optional): `matplotlib.axes`. Defaults to None.
'''

if mode is None:
ndim = len(np.shape(lat))
if ndim == 1:
Expand Down Expand Up @@ -383,19 +382,23 @@ def plot_field_map(field_var, lat, lon, levels=50, add_cyclic_point=True,
if title:
plt.title(title, fontsize=title_size, fontweight=title_weight)

if latlon_range:
if latlon_range is not None:
lat_min, lat_max, lon_min, lon_max = latlon_range
ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=transform)
lon_formatter = LongitudeFormatter(zero_direction_label=False)
lat_formatter = LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
lon_ticks = np.array(lon_ticks)
lat_ticks = np.array(lat_ticks)
lon_ticks = np.array(lon_ticks)
if lon_min < 0:
lon_ticks = np.sort(np.mod(lon_ticks+180, 360) - 180)

mask_lon = (lon_ticks >= lon_min) & (lon_ticks <= lon_max)
mask_lat = (lat_ticks >= lat_min) & (lat_ticks <= lat_max)
ax.set_xticks(lon_ticks[mask_lon], crs=ccrs.PlateCarree())
ax.set_yticks(lat_ticks[mask_lat], crs=ccrs.PlateCarree())
if lon_min >= 0:
lon_formatter = LongitudeFormatter(zero_direction_label=False)
lat_formatter = LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
ax.set_xticks(lon_ticks[mask_lon], crs=ccrs.PlateCarree())
ax.set_yticks(lat_ticks[mask_lat], crs=ccrs.PlateCarree())
else:
ax.set_global()

Expand Down
2 changes: 1 addition & 1 deletion docsrc/cg-updating-docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Updating the Documentation

About the `cfr` documentation
"""""""""""""""""""""""""""""""""
`cfr`'s documentation is built automatically from the function and class docstrings, via `Sphinx The Docs <https://www.sphinx-doc.org>`_.
`cfr`'s documentation is built automatically from the function and class docstrings, via `Sphinx <https://www.sphinx-doc.org>`_.
It is therefore especially important for your code to include a docstring, and to modify the docstrings of the functions/classes you modified to make sure the documentation is current.

Updating docstrings
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='cfr', # required
version='2023.9.8',
version='2023.9.9',
description='cfr: a Python package for Climate Field Reconstruction',
long_description=long_description,
long_description_content_type='text/x-rst',
Expand Down

0 comments on commit 711ec40

Please sign in to comment.