[Git][debian-gis-team/pyresample][upstream] New upstream version 1.22.1
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Sat Nov 27 13:46:18 GMT 2021
Antonio Valentino pushed to branch upstream at Debian GIS Project / pyresample
Commits:
b3754474 by Antonio Valentino at 2021-11-27T13:34:03+00:00
New upstream version 1.22.1
- - - - -
19 changed files:
- .github/workflows/ci.yaml
- .pre-commit-config.yaml
- CHANGELOG.md
- docs/source/geometry_utils.rst
- pyresample/_spatial_mp.py
- pyresample/bilinear/xarr.py
- pyresample/bucket/__init__.py
- pyresample/ewa/_fornav_templates.cpp
- pyresample/ewa/dask_ewa.py
- pyresample/future/resamplers/nearest.py
- pyresample/geometry.py
- pyresample/kd_tree.py
- pyresample/spherical.py
- pyresample/test/test_ewa_fornav.py
- pyresample/test/test_geometry.py
- pyresample/test/test_kd_tree.py
- pyresample/test/utils.py
- pyresample/version.py
- setup.py
Changes:
=====================================
.github/workflows/ci.yaml
=====================================
@@ -41,10 +41,10 @@ jobs:
fail-fast: true
matrix:
os: ["windows-latest", "ubuntu-latest", "macos-latest"]
- python-version: ["3.7", "3.8"]
+ python-version: ["3.7", "3.8", "3.9"]
experimental: [false]
include:
- - python-version: "3.8"
+ - python-version: "3.9"
os: "ubuntu-latest"
experimental: true
=====================================
.pre-commit-config.yaml
=====================================
@@ -7,7 +7,7 @@ repos:
- id: flake8
additional_dependencies: [flake8-docstrings, flake8-debugger, flake8-bugbear]
- repo: https://github.com/pycqa/isort
- rev: 5.9.3
+ rev: 5.10.1
hooks:
- id: isort
language_version: python3
=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,30 @@
+## Version 1.22.1 (2021/11/18)
+
+### Issues Closed
+
+* [Issue 390](https://github.com/pytroll/pyresample/issues/390) - What units does SphPolygon.area return?
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 398](https://github.com/pytroll/pyresample/pull/398) - Fix EWA resampling when input data is larger than the output area
+* [PR 389](https://github.com/pytroll/pyresample/pull/389) - Fix SwathDefinition get_bbox_lonlats returning counter-clockwise coordinates
+
+#### Features added
+
+* [PR 396](https://github.com/pytroll/pyresample/pull/396) - Add Python 3.9 to CI runs and use it for the experimental run
+* [PR 395](https://github.com/pytroll/pyresample/pull/395) - Replace depracated Numpy dtypes
+
+#### Documentation changes
+
+* [PR 388](https://github.com/pytroll/pyresample/pull/388) - Fix indentation on geometry utils page
+
+In this release 5 pull requests were closed.
+
+
## Version 1.22.0 (2021/10/25)
### Issues Closed
=====================================
docs/source/geometry_utils.rst
=====================================
@@ -30,9 +30,9 @@ and optional arguments:
* **description**: Human-readable description. If not provided, defaults to **area_id**
* **proj_id**: ID of projection (deprecated)
* **units**: Units that provided arguments should be interpreted as. This can be
- one of 'deg', 'degrees', 'meters', 'metres', and any parameter supported by the
- `cs2cs -lu <https://proj4.org/apps/cs2cs.html#cmdoption-cs2cs-lu>`_
- command. Units are determined in the following priority:
+ one of 'deg', 'degrees', 'meters', 'metres', and any parameter supported by the
+ `cs2cs -lu <https://proj4.org/apps/cs2cs.html#cmdoption-cs2cs-lu>`_
+ command. Units are determined in the following priority:
1. units expressed with each variable through a DataArray's attrs attribute.
2. units passed to ``units``
=====================================
pyresample/_spatial_mp.py
=====================================
@@ -179,7 +179,7 @@ class Cartesian(object):
def transform_lonlats(self, lons, lats):
"""Transform longitudes and latitues to cartesian coordinates."""
if np.issubdtype(lons.dtype, np.integer):
- lons = lons.astype(np.float)
+ lons = lons.astype(np.float64)
coords = np.zeros((lons.size, 3), dtype=lons.dtype)
if ne:
deg2rad = np.pi / 180 # noqa: F841
=====================================
pyresample/bilinear/xarr.py
=====================================
@@ -171,7 +171,7 @@ class XArrayBilinearResampler(BilinearBase):
self._radius_of_influence)
input_coords = lonlat2xyz(source_lons, source_lats)
valid_input_index = np.ravel(valid_input_index)
- input_coords = input_coords[valid_input_index, :].astype(np.float)
+ input_coords = input_coords[valid_input_index, :].astype(np.float64)
return da.compute(valid_input_index, input_coords)
=====================================
pyresample/bucket/__init__.py
=====================================
@@ -126,8 +126,8 @@ class BucketResampler(object):
# Calculate array indices. Orient so that 0-meridian is pointing down.
adef = self.target_area
x_res, y_res = adef.resolution
- x_idxs = da.floor((proj_x - adef.area_extent[0]) / x_res).astype(np.int)
- y_idxs = da.floor((adef.area_extent[3] - proj_y) / y_res).astype(np.int)
+ x_idxs = da.floor((proj_x - adef.area_extent[0]) / x_res).astype(np.int64)
+ y_idxs = da.floor((adef.area_extent[3] - proj_y) / y_res).astype(np.int64)
# Get valid index locations
mask = (x_idxs >= 0) & (x_idxs < adef.width) & (y_idxs >= 0) & (y_idxs < adef.height)
@@ -230,7 +230,7 @@ class BucketResampler(object):
# fill missed index
statistics = (statistics + pd.Series(np.zeros(out_size))).fillna(0)
- counts = self.get_sum(np.logical_not(np.isnan(data)).astype(int)).ravel()
+ counts = self.get_sum(np.logical_not(np.isnan(data)).astype(np.int64)).ravel()
# TODO remove following line in favour of weights = data when dask histogram bug (issue #6935) is fixed
statistics = self._mask_bins_with_nan_if_not_skipna(skipna, data, out_size, statistics)
@@ -346,7 +346,7 @@ class BucketResampler(object):
data = da.where(data == fill_value, np.nan, data)
sums = self.get_sum(data, skipna=skipna)
- counts = self.get_sum(np.logical_not(np.isnan(data)).astype(int))
+ counts = self.get_sum(np.logical_not(np.isnan(data)).astype(np.int64))
average = sums / da.where(counts == 0, np.nan, counts)
average = da.where(np.isnan(average), fill_value, average)
=====================================
pyresample/ewa/_fornav_templates.cpp
=====================================
@@ -245,7 +245,7 @@ int compute_ewa(size_t chan_count, int maximum_weight_mode,
u0 = uimg[swath_offset];
v0 = vimg[swath_offset];
- if (u0 < 0.0 || v0 < 0.0 || __isnan(u0) || __isnan(v0)) {
+ if (u0 < -this_ewap->u_del || v0 < -this_ewap->v_del || __isnan(u0) || __isnan(v0)) {
continue;
}
@@ -352,7 +352,6 @@ int compute_ewa_single(int maximum_weight_mode,
IMAGE_TYPE this_val;
unsigned int swath_offset;
unsigned int grid_offset;
- size_t chan;
got_point = 0;
for (row = 0, swath_offset=0; row < swath_rows; row+=1) {
@@ -360,7 +359,7 @@ int compute_ewa_single(int maximum_weight_mode,
u0 = uimg[swath_offset];
v0 = vimg[swath_offset];
- if (u0 < 0.0 || v0 < 0.0 || __isnan(u0) || __isnan(v0)) {
+ if (u0 < -this_ewap->u_del || v0 < -this_ewap->v_del || __isnan(u0) || __isnan(v0)) {
continue;
}
=====================================
pyresample/ewa/dask_ewa.py
=====================================
@@ -351,8 +351,8 @@ class DaskEWAResampler(BaseResampler):
ll2cr_blocks = self.cache['ll2cr_blocks'].items()
ll2cr_numblocks = ll2cr_result.shape if isinstance(ll2cr_result, np.ndarray) else ll2cr_result.numblocks
fornav_task_name = "fornav-{}".format(data.name)
- maximum_weight_mode = kwargs.get('maximum_weight_mode', False)
- weight_sum_min = kwargs.get('weight_sum_min', -1.0)
+ maximum_weight_mode = kwargs.setdefault('maximum_weight_mode', False)
+ weight_sum_min = kwargs.setdefault('weight_sum_min', -1.0)
output_stack = self._generate_fornav_dask_tasks(out_chunks,
ll2cr_blocks,
fornav_task_name,
=====================================
pyresample/future/resamplers/nearest.py
=====================================
@@ -192,7 +192,7 @@ class KDTreeNearestXarrayResampler(Resampler):
query_no_distance, 'jik', tlons, 'ji', tlats, 'ji',
valid_output_index, 'ji', *args, kdtree=resample_kdtree,
neighbours=neighbors, epsilon=epsilon,
- radius=radius_of_influence, dtype=np.int,
+ radius=radius_of_influence, dtype=np.int64,
new_axes={'k': neighbors}, concatenate=True)
return res
=====================================
pyresample/geometry.py
=====================================
@@ -269,9 +269,19 @@ class BaseDefinition:
return (SimpleBoundary(s1_lon.squeeze(), s2_lon.squeeze(), s3_lon.squeeze(), s4_lon.squeeze()),
SimpleBoundary(s1_lat.squeeze(), s2_lat.squeeze(), s3_lat.squeeze(), s4_lat.squeeze()))
- def get_bbox_lonlats(self) -> tuple:
+ def get_bbox_lonlats(self, force_clockwise: bool = True) -> tuple:
"""Return the bounding box lons and lats.
+ Args:
+ force_clockwise:
+ Perform minimal checks and reordering of coordinates to ensure
+ that the returned coordinates follow a clockwise direction.
+ This is important for compatibility with
+ :class:`pyresample.spherical.SphPolygon` where operations depend
+ on knowing the inside versus the outside of a polygon. These
+ operations assume that coordinates are clockwise.
+ Default is True.
+
Returns:
Two lists of four elements each. The first list is longitude
coordinates, the second latitude. Each element is a numpy array
@@ -282,9 +292,9 @@ class BaseDefinition:
in the coordinates starting in the north-west corner. In the case
where the data is oriented with the first pixel (row 0, column 0)
in the south-east corner, the coordinates will start in that
- corner. Other orientations of data are not currently supported by
- this method and will result in the coordinates not following a
- clockwise path which may be incompatible with other parts of
+ corner. Other orientations that are detected to follow a
+ counter-clockwise path will be reordered to provide a
+ clockwise path in order to be compatible with other parts of
pyresample (ex. :class:`pyresample.spherical.SphPolygon`).
"""
@@ -298,8 +308,48 @@ class BaseDefinition:
(s4_lon.squeeze(), s4_lat.squeeze())])
if hasattr(lons[0], 'compute') and da is not None:
lons, lats = da.compute(lons, lats)
+ if force_clockwise and not self._corner_is_clockwise(
+ lons[0][-2], lats[0][-2], lons[0][-1], lats[0][-1], lons[1][1], lats[1][1]):
+ # going counter-clockwise
+ lons, lats = self._reverse_boundaries(lons, lats)
+ return lons, lats
+
+ @staticmethod
+ def _reverse_boundaries(sides_lons: list, sides_lats: list) -> tuple:
+ """Reverse the order of the lists and the arrays in those lists.
+
+ Given lists of 4 numpy arrays, this will reverse the order of the
+ arrays in that list and the elements of each of those arrays. This
+ has the end result when the coordinates are counter-clockwise of
+ reversing the coordinates to make them clockwise.
+
+ """
+ lons = [lon[::-1] for lon in sides_lons[::-1]]
+ lats = [lat[::-1] for lat in sides_lats[::-1]]
return lons, lats
+ @staticmethod
+ def _corner_is_clockwise(lon1, lat1, corner_lon, corner_lat, lon2, lat2):
+ """Determine if coordinates follow a clockwise path.
+
+ This uses :class:`pyresample.spherical.Arc` to determine the angle
+ between the first line segment (Arc) from (lon1, lat1) to
+ (corner_lon, corner_lat) and the second line segment from
+ (corner_lon, corner_lat) to (lon2, lat2). A straight line would
+ produce an angle of 0, a clockwise path would have a negative angle,
+ and a counter-clockwise path would have a positive angle.
+
+ """
+ from pyresample.spherical import Arc, SCoordinate
+ point1 = SCoordinate(math.radians(lon1), math.radians(lat1))
+ point2 = SCoordinate(math.radians(corner_lon), math.radians(corner_lat))
+ point3 = SCoordinate(math.radians(lon2), math.radians(lat2))
+ arc1 = Arc(point1, point2)
+ arc2 = Arc(point2, point3)
+ angle = arc1.angle(arc2)
+ is_clockwise = -np.pi < angle < 0
+ return is_clockwise
+
def get_cartesian_coords(self, nprocs=None, data_slice=None, cache=False):
"""Retrieve cartesian coordinates of geometry definition.
@@ -760,7 +810,7 @@ class SwathDefinition(CoordinateDefinition):
def get_edge_lonlats(self):
"""Get the concatenated boundary of the current swath."""
- lons, lats = self.get_bbox_lonlats()
+ lons, lats = self.get_bbox_lonlats(force_clockwise=False)
blons = np.ma.concatenate(lons)
blats = np.ma.concatenate(lats)
return blons, blats
@@ -1050,8 +1100,10 @@ class DynamicAreaDefinition(object):
y_is_pole = (ymax >= 90 - epsilon) or (ymin <= -90 + epsilon)
if crs.is_geographic and x_passes_antimeridian and not y_is_pole:
# cross anti-meridian of projection
- xmin = np.nanmin(xarr[xarr >= 0])
- xmax = np.nanmax(xarr[xarr < 0]) + 360
+ xarr_pos = da.where(xarr >= 0, xarr, np.nan)
+ xarr_neg = da.where(xarr < 0, xarr, np.nan)
+ xmin = np.nanmin(xarr_pos)
+ xmax = np.nanmax(xarr_neg) + 360
xmin, xmax = da.compute(xmin, xmax)
return xmin, ymin, xmax, ymax
=====================================
pyresample/kd_tree.py
=====================================
@@ -965,7 +965,7 @@ class XArrayResamplerNN(object):
res = blockwise(query_no_distance, 'jik', tlons, 'ji', tlats, 'ji',
valid_oi, 'ji', *args, kdtree=resample_kdtree,
neighbours=self.neighbours, epsilon=self.epsilon,
- radius=self.radius_of_influence, dtype=np.int,
+ radius=self.radius_of_influence, dtype=np.int64,
new_axes={'k': self.neighbours}, concatenate=True)
return res, None
=====================================
pyresample/spherical.py
=====================================
@@ -28,7 +28,11 @@ logger = logging.getLogger(__name__)
class SCoordinate(object):
- """Spherical coordinates."""
+ """Spherical coordinates.
+
+ The ``lon`` and ``lat`` coordinates should be provided in radians.
+
+ """
def __init__(self, lon, lat):
self.lon = lon
@@ -200,7 +204,13 @@ class Arc(object):
return str(self.start) + " -> " + str(self.end)
def angle(self, other_arc):
- """Oriented angle between two arcs."""
+ """Oriented angle between two arcs.
+
+ Returns:
+ Angle in radians. A straight line will be 0. A clockwise path
+ will be a negative angle and counter-clockwise will be positive.
+
+ """
if self.start == other_arc.start:
a__ = self.start
b__ = self.end
@@ -321,15 +331,30 @@ class Arc(object):
return None, None
-class SphPolygon(object):
+class SphPolygon:
"""Spherical polygon.
- Vertices as a 2-column array of (col 1) lons and (col 2) lats is
- given in radians. The inside of the polygon is defined by the
- vertices being defined clockwise around it.
+ Represents a polygon on a spherical geoid. Initialise with
+ an ndarray of shape ``[N, 2]`` where the first column contains longitudes
+ and the second column contains latitudes. The units should be in radians.
+ The inside of the polygon is defined by the vertices being defined clockwise
+ around it.
+
+ The optional second argument ``radius`` indicates the radius of the
+ spherical geoid on which calculations occur.
+
"""
def __init__(self, vertices, radius=1):
+ """Initialise SphPolygon object.
+
+ Args:
+ vertices (np.ndarray): ndarray of shape ``[N, 2]`` with ``N``
+ points describing a polygon clockwise. First column
+ describes longitudes, second column describes latitudes. Units
+ should be in radians.
+ radius (optional, number): Radius of spherical planet.
+ """
self.vertices = vertices.astype(np.float64, copy=False)
self.lon = self.vertices[:, 0]
self.lat = self.vertices[:, 1]
@@ -381,6 +406,19 @@ class SphPolygon(object):
Note: The article mixes up longitudes and latitudes in equation 3! Look
at the fortran code appendix for the correct version.
+
+ The units are the square of the radius passed to the constructor. For
+ example, to calculate the area in km² of a polygon near the equator of a
+ spherical planet with a radius of 6371 km (similar to Earth):
+
+ >>> pol = SphPolygon(np.deg2rad(np.array([[0., 0.], [0., 1.], [1., 1.], [1., 0.]])),
+ radius=6371)
+ >>> print(pol.area())
+ 12363.997753690213
+
+ If `SphPolygon` was constructed without passing any units, the result
+ has units of square radii (i.e., the polygon containing the entire
+ planet would have area 4π).
"""
phi_a = self.lat
phi_p = self.lat.take(np.arange(len(self.lat)) + 1, mode="wrap")
=====================================
pyresample/test/test_ewa_fornav.py
=====================================
@@ -54,6 +54,28 @@ class TestFornav(unittest.TestCase):
self.assertTrue(((out == 1) | np.isnan(out)).all(),
msg="Unexpected interpolation values were returned")
+ def test_fornav_swath_wide_input(self):
+ """Test that a swath with large input pixels on the left edge of the output."""
+ from pyresample.ewa import _fornav
+ swath_shape = (400, 800)
+ data_type = np.float32
+ # Create a fake row and cols array
+ rows = np.empty(swath_shape, dtype=np.float32)
+ rows[:] = np.linspace(-500, 500, 400)[:, None]
+ cols = np.empty(swath_shape, dtype=np.float32)
+ cols[:] = np.linspace(-500, 500, 800) + 0.5
+ rows_per_scan = 16
+ # Create a fake data swath
+ data = np.ones(swath_shape, dtype=data_type)
+ out = np.empty((800, 1000), dtype=data_type)
+
+ grid_points_covered = _fornav.fornav_wrapper(cols, rows, (data,), (out,),
+ np.nan, np.nan, rows_per_scan)
+ one_grid_points_covered = grid_points_covered[0]
+ # the upper-left 500x500 square should be filled with 1s at least
+ assert 500 * 500 <= one_grid_points_covered <= 505 * 505
+ np.testing.assert_allclose(out[:500, :500], 1)
+
def test_fornav_swath_smaller(self):
"""Test that a swath smaller than the output grid is entirely used."""
from pyresample.ewa import _fornav
=====================================
pyresample/test/test_geometry.py
=====================================
@@ -2362,8 +2362,8 @@ class TestDynamicAreaDefinition:
# if we aren't at a pole then we adjust the coordinates
# that takes a total of 2 computations
num_computes = 1 if is_pole else 2
- lons = da.from_array(lons)
- lats = da.from_array(lats)
+ lons = da.from_array(lons, chunks=2)
+ lats = da.from_array(lats, chunks=2)
with dask.config.set(scheduler=CustomScheduler(num_computes)):
result = area.freeze((lons, lats),
resolution=0.0056)
@@ -2790,18 +2790,20 @@ class TestBboxLonlats:
"""Test 'get_bbox_lonlats' for various geometry cases."""
@pytest.mark.parametrize(
- ("lon_start", "lon_stop", "lat_start", "lat_stop", "exp_clockwise"),
+ ("lon_start", "lon_stop", "lat_start", "lat_stop", "exp_nonforced_clockwise"),
[
- (3.0, 12.0, 75.0, 26.0, True),
- (12.0, 3.0, 75.0, 26.0, False),
- (3.0, 12.0, 26.0, 75.0, False),
- (12.0, 3.0, 26.0, 75.0, True),
+ (3.0, 12.0, 75.0, 26.0, True), # [0, 0] at north-west corner
+ (12.0, 3.0, 75.0, 26.0, False), # [0, 0] at north-east corner
+ (3.0, 12.0, 26.0, 75.0, False), # [0, 0] at south-west corner
+ (12.0, 3.0, 26.0, 75.0, True), # [0, 0] at south-east corner
]
)
+ @pytest.mark.parametrize("force_clockwise", [False, True])
@pytest.mark.parametrize("use_dask", [False, True])
@pytest.mark.parametrize("use_xarray", [False, True])
def test_swath_def_bbox(self, lon_start, lon_stop,
- lat_start, lat_stop, exp_clockwise,
+ lat_start, lat_stop, exp_nonforced_clockwise,
+ force_clockwise,
use_dask, use_xarray):
from pyresample.geometry import SwathDefinition
@@ -2818,7 +2820,7 @@ class TestBboxLonlats:
lats = xr.DataArray(lats, dims=('y', 'x'))
swath_def = SwathDefinition(lons, lats)
- bbox_lons, bbox_lats = swath_def.get_bbox_lonlats()
+ bbox_lons, bbox_lats = swath_def.get_bbox_lonlats(force_clockwise=force_clockwise)
assert len(bbox_lons) == len(bbox_lats)
assert len(bbox_lons) == 4
for side_lons, side_lats in zip(bbox_lons, bbox_lats):
@@ -2826,7 +2828,10 @@ class TestBboxLonlats:
assert isinstance(side_lats, np.ndarray)
assert side_lons.shape == side_lats.shape
is_cw = _is_clockwise(np.concatenate(bbox_lons), np.concatenate(bbox_lats))
- assert is_cw if exp_clockwise else not is_cw
+ if exp_nonforced_clockwise or force_clockwise:
+ assert is_cw
+ else:
+ assert not is_cw
def _is_clockwise(lons, lats):
=====================================
pyresample/test/test_kd_tree.py
=====================================
@@ -128,7 +128,7 @@ class Test(unittest.TestCase):
data = np.fromfunction(lambda y, x: y * x, (50, 10))
lons = np.fromfunction(lambda y, x: 3 + x, (50, 10))
lats = np.fromfunction(lambda y, x: 75 - y, (50, 10))
- mask = np.ones_like(lons, dtype=np.bool)
+ mask = np.ones_like(lons, dtype=np.bool_)
mask[::2, ::2] = False
swath_def = geometry.SwathDefinition(
lons=np.ma.masked_array(lons, mask=mask),
=====================================
pyresample/test/utils.py
=====================================
@@ -166,7 +166,8 @@ class catch_warnings(warnings.catch_warnings):
def create_test_longitude(start, stop, shape, twist_factor=0.0, dtype=np.float32):
"""Get basic sample of longitude data."""
- if start > stop:
+ if start > 0 > stop:
+ # cross anti-meridian
stop += 360.0
num_cols = 1 if len(shape) < 2 else shape[1]
=====================================
pyresample/version.py
=====================================
@@ -23,9 +23,9 @@ def get_keywords():
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
- git_refnames = " (HEAD -> main, tag: v1.22.0)"
- git_full = "f9809a303377f3c9e224b05e02c0339d894e291a"
- git_date = "2021-10-25 10:35:35 -0500"
+ git_refnames = " (HEAD -> main, tag: v1.22.1)"
+ git_full = "e6a452ece4e5c4f7609136a20795cf3f0cef388a"
+ git_date = "2021-11-18 13:32:40 -0600"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
=====================================
setup.py
=====================================
@@ -46,7 +46,8 @@ extras_require = {'numexpr': ['numexpr'],
'tests': test_requires}
setup_requires = ['numpy>=1.10.0', 'cython']
-test_requires = ['rasterio', 'dask', 'xarray', 'cartopy>=0.20.0', 'pillow', 'matplotlib', 'scipy', 'zarr']
+test_requires = ['rasterio', 'dask', 'xarray', 'cartopy>=0.20.0', 'pillow', 'matplotlib', 'scipy', 'zarr',
+ 'pytest-lazy-fixture']
if sys.platform.startswith("win"):
extra_compile_args = []
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyresample/-/commit/b3754474ccaea05e2af59e6960985fb81ace01a2
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyresample/-/commit/b3754474ccaea05e2af59e6960985fb81ace01a2
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20211127/84b4dc6c/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list