From db45aaa9eb272ecc29bf31421dfad287216992bc Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Wed, 23 Oct 2024 11:21:12 -0600 Subject: [PATCH] ENH: Add HEALPix and rHEALPix projections --- docs/make_projection.py | 3 +- lib/cartopy/crs.py | 109 ++++++++++++++++++ lib/cartopy/tests/crs/test_healpix.py | 22 ++++ lib/cartopy/tests/crs/test_rhealpix.py | 36 ++++++ .../test_global_map_HEALPix.png | Bin 0 -> 9844 bytes .../test_global_map_RHEALPix.png | Bin 0 -> 8657 bytes lib/cartopy/tests/mpl/test_mpl_integration.py | 2 + 7 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 lib/cartopy/tests/crs/test_healpix.py create mode 100644 lib/cartopy/tests/crs/test_rhealpix.py create mode 100644 lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/test_global_map_HEALPix.png create mode 100644 lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/test_global_map_RHEALPix.png diff --git a/docs/make_projection.py b/docs/make_projection.py index 8f50fd559..b0aca511c 100644 --- a/docs/make_projection.py +++ b/docs/make_projection.py @@ -95,7 +95,8 @@ def utm_plot(): 'OSGB': 4, 'LambertZoneII': 4.1, 'EuroPP': 5, 'Geostationary': 6, 'NearsidePerspective': 7, 'EckertI': 8.1, 'EckertII': 8.2, 'EckertIII': 8.3, - 'EckertIV': 8.4, 'EckertV': 8.5, 'EckertVI': 8.6} + 'EckertIV': 8.4, 'EckertV': 8.5, 'EckertVI': 8.6, + 'HEALPix': 9.1, 'RHEALPix': 9.2} def find_projections(): diff --git a/lib/cartopy/crs.py b/lib/cartopy/crs.py index 600f8ac24..d3e4fbd7b 100644 --- a/lib/cartopy/crs.py +++ b/lib/cartopy/crs.py @@ -1832,6 +1832,115 @@ def __init__(self): self.bounds = [-5242.32, 1212512.16, 1589155.51, 2706796.21] +class HEALPix(Projection): + """ + Hierarchical Equal Area isoLatitude Pixelisation (HEALPix) of a 2-sphere is an + algorithm for pixelisation of the 2-sphere based on subdivision of a distorted + rhombic dodecahedron. The HEALPix projection is area preserving and can be used + with a spherical and ellipsoidal model. It was initially developed for mapping + cosmic background microwave radiation. + + """ + _wrappable = True + + def __init__(self, central_longitude=0, globe=None): + """ + Parameters + ---------- + central_longitude: optional + The true longitude of the central meridian in degrees. + Defaults to 0. + + globe: optional + An instance of :class:`cartopy.crs.Globe`. If omitted, a default + globe with a spherical radius of 6370997 meters is created. + """ + proj4_params = [('proj', 'healpix'), + ('lon_0', central_longitude)] + super().__init__(proj4_params, globe=globe) + # Defaults to the PROJ sphere with semimajor axis 6370997m + w = (self.globe.semimajor_axis or 6370997) * np.pi + h = w/2 + self.bounds = [-w, w, -h, h] + self._threshold = w / 1e4 + + self._boundary = sgeom.LinearRing( + [(-w, h/2), (-3/4*w, h), (-w/2, h/2), (-w/4, h), (0, h/2), + (w/4, h), (w/2, h/2), (3/4*w, h), (w, h/2), + (w, -h/2), (3/4*w, -h), (w/2, -h/2), (w/4, -h), (0, -h/2), + (-w/4, -h), (-w/2, -h/2), (-3/4*w, -h), (-w, -h/2), (-w, h/2)]) + + @property + def boundary(self): + return self._boundary + + +class RHEALPix(Projection): + """ + Also known as rHEALPix in PROJ, this projection is an extension of the + HEALPix projection to present rectangles, rather than triangles, at the + north and south poles. + + Parameters + ---------- + central_longitude: optional + The true longitude of the central meridian in degrees. + Defaults to 0. + north_square : int + The position for the north pole square. Must be one of 0, 1, 2 or 3. + 0 would have the north pole square aligned with the left-most square, + and 3 would be aligned with the right-most. + south_square : int + The position for the south pole square. Must be one of 0, 1, 2 or 3. + """ + _wrappable = True + + def __init__(self, central_longitude=0, north_square=0, south_square=0, globe=None): + valid_square_pos = [0, 1, 2, 3] + if north_square not in valid_square_pos: + raise ValueError(f'north_square must be one of {valid_square_pos}') + if south_square not in valid_square_pos: + raise ValueError(f'south_square must be one of {valid_square_pos}') + + proj4_params = [('proj', 'rhealpix'), + ('north_square', north_square), + ('south_square', south_square), + ('lon_0', central_longitude)] + super().__init__(proj4_params) + + # Defaults to the PROJ sphere with semimajor axis 6370997m + w = (self.globe.semimajor_axis or 6370997) * np.pi + h = 3/4 * w + box_h = w / 4 + self.bounds = [-w, w, -h, h] + self._threshold = w / 1e4 + + self._boundary = sgeom.LinearRing([ + # Left edge + (-w, box_h), + # Cut-out for the north square + (-w + north_square * w/2, box_h), + (-w + north_square * w/2, h), + (-w + (north_square + 1) * w/2, h), + (-w + (north_square + 1) * w/2, box_h), + # Right edge + (w, box_h), + (w, -box_h), + # Cut-out for the south square + (-w + (south_square + 1) * w/2, -box_h), + (-w + (south_square + 1) * w/2, -h), + (-w + south_square * w/2, -h), + (-w + south_square * w/2, -box_h), + # Left edge + (-w, -box_h), + (-w, box_h) + ]) + + @property + def boundary(self): + return self._boundary + + class LambertAzimuthalEqualArea(Projection): """ A Lambert Azimuthal Equal-Area projection. diff --git a/lib/cartopy/tests/crs/test_healpix.py b/lib/cartopy/tests/crs/test_healpix.py new file mode 100644 index 000000000..01c1ebb0d --- /dev/null +++ b/lib/cartopy/tests/crs/test_healpix.py @@ -0,0 +1,22 @@ +# Copyright Crown and Cartopy Contributors +# +# This file is part of Cartopy and is released under the BSD 3-clause license. +# See LICENSE in the root of the repository for full licensing details. +""" +Tests for the HEALPix projection. + +""" +import cartopy.crs as ccrs +from .helpers import check_proj_params + + +def test_defaults(): + crs = ccrs.HEALPix() + expected = {'ellps=WGS84', 'lon_0=0'} + check_proj_params('healpix', crs, expected) + + +def test_central_longitude(): + crs = ccrs.HEALPix(central_longitude=124.8) + expected = {'ellps=WGS84', 'lon_0=124.8'} + check_proj_params('healpix', crs, expected) diff --git a/lib/cartopy/tests/crs/test_rhealpix.py b/lib/cartopy/tests/crs/test_rhealpix.py new file mode 100644 index 000000000..552488ddc --- /dev/null +++ b/lib/cartopy/tests/crs/test_rhealpix.py @@ -0,0 +1,36 @@ +# Copyright Crown and Cartopy Contributors +# +# This file is part of Cartopy and is released under the BSD 3-clause license. +# See LICENSE in the root of the repository for full licensing details. +""" +Tests for the RHEALPix projection. + +""" +import pytest + +import cartopy.crs as ccrs +from .helpers import check_proj_params + + +def test_defaults(): + crs = ccrs.RHEALPix() + expected = {'ellps=WGS84', 'lon_0=0', 'north_square=0', 'south_square=0'} + check_proj_params('rhealpix', crs, expected) + + +def test_square_positions(): + crs = ccrs.RHEALPix(north_square=1, south_square=2) + expected = {'ellps=WGS84', 'lon_0=0', 'north_square=1', 'south_square=2'} + check_proj_params('rhealpix', crs, expected) + +def test_invalid_square_positions(): + with pytest.raises(ValueError, match='north_square must be'): + ccrs.RHEALPix(north_square=4) + with pytest.raises(ValueError, match='south_square must be'): + ccrs.RHEALPix(south_square=-1) + +def test_central_longitude(): + crs = ccrs.RHEALPix(north_square=2, central_longitude=-124.8) + expected = {'ellps=WGS84', 'lon_0=-124.8', 'north_square=2', + 'south_square=0'} + check_proj_params('rhealpix', crs, expected) diff --git a/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/test_global_map_HEALPix.png b/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/test_global_map_HEALPix.png new file mode 100644 index 0000000000000000000000000000000000000000..273f4d6ab9be62a858a94b44e6f6a4149e02499d GIT binary patch literal 9844 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 literal 0 HcmV?d00001 diff --git a/lib/cartopy/tests/mpl/test_mpl_integration.py b/lib/cartopy/tests/mpl/test_mpl_integration.py index 6b528b982..6b0f0a9b9 100644 --- a/lib/cartopy/tests/mpl/test_mpl_integration.py +++ b/lib/cartopy/tests/mpl/test_mpl_integration.py @@ -169,6 +169,7 @@ def test_simple_global(): ccrs.EqualEarth, ccrs.Gnomonic, ccrs.Hammer, + ccrs.HEALPix, pytest.param((ccrs.InterruptedGoodeHomolosine, dict(emphasis='land')), id='InterruptedGoodeHomolosine'), ccrs.LambertCylindrical, @@ -185,6 +186,7 @@ def test_simple_global(): pytest.param((ccrs.RotatedPole, dict(pole_latitude=45, pole_longitude=180)), id='RotatedPole'), + ccrs.RHEALPix, ccrs.Stereographic, ccrs.SouthPolarStereo, pytest.param((ccrs.TransverseMercator, dict(approx=True)),