Skip to content

Commit

Permalink
FIX: Add clip limits to Nightshade floating point calculations
Browse files Browse the repository at this point in the history
Depending on the values input to Nightshade, the floating point
precision could put the range for arccos outside of [-1, 1], which
in turn numpy returns as nan's, yielding bad geometries further
downstream. This patch clips the arccos calculations to [-1, 1] to
guarantee we aren't out of the valid floating point bounds.
  • Loading branch information
greglucas committed Jan 21, 2022
1 parent 591fb54 commit f43ae9e
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 4 deletions.
9 changes: 6 additions & 3 deletions lib/cartopy/feature/nightshade.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ def __init__(self, date=None, delta=0.1, refraction=-0.83,

# Solve the generalized equation for omega0, which is the
# angle of sunrise/sunset from solar noon
omega0 = np.rad2deg(np.arccos(np.sin(np.deg2rad(refraction)) /
np.cos(np.deg2rad(y))))
# We need to clip the input to arccos to [-1, 1] due to floating
# point precision and arccos creating nans for values outside
# of the domain
arccos_tmp = np.clip(np.sin(np.deg2rad(refraction)) /
np.cos(np.deg2rad(y)), -1, 1)
omega0 = np.rad2deg(np.arccos(arccos_tmp))

# Fill the longitude values from the offset for midnight.
# This needs to be a closed loop to fill the polygon.
Expand All @@ -89,7 +93,6 @@ def __init__(self, date=None, delta=0.1, refraction=-0.83,

kwargs.setdefault('facecolor', color)
kwargs.setdefault('alpha', alpha)

geom = sgeom.Polygon(np.column_stack((x, y)))
return super().__init__(
[geom], rotated_pole, **kwargs)
Expand Down
11 changes: 10 additions & 1 deletion lib/cartopy/tests/feature/test_nightshade.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import pytest

from cartopy.feature.nightshade import _julian_day, _solar_position
from cartopy.feature.nightshade import _julian_day, _solar_position, Nightshade


def test_julian_day():
Expand Down Expand Up @@ -44,3 +44,12 @@ def test_solar_position(dt, true_lat, true_lon):
lat, lon = _solar_position(dt)
assert pytest.approx(true_lat, 0.1) == lat
assert pytest.approx(true_lon, 0.1) == lon


def test_nightshade_floating_point():
# Smoke test for clipping nightshade floating point values
date = datetime(1999, 12, 31, 12)

# This can cause an error with floating point precision if it is
# set to exactly -6 and arccos input is not clipped to [-1, 1]
Nightshade(date, refraction=-6.0, color='none')

0 comments on commit f43ae9e

Please sign in to comment.