Skip to content

Commit

Permalink
MNT: Refactor projection handling in Formatters
Browse files Browse the repository at this point in the history
Move the source and target projection calculation into the
set_axis() call instead of the __call__ method.
  • Loading branch information
greglucas committed Nov 10, 2023
1 parent d4c0ef0 commit a6c3419
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 31 deletions.
39 changes: 22 additions & 17 deletions lib/cartopy/mpl/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ class _PlateCarreeFormatter(Formatter):
rectangular projection (e.g. Plate Carree, Mercator).
"""

_target_projection = ccrs.PlateCarree()

def __init__(self, direction_label=True, degree_symbol='°',
number_format='g', transform_precision=1e-8, dms=False,
minute_symbol='′', second_symbol='″',
Expand Down Expand Up @@ -52,24 +49,14 @@ def __init__(self, direction_label=True, degree_symbol='°',
cardinal_labels = {}
self._cardinal_labels = cardinal_labels
self._decimal_point = decimal_point
self._source_projection = None
self._target_projection = None

def __call__(self, value, pos=None):
if self.axis is not None and isinstance(self.axis.axes, GeoAxes):

# We want to produce labels for values in the familiar Plate Carree
# projection, so convert the tick values from their own projection
# before formatting them.
source = self.axis.axes.projection
if not isinstance(source, (ccrs._RectangularProjection,
ccrs.Mercator)):
raise TypeError("This formatter cannot be used with "
"non-rectangular projections.")
if source.globe != self._target_projection.globe:
# The transforms need to use the same globe
self._target_projection = ccrs.PlateCarree(globe=source.globe)
if self._source_projection is not None:
projected_value = self._apply_transform(value,
self._target_projection,
source)
self._source_projection)

# Round the transformed value using a given precision for display
# purposes. Transforms can introduce minor rounding errors that
Expand Down Expand Up @@ -140,6 +127,24 @@ def _get_dms(self, x):
secs = np.round((y - mins) * 60, self._precision - 3)
return x, degs, mins, secs

def set_axis(self, axis):
super().set_axis(axis)

# Set the source and target projections for the formatter
# setting them to None if we aren't interacting with a GeoAxes
if self.axis is None or not isinstance(self.axis.axes, GeoAxes):
self._source_projection = None
self._target_projection = None
return

self._source_projection = self.axis.axes.projection
if not isinstance(self._source_projection, (ccrs._RectangularProjection,
ccrs.Mercator)):
raise TypeError("This formatter cannot be used with "
"non-rectangular projections.")
# The transforms need to use the same globe
self._target_projection = ccrs.PlateCarree(globe=self._source_projection.globe)

def set_locs(self, locs):
Formatter.set_locs(self, locs)
if not self._auto_hide:
Expand Down
27 changes: 13 additions & 14 deletions lib/cartopy/tests/mpl/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,15 @@
@pytest.mark.parametrize('cls', [LatitudeFormatter, LongitudeFormatter])
def test_formatter_bad_projection(cls):
formatter = cls()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=ccrs.Orthographic()))
match = r'This formatter cannot be used with non-rectangular projections\.'
with pytest.raises(TypeError, match=match):
formatter(0)
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=ccrs.Orthographic())))


def test_LatitudeFormatter():
formatter = LatitudeFormatter()
p = ccrs.PlateCarree()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-90, -60, -30, 0, 30, 60, 90]
result = [formatter(tick) for tick in test_ticks]
expected = ['90°S', '60°S', '30°S', '0°', '30°N', '60°N', '90°N']
Expand All @@ -42,7 +41,7 @@ def test_LatitudeFormatter():
def test_LatitudeFormatter_direction_label():
formatter = LatitudeFormatter(direction_label=False)
p = ccrs.PlateCarree()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-90, -60, -30, 0, 30, 60, 90]
result = [formatter(tick) for tick in test_ticks]
expected = ['-90°', '-60°', '-30°', '0°', '30°', '60°', '90°']
Expand All @@ -52,7 +51,7 @@ def test_LatitudeFormatter_direction_label():
def test_LatitudeFormatter_degree_symbol():
formatter = LatitudeFormatter(degree_symbol='')
p = ccrs.PlateCarree()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-90, -60, -30, 0, 30, 60, 90]
result = [formatter(tick) for tick in test_ticks]
expected = ['90S', '60S', '30S', '0', '30N', '60N', '90N']
Expand All @@ -62,7 +61,7 @@ def test_LatitudeFormatter_degree_symbol():
def test_LatitudeFormatter_number_format():
formatter = LatitudeFormatter(number_format='.2f', dms=False)
p = ccrs.PlateCarree()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-90, -60, -30, 0, 30, 60, 90]
result = [formatter(tick) for tick in test_ticks]
expected = ['90.00°S', '60.00°S', '30.00°S', '0.00°',
Expand All @@ -73,7 +72,7 @@ def test_LatitudeFormatter_number_format():
def test_LatitudeFormatter_mercator():
formatter = LatitudeFormatter()
p = ccrs.Mercator()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-15496570.739707904, -8362698.548496634,
-3482189.085407435, 0.0, 3482189.085407435,
8362698.548496634, 15496570.739707898]
Expand All @@ -85,7 +84,7 @@ def test_LatitudeFormatter_mercator():
def test_LatitudeFormatter_small_numbers():
formatter = LatitudeFormatter(number_format='.7f', dms=False)
p = ccrs.PlateCarree()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [40.1275150, 40.1275152, 40.1275154]
result = [formatter(tick) for tick in test_ticks]
expected = ['40.1275150°N', '40.1275152°N', '40.1275154°N']
Expand All @@ -97,7 +96,7 @@ def test_LongitudeFormatter_direction_label():
dateline_direction_label=True,
zero_direction_label=True)
p = ccrs.PlateCarree()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-180, -120, -60, 0, 60, 120, 180]
result = [formatter(tick) for tick in test_ticks]
expected = ['-180°', '-120°', '-60°', '0°', '60°', '120°', '180°']
Expand All @@ -116,7 +115,7 @@ def test_LongitudeFormatter_central_longitude(central_longitude, kwargs,
expected):
formatter = LongitudeFormatter(**kwargs)
p = ccrs.PlateCarree(central_longitude=central_longitude)
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-180, -120, -60, 0, 60, 120, 180]
result = [formatter(tick) for tick in test_ticks]
assert result == expected
Expand All @@ -126,7 +125,7 @@ def test_LongitudeFormatter_degree_symbol():
formatter = LongitudeFormatter(degree_symbol='',
dateline_direction_label=True)
p = ccrs.PlateCarree()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-180, -120, -60, 0, 60, 120, 180]
result = [formatter(tick) for tick in test_ticks]
expected = ['180W', '120W', '60W', '0', '60E', '120E', '180E']
Expand All @@ -137,7 +136,7 @@ def test_LongitudeFormatter_number_format():
formatter = LongitudeFormatter(number_format='.2f', dms=False,
dateline_direction_label=True)
p = ccrs.PlateCarree()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-180, -120, -60, 0, 60, 120, 180]
result = [formatter(tick) for tick in test_ticks]
expected = ['180.00°W', '120.00°W', '60.00°W', '0.00°',
Expand All @@ -148,7 +147,7 @@ def test_LongitudeFormatter_number_format():
def test_LongitudeFormatter_mercator():
formatter = LongitudeFormatter(dateline_direction_label=True)
p = ccrs.Mercator()
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-20037508.342783064, -13358338.895188706,
-6679169.447594353, 0.0, 6679169.447594353,
13358338.895188706, 20037508.342783064]
Expand All @@ -166,7 +165,7 @@ def test_LongitudeFormatter_small_numbers(central_longitude,
formatter = LongitudeFormatter(number_format='.7f', dms=False,
zero_direction_label=zero_direction_label)
p = ccrs.PlateCarree(central_longitude=central_longitude)
formatter.axis = Mock(axes=Mock(GeoAxes, projection=p))
formatter.set_axis(Mock(axes=Mock(GeoAxes, projection=p)))
test_ticks = [-17.1142343, -17.1142340, -17.1142337]
result = [formatter(tick) for tick in test_ticks]
assert result == expected
Expand Down

0 comments on commit a6c3419

Please sign in to comment.