Skip to content

Commit

Permalink
Merge branch 'low-hanging-refactors' into harmony
Browse files Browse the repository at this point in the history
  • Loading branch information
trey-stafford committed Oct 31, 2024
2 parents ba1b007 + a180892 commit 014d566
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 30 deletions.
13 changes: 5 additions & 8 deletions icepyx/core/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ class GenQuery:
Quest
"""

_temporal: tp.Temporal
_spatial: spat.Spatial
_temporal: tp.Temporal

def __init__(
self,
Expand Down Expand Up @@ -751,10 +751,9 @@ def order_vars(self) -> Variables:
def granules(self) -> Granules:
"""
Return the granules object, which provides the underlying functionality
hing, ordering,
and downloading granules for the specified product.
Users are encouraged to use the built-in wrappers
rather than trying to access the granules object themselves.
for searching, ordering, and downloading granules for the specified
product. Users are encouraged to use the built-in wrappers rather than
trying to access the granules object themselves.
See Also
--------
Expand All @@ -765,9 +764,7 @@ def granules(self) -> Granules:
Examples
--------
>>> reg_a = ipx.Query('ATL06',[-55, 68, -48,
71],['2019-02-20','2019-02-28']) #
+SKIP
>>> reg_a = ipx.Query('ATL06',[-55, 68, -48, 71],['2019-02-20','2019-02-28']) # +SKIP
>>> reg_a.granules # doctest: +SKIP
<icepyx.core.granules.Granules at [location]>
"""
Expand Down
61 changes: 39 additions & 22 deletions icepyx/core/spatial.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from itertools import chain
import os
from typing import Literal, Optional, Union, cast
import warnings
Expand All @@ -8,6 +9,8 @@
from shapely.geometry import Polygon, box
from shapely.geometry.polygon import orient

import icepyx.core.exceptions

# DevGoal: need to update the spatial_extent docstring to describe coordinate order for input


Expand All @@ -16,7 +19,7 @@

def geodataframe(
extent_type: ExtentType,
spatial_extent: Union[str, list[float]],
spatial_extent: Union[str, list[float], list[tuple[float, float]], Polygon],
file: bool = False,
xdateline: Optional[bool] = None,
) -> gpd.GeoDataFrame:
Expand All @@ -29,14 +32,17 @@ def geodataframe(
One of 'bounding_box' or 'polygon', indicating what type of input the spatial extent is
spatial_extent :
A list containing the spatial extent OR a string containing a filename.
If file is False, spatial_extent should be a
list of coordinates in decimal degrees of [lower-left-longitude,
lower-left-latitute, upper-right-longitude, upper-right-latitude] or
[longitude1, latitude1, longitude2, latitude2, ... longitude_n,latitude_n, longitude1,latitude1].
A list containing the spatial extent, a shapely.Polygon, a list of
tuples (i.e.,, `[(longitude1, latitude1), (longitude2, latitude2),
...]`)containing floats, OR a string containing a filename.
If file is False, spatial_extent should be a shapely.Polygon,
list of bounding box coordinates in decimal degrees of [lower-left-longitude,
lower-left-latitute, upper-right-longitude, upper-right-latitude] or polygon vertices as
[longitude1, latitude1, longitude2, latitude2, ...
longitude_n,latitude_n, longitude1,latitude1].
If file is True, spatial_extent is a string containing the full file path and filename to the
file containing the desired spatial extent.
If file is True, spatial_extent is a string containing the full file path and filename
to the file containing the desired spatial extent.
file :
Indication for whether the spatial_extent string is a filename or coordinate list
Expand Down Expand Up @@ -65,15 +71,31 @@ def geodataframe(
"""

# If extent_type is a polygon AND from a file, create a geopandas geodataframe from it
# DevGoal: Currently this branch isn't tested...
if file is True:
if extent_type == "polygon":
return gpd.read_file(spatial_extent)
else:
raise TypeError("When 'file' is True, 'extent_type' must be 'polygon'")

if isinstance(spatial_extent, str):
raise TypeError(f"Expected list of floats, received {spatial_extent=}")
raise TypeError(
f"Expected list of floats, list of tuples of floats, or Polygon, received {spatial_extent=}"
)

if isinstance(spatial_extent, Polygon):
# Convert `spatial_extent` into a list of floats like:
# `[longitude1, latitude1, longitude2, latitude2, ...]`
spatial_extent = [
float(coord) for point in spatial_extent.exterior.coords for coord in point
]

# We are dealing with a `list[tuple[float, float]]`
if isinstance(spatial_extent, list) and isinstance(spatial_extent[0], tuple):
# Convert the list of tuples into a flat list of floats
spatial_extent = cast(list[tuple[float, float]], spatial_extent)
spatial_extent = list(chain.from_iterable(spatial_extent))

spatial_extent = cast(list[float], spatial_extent)

if xdateline is not None:
xdateline = xdateline
Expand Down Expand Up @@ -312,7 +334,7 @@ def validate_polygon_pairs(

def validate_polygon_list(
spatial_extent: Union[
list[Union[float, str]],
list[float],
NDArray[np.floating],
],
) -> tuple[Literal["polygon"], list[float], None]:
Expand All @@ -327,7 +349,7 @@ def validate_polygon_list(
Parameters
----------
spatial_extent:
A list or np.ndarray of strings or numerics representing polygon coordinates,
A list or np.ndarray of numerics representing polygon coordinates,
provided as coordinate pairs in decimal degrees in the order:
[longitude1, latitude1, longitude2, latitude2, ...
... longitude_n,latitude_n, longitude1,latitude1]
Expand Down Expand Up @@ -411,14 +433,13 @@ def validate_polygon_file(

class Spatial:
_ext_type: ExtentType
_spatial_ext: list[float]
_geom_file: Optional[str]
_spatial_ext: list[float]

def __init__(
self,
spatial_extent: Union[
str, # Filepath
list[str], # Bounding box or polygon
list[float], # Bounding box or polygon
list[tuple[float, float]], # Polygon
NDArray, # Polygon
Expand All @@ -436,7 +457,7 @@ def __init__(
----------
spatial_extent : list or string
* list of coordinates
(stored in a list of strings, list of numerics, list of tuples, OR np.ndarray) as one of:
(stored in a list of numerics, list of tuples, OR np.ndarray) as one of:
* bounding box
* provided in the order: [lower-left-longitude, lower-left-latitude,
upper-right-longitude, upper-right-latitude].)
Expand Down Expand Up @@ -542,7 +563,7 @@ def __init__(
# HACK: Unfortunately, the typechecker can't narrow based on the
# above conditional expressions. Tell the typechecker, "trust us"!
cast(
Union[list[Union[str, float]], NDArray[np.floating]],
Union[list[float], NDArray[np.floating]],
spatial_extent,
)
)
Expand Down Expand Up @@ -730,9 +751,7 @@ def fmt_for_CMR(self) -> str:
return ",".join(map(str, extent))

else:
# HACK: Pyright doesn't correctly understand this exhaustive check
# see: https://github.com/microsoft/pyright/issues/8955
raise RuntimeError("Programmer error!")
raise icepyx.core.exceptions.ExhaustiveTypeGuardException

def fmt_for_EGI(self) -> str:
"""
Expand Down Expand Up @@ -761,6 +780,4 @@ def fmt_for_EGI(self) -> str:
return egi_extent.replace(" ", "") # remove spaces for API call

else:
# HACK: Pyright doesn't correctly understand this exhaustive check
# see: https://github.com/microsoft/pyright/issues/8955
raise RuntimeError("Programmer error!")
raise icepyx.core.exceptions.ExhaustiveTypeGuardException
80 changes: 80 additions & 0 deletions icepyx/tests/unit/test_spatial.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest
from shapely.geometry import Polygon

import icepyx
import icepyx.core.spatial as spat

# ######### "Bounding Box" input tests ################################################################################
Expand Down Expand Up @@ -406,6 +407,71 @@ def test_gdf_from_multi_bbox():
assert obs.geometry[0].equals(exp.geometry[0])


def test_gdf_from_polygon():
polygon = Polygon(list(zip([-55, -55, -48, -48, -55], [68, 71, 71, 68, 68])))
obs = spat.geodataframe("polygon", polygon)
exp = gpd.GeoDataFrame(geometry=[polygon])

# make sure there is only one geometry before comparing them
assert len(obs.geometry) == 1
assert len(exp.geometry) == 1
assert obs.geometry[0].equals(exp.geometry[0])


def test_gdf_from_list_tuples():
polygon_tuples = list(
zip([-55.0, -55.0, -48.0, -48.0, -55.0], [68.0, 71.0, 71.0, 68.0, 68.0])
)
obs = spat.geodataframe("polygon", polygon_tuples)
geom = [Polygon(polygon_tuples)]
exp = gpd.GeoDataFrame(geometry=geom)

# make sure there is only one geometry before comparing them
assert len(obs.geometry) == 1
assert len(exp.geometry) == 1
assert obs.geometry[0].equals(exp.geometry[0])


def test_gdf_raises_error_bounding_box_file():
with pytest.raises(TypeError):
spat.geodataframe("bounding_box", "/fake/file/somewhere/polygon.shp", file=True)


def test_gdf_raises_error_string_file_false():
with pytest.raises(TypeError):
spat.geodataframe(
"bounding_box", "/fake/file/somewhere/polygon.shp", file=False
)


def test_gdf_boundingbox_xdateline():
bbox = [-55.5, 66.2, -64.2, 72.5]

# construct a geodataframe with the geometry corrected for the xdateline.
bbox_with_fix_for_xdateline = [304.5, 66.2, 295.8, 72.5]
min_x, min_y, max_x, max_y = bbox_with_fix_for_xdateline
exp = gpd.GeoDataFrame(
geometry=[
Polygon(
[
(min_x, min_y),
(min_x, max_y),
(max_x, max_y),
(max_x, min_y),
(min_x, min_y),
]
)
]
)

obs = spat.geodataframe("bounding_box", bbox)

# make sure there is only one geometry before comparing them
assert len(obs.geometry) == 1
assert len(exp.geometry) == 1
assert obs.geometry[0].equals(exp.geometry[0])


# Potential tests to include once multipolygon and complex polygons are handled

# def test_gdf_from_strpoly_one_simple():
Expand Down Expand Up @@ -498,6 +564,20 @@ def test_bbox_fmt():
assert obs == exp


def test_fmt_for_cmr_fails_unknown_extent_type():
bbox = spat.Spatial([-55, 68, -48, 71])
bbox._ext_type = "Unknown_user_override"
with pytest.raises(icepyx.core.exceptions.ExhaustiveTypeGuardException):
bbox.fmt_for_CMR()


def test_fmt_for_egi_fails_unknown_extent_type():
bbox = spat.Spatial([-55, 68, -48, 71])
bbox._ext_type = "Unknown_user_override"
with pytest.raises(icepyx.core.exceptions.ExhaustiveTypeGuardException):
bbox.fmt_for_EGI()


@pytest.fixture
def poly():
coords = [
Expand Down

0 comments on commit 014d566

Please sign in to comment.