-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor for boundary lat/lon extraction #546
base: main
Are you sure you want to change the base?
Changes from 39 commits
e203899
95decd3
ae730f7
edcfcb4
eae1873
1662d2d
655fb72
b7df9be
a5322a6
88065d7
0e992a2
8eff9b4
36795df
1dca712
3c60e74
bba3bde
34edc5c
8fbf02f
d9f4165
83ec6df
9de0a44
efd0773
0b669f8
cea363f
b9cda35
d41973a
c6a9378
6323814
6210bea
28b5527
0eb7a6b
0f1b578
059d496
d35f1b5
b3273b8
e3f4b24
915e34f
61240eb
7069197
e638557
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,8 +56,13 @@ | |
|
||
from .version import get_versions # noqa | ||
|
||
__all__ = ['grid', 'image', 'kd_tree', 'utils', 'plot', 'geo_filter', 'geometry', 'CHUNK_SIZE', | ||
'load_area', 'create_area_def', 'get_area_def', 'parse_area_file', 'convert_def_to_yaml'] | ||
_root_path = os.path.dirname(os.path.realpath(__file__)) | ||
|
||
__all__ = [ | ||
'grid', 'image', 'kd_tree', 'utils', 'plot', 'geo_filter', 'geometry', 'CHUNK_SIZE', | ||
'load_area', 'create_area_def', 'get_area_def', 'parse_area_file', 'convert_def_to_yaml', | ||
"_root_path", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this listed in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad. I didn't know the purpose of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On this topic, |
||
] | ||
|
||
__version__ = get_versions()['version'] | ||
del get_versions |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,7 @@ | |
"features": { | ||
"future_geometries": False, | ||
}, | ||
"force_boundary_computations": False, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we keep this it will need documentation, but that can be the last thing in this PR. |
||
}], | ||
paths=_CONFIG_PATHS, | ||
) |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -599,14 +599,14 @@ def get_valid_indices_from_lonlat_boundaries( | |||
target_geo_def, source_lons, source_lats, radius_of_influence): | ||||
"""Get valid indices from lonlat boundaries.""" | ||||
# Resampling from swath to grid or from grid to grid | ||||
lonlat_boundary = target_geo_def.get_boundary_lonlats() | ||||
|
||||
# Combine reduced and legal values | ||||
return data_reduce.get_valid_index_from_lonlat_boundaries( | ||||
lonlat_boundary[0], | ||||
lonlat_boundary[1], | ||||
source_lons, source_lats, | ||||
radius_of_influence) | ||||
try: | ||||
sides_lons, sides_lats = target_geo_def.boundary().sides | ||||
valid_indices = data_reduce.get_valid_index_from_lonlat_boundaries(sides_lons, sides_lats, | ||||
source_lons, source_lats, | ||||
radius_of_influence) | ||||
except Exception: | ||||
valid_indices = np.ones(source_lons.size, dtype=bool) | ||||
Comment on lines
+607
to
+608
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing this was done to make tests pass? What's going on here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No @djhoese. This try-except is used to deal with out of Earth disk projections !!! pyresample/pyresample/geometry.py Line 276 in 294e6ea
So for the geostationary full disk and other projections with out-of-earth disk at the border, the boundary sides were all Inf . And inside the .get_valid_index_from_lonlat_boundaries if there was any invalid side coordinate, it was returning an array of ones (no data reduction done !). This means that for full disc GEO projections we were not doing data reduction !!!
In this PR, I replaced
|
||||
return valid_indices | ||||
|
||||
|
||||
def get_slicer(data): | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (c) 2014-2021 Pyresample developers | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
"""The Boundary classes.""" | ||
|
||
from pyresample.boundary.area_boundary import AreaBoundary, AreaDefBoundary | ||
from pyresample.boundary.planar_boundary import PlanarBoundary | ||
from pyresample.boundary.simple_boundary import SimpleBoundary | ||
from pyresample.boundary.spherical_boundary import SphericalBoundary | ||
|
||
__all__ = [ | ||
"SphericalBoundary", | ||
"PlanarBoundary", | ||
# Deprecated | ||
"SimpleBoundary", | ||
"AreaBoundary", | ||
"AreaDefBoundary", | ||
] | ||
Comment on lines
+25
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (c) 2014-2023 Pyresample developers | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
"""Define the BaseBoundary class.""" | ||
|
||
import logging | ||
|
||
import numpy as np | ||
|
||
from pyresample.boundary.sides import BoundarySides | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class BaseBoundary: | ||
"""Base class for boundary objects.""" | ||
__slots__ = ["_sides_x", "_sides_y"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you motivate the use of |
||
|
||
def __init__(self, area, vertices_per_side=None): | ||
|
||
sides_x, sides_y = self._compute_boundary_sides(area, vertices_per_side) | ||
self._sides_x = BoundarySides(sides_x) | ||
self._sides_y = BoundarySides(sides_y) | ||
self._area = area | ||
|
||
self.is_clockwise = self._check_is_boundary_clockwise(sides_x, sides_y, area) | ||
self.is_counterclockwise = not self.is_clockwise | ||
self._set_order(order=None) # FIX ! | ||
|
||
def _check_is_boundary_clockwise(self, sides_x, sides_y, area): | ||
"""Check if the boundary is clockwise or counterclockwise.""" | ||
raise NotImplementedError() | ||
|
||
def _compute_boundary_sides(self, area, vertices_per_side): | ||
"""Compute boundary sides.""" | ||
raise NotImplementedError() | ||
Comment on lines
+48
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like the base class should be abstract, right? |
||
|
||
def _set_order(self, order): | ||
"""Set the order of the boundary vertices.""" | ||
if self.is_clockwise: | ||
self._actual_order = "clockwise" | ||
else: | ||
self._actual_order = "counterclockwise" | ||
|
||
if order is None: | ||
self._wished_order = self._actual_order | ||
else: | ||
if order not in ["clockwise", "counterclockwise"]: | ||
raise ValueError("Valid 'order' is 'clockwise' or 'counterclockwise'") | ||
self._wished_order = order | ||
|
||
def set_clockwise(self): | ||
"""Set clockwise order for vertices retrieval.""" | ||
self._wished_order = "clockwise" | ||
return self | ||
|
||
def set_counterclockwise(self): | ||
"""Set counterclockwise order for vertices retrieval.""" | ||
self._wished_order = "counterclockwise" | ||
return self | ||
|
||
@property | ||
def sides(self): | ||
"""Return the boundary sides as a tuple of (sides_x, sides_y) arrays.""" | ||
return self._sides_x, self._sides_y | ||
|
||
@property | ||
def _x(self): | ||
"""Retrieve boundary x vertices.""" | ||
xs = self._sides_x.vertices | ||
if self._wished_order == self._actual_order: | ||
return xs | ||
else: | ||
return xs[::-1] | ||
|
||
@property | ||
def _y(self): | ||
"""Retrieve boundary y vertices.""" | ||
ys = self._sides_y.vertices | ||
if self._wished_order == self._actual_order: | ||
return ys | ||
else: | ||
return ys[::-1] | ||
|
||
@property | ||
def vertices(self): | ||
"""Return boundary vertices 2D array [x, y].""" | ||
vertices = np.vstack((self._x, self._y)).T | ||
vertices = vertices.astype(np.float64, copy=False) # Important for spherical ops. | ||
return vertices | ||
|
||
def contour(self, closed=False): | ||
"""Return the (x, y) tuple of the boundary object. | ||
|
||
If excludes the last element of each side because it's included in the next side. | ||
If closed=False (the default), the last vertex is not equal to the first vertex | ||
If closed=True, the last vertex is set to be equal to the first | ||
closed=True is required for shapely Polygon creation. | ||
closed=False is required for pyresample SPolygon creation. | ||
""" | ||
x = self._x | ||
y = self._y | ||
if closed: | ||
x = np.hstack((x, x[0])) | ||
y = np.hstack((y, y[0])) | ||
return x, y | ||
|
||
def to_shapely_polygon(self): | ||
"""Define a Shapely Polygon.""" | ||
from shapely.geometry import Polygon | ||
self = self.set_counterclockwise() | ||
x, y = self.contour(closed=True) | ||
return Polygon(zip(x, y)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (c) 2014-2023 Pyresample developers | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
"""Define the PlanarBoundary class.""" | ||
|
||
import logging | ||
|
||
import numpy as np | ||
|
||
from pyresample.boundary.base_boundary import BaseBoundary | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def _is_projection_boundary_clockwise(sides_x, sides_y): | ||
"""Determine if the boundary is clockwise-defined in planar coordinates.""" | ||
from shapely.geometry import Polygon | ||
x = np.concatenate([xs[:-1] for xs in sides_x]) | ||
y = np.concatenate([ys[:-1] for ys in sides_y]) | ||
x = np.hstack((x, x[0])) | ||
y = np.hstack((y, y[0])) | ||
polygon = Polygon(zip(x, y)) | ||
return not polygon.exterior.is_ccw | ||
|
||
|
||
class PlanarBoundary(BaseBoundary): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class is not sufficiently tested. |
||
"""Projection Boundary object. | ||
|
||
The inputs must be the x and y sides of the projection. | ||
It expects the projection coordinates to be planar (i.e. metric, radians). | ||
""" | ||
|
||
@classmethod | ||
def _check_is_boundary_clockwise(cls, sides_x, sides_y, area=None): | ||
"""SphericalBoundary specific implementation.""" | ||
return _is_projection_boundary_clockwise(sides_x=sides_x, sides_y=sides_y) | ||
Comment on lines
+47
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this version a classmethod? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method must be called within There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
@classmethod | ||
def _compute_boundary_sides(cls, area, vertices_per_side): | ||
sides_x, sides_y = area._get_projection_sides(vertices_per_side=vertices_per_side) | ||
return sides_x, sides_y | ||
Comment on lines
+52
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be a static method. |
||
|
||
def __init__(self, area, vertices_per_side=None): | ||
super().__init__(area=area, vertices_per_side=vertices_per_side) | ||
|
||
self.sides_x = self._sides_x | ||
self.sides_y = self._sides_y | ||
self.crs = self._area.crs | ||
self.cartopy_crs = self._area.to_cartopy_crs() | ||
Comment on lines
+57
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be at the top of the class |
||
|
||
@property | ||
def _point_inside(self): | ||
x, y = self._area.get_proj_coords(data_slice=(int(self.shape[0] / 2), int(self.shape[1] / 2))) | ||
return x.squeeze(), y.squeeze() | ||
|
||
@property | ||
def x(self): | ||
"""Retrieve boundary x vertices.""" | ||
return self._x | ||
|
||
@property | ||
def y(self): | ||
"""Retrieve boundary y vertices.""" | ||
return self._y | ||
|
||
def plot(self, ax=None, subplot_kw=None, crs=None, alpha=0.6, **kwargs): | ||
"""Plot the the boundary. crs must be a Cartopy CRS !""" | ||
from pyresample.visualization.geometries import plot_geometries | ||
|
||
if self.cartopy_crs is None and crs is None: | ||
raise ValueError("Projection Cartopy 'crs' is required to display projection boundary.") | ||
if crs is None: | ||
crs = self.cartopy_crs | ||
if subplot_kw is None: | ||
subplot_kw = {"projection": crs} | ||
|
||
geom = self.to_shapely_polygon() | ||
p = plot_geometries(geometries=[geom], crs=crs, | ||
ax=ax, subplot_kw=subplot_kw, alpha=alpha, **kwargs) | ||
return p |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And is this used outside of the test modules? If not, I'd prefer this go in
pyresample/test/__init__.py
or maybe in conftest.py but I think it'd have to be a fixture to be useful there and that probably isn't worth it. You'd have to do the right amount of.parent.parent
to get the correct directory.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok