Skip to content

Commit

Permalink
Merge branch 'main' into hayes-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
scottyhq authored Dec 21, 2024
2 parents 3ee68c4 + 00d1a4f commit 361ab93
Show file tree
Hide file tree
Showing 23 changed files with 760 additions and 420 deletions.
1 change: 0 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@
- [ ] Tests added
- [ ] Tests passing
- [ ] Full type hint coverage
- [ ] Changes are documented in `docs/releases.rst`
- [ ] New functions/methods are listed in `api.rst`
- [ ] New functionality has documentation
51 changes: 27 additions & 24 deletions docs/examples/contextual_data.ipynb

Large diffs are not rendered by default.

75 changes: 33 additions & 42 deletions docs/examples/sliderule.ipynb

Large diffs are not rendered by default.

222 changes: 98 additions & 124 deletions pixi.lock

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ dependencies = [
"geopandas>=1.0.1,<2",
"maxar-platform>=1.0.2,<2",
"odc-stac>=0.3.10,<0.4",
"planetary-computer>=1.0.0,<2",
"pyarrow>=18.0.0,<19",
"pystac-client>=0.8.3,<0.9",
"requests>=2.32.3,<3",
Expand Down Expand Up @@ -212,7 +211,6 @@ docs = { features = ["docs"], solve-group = "default" }
python = "<3.13" # https://github.com/stac-utils/stac-geoparquet/issues/81
geopandas = "*"
odc-stac = "*"
planetary-computer = "*"
pystac-client = "*"
requests = "*"
types-requests = "*"
Expand All @@ -223,6 +221,7 @@ pyarrow = "*"
# Testing additional dependencies (not in pypi list
jsonschema = ">=4.23.0,<5"
libgdal-arrow-parquet = ">=3.10.0,<4"
gdal = ">=3.10.0,<4"


[tool.pixi.feature.dev.dependencies]
Expand Down Expand Up @@ -254,7 +253,7 @@ precommit = "pre-commit run --all"
# NOTE: consider --output-format=github for github actions
lint = "pylint src"
test = "pytest -o markers=network -m 'not network' --cov --cov-report=xml --cov-report=term"
networktest = "pytest --cov --cov-report=xml --cov-report=term"
networktest = "pytest --cov --cov-report=xml --cov-report=term --durations=0"

[tool.pixi.feature.docs.pypi-dependencies]
coincident = { path = ".", editable = true }
Expand Down
4 changes: 2 additions & 2 deletions src/coincident/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from __future__ import annotations

from coincident import datasets, io, overlaps, search
from coincident import datasets, io, overlaps, plot, search
from coincident._version import version as __version__

__all__ = ["__version__", "datasets", "search", "overlaps", "io"]
__all__ = ["__version__", "datasets", "search", "overlaps", "io", "plot"]
105 changes: 0 additions & 105 deletions src/coincident/datasets/maxar.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,11 @@
# from pydantic.dataclasses import dataclass, Field # type: ignore[attr-defined]
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from stac_asset import Config

import os
import warnings
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path

import numpy as np
import pystac
import rasterio
import stac_asset
import xarray as xr

from coincident._utils import depends_on_optional
from coincident.datasets.general import Dataset

MAXAR_CONFIG = stac_asset.Config(
http_headers={"MAXAR-API-KEY": os.environ.get("MAXAR_API_KEY")}
)
try:
import matplotlib.pyplot as plt
except ImportError:
warnings.warn(
"'matplotlib' package not found. Install for plotting functions: https://matplotlib.org/stable/install/index.html",
stacklevel=2,
)


class Collection(str, Enum):
"""wv01,wv02,wv03-vnir,ge01"""
Expand All @@ -64,82 +38,3 @@ class Stereo(Dataset):
# Unique to Maxar
area_based_calc: bool = False
provider: str = "maxar"


# NOTE: expose as coincident.search.download
# Or just add documentation for how to do this?
async def download_item(
item: pystac.Item,
path: str = "/tmp",
config: Config = MAXAR_CONFIG,
) -> pystac.Item:
"""localitem = asyncio.run(download_item(item, config=MAXAR_CONFIG))"""
posixpath = Path(path)
item = await stac_asset.download_item(item, posixpath, config=config)
return item # noqa: RET504


def open_browse(item: pystac.Item, overview_level: int = 0) -> xr.DataArray:
"""
Open a browse image from a STAC item using the specified overview level.
Parameters
----------
item : pystac.Item
The STAC item containing the browse image asset.
overview_level : int, optional
The overview level to use when opening the image, by default 0.
Returns
-------
xr.DataArray
The opened browse image as an xarray DataArray.
Notes
-----
The function uses the `rasterio` engine to open the image and sets the
`GDAL_DISABLE_READDIR_ON_OPEN` and `GDAL_HTTP_HEADERS` environment variables
for optimized reading and authentication, respectively.
"""

# href = item.assets['browse']['href'] # accessed via geopandas
href = item.assets["browse"].href # accessed via pystac

env = rasterio.Env(
GDAL_DISABLE_READDIR_ON_OPEN="EMPTY_DIR",
GDAL_HTTP_HEADERS=f'MAXAR-API-KEY:{os.environ["MAXAR_API_KEY"]}',
)
with env:
return xr.open_dataarray(
href,
engine="rasterio",
mask_and_scale=False, # otherwise uint8 -> float32!
backend_kwargs={"open_kwargs": {"overview_level": overview_level}},
)


@depends_on_optional("matplotlib")
def plot_browse(item: pystac.Item, overview_level: int = 0) -> None:
"""
Plots a browse image from a STAC item using Matplotlib.
Parameters
----------
item : pystac.Item
The STAC item containing the browse image to be plotted.
overview_level : int, optional
The overview level of the browse image to be opened, by default 0.
Returns
-------
None
This function does not return any value
"""

da = open_browse(item, overview_level=overview_level)
mid_lat = da.y[int(da.y.size / 2)].to_numpy() # PD011

fig, ax = plt.subplots(figsize=(8, 11)) # pylint: disable=unused-variable
da.plot.imshow(rgb="band", add_labels=False, ax=ax)
ax.set_aspect(aspect=1 / np.cos(np.deg2rad(mid_lat)))
ax.set_title(item.id)
4 changes: 2 additions & 2 deletions src/coincident/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

from __future__ import annotations

from coincident.io import sliderule, xarray
from coincident.io import download, sliderule, xarray

__all__ = ["sliderule", "xarray"]
__all__ = ["sliderule", "xarray", "download"]
47 changes: 47 additions & 0 deletions src/coincident/io/download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Convenience functions for downloading STAC items via stac-asset
"""

from __future__ import annotations

import os
from pathlib import Path

import stac_asset
from pystac import Item


async def download_item(
item: Item,
path: str = "/tmp",
config: str | None = None,
) -> Item:
"""
Downloads a STAC item to a specified local path.
Parameters
----------
item : pystac.Item
The STAC item to be downloaded.
path : str, optional
The local directory path where the item will be downloaded. Default is "/tmp".
config : str, optional
If config=='maxar', a MAXAR-API-KEY HTTP Header is used for authentication.
Returns
-------
pystac.Item
The downloaded STAC item.
Examples
--------
>>> localitem = asyncio.run(download_item(item, config=MAXAR_CONFIG))
"""
posixpath = Path(path)

if config == "maxar":
config = stac_asset.Config(
http_headers={"MAXAR-API-KEY": os.environ.get("MAXAR_API_KEY")}
)
# NOTE: prevent in-place modification of remote hrefs with item.clone()
await stac_asset.download_item(item.clone(), posixpath, config=config)
10 changes: 5 additions & 5 deletions src/coincident/io/sliderule.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@


def _gdf_to_sliderule_polygon(gf: gpd.GeoDataFrame) -> list[dict[str, float]]:
# Ignore type necessary I think b/c sliderule doesn't have type hints?
return toregion(gf[["geometry"]].dissolve())["poly"] # type: ignore[no-any-return]
# NOTE: .union_all().convex_hull of 2 point geodataframe is LINESTRING
bounding_polygon = gpd.GeoDataFrame(geometry=gf[["geometry"]].dissolve().envelope)
return toregion(bounding_polygon)["poly"] # type: ignore[no-any-return]


def _gdf_to_sliderule_params(gf: gpd.GeoDataFrame) -> dict[str, Any]:
Expand Down Expand Up @@ -203,8 +204,8 @@ def process_atl06sr(
gf: gpd.GeoDataFrame,
aoi: gpd.GeoDataFrame = None,
target_surface: str = "ground",
include_worldcover: bool = True,
include_3dep: bool = True,
include_worldcover: bool = False,
include_3dep: bool = False,
sliderule_params: dict[str, Any] | None = None,
) -> gpd.GeoDataFrame:
"""
Expand Down Expand Up @@ -266,7 +267,6 @@ def process_atl06sr(
# User-provided parameters take precedence
if sliderule_params is not None:
params.update(sliderule_params)
# print(params)

gfsr = icesat2.atl06p(params, resources=granule_names)

Expand Down
87 changes: 41 additions & 46 deletions src/coincident/io/xarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,22 @@

from __future__ import annotations

import warnings
import os
from typing import Any

import geopandas as gpd
import odc.stac
import pystac
import rasterio

# NOTE: must import for odc.stac outputs to have .rio accessor
import rioxarray # noqa: F401
import xarray as xr

from coincident._utils import depends_on_optional
from coincident.datasets.planetary_computer import WorldCover
from coincident.search.stac import to_pystac_items

try:
import matplotlib.colors
import matplotlib.pyplot as plt
from matplotlib import cm
except ImportError:
warnings.warn(
"'matplotlib' package not found. Install for plotting functions: https://matplotlib.org/stable/install/index.html",
stacklevel=2,
)

# Sets GDAL_DISABLE_READDIR_ON_OPEN to 'EMPTY_DIR' etc.
odc.stac.configure_rio(cloud_defaults=True)
odc.stac.configure_rio(cloud_defaults=True, VSICURL_PC_URL_SIGNING="YES")


def to_dataset(
Expand Down Expand Up @@ -69,36 +59,41 @@ def to_dataset(
return ds


@depends_on_optional("matplotlib")
def plot_esa_worldcover(ds: xr.Dataset) -> plt.Axes:
"""From https://planetarycomputer.microsoft.com/dataset/esa-worldcover#Example-Notebook"""
classmap = WorldCover().classmap

colors = ["#000000" for r in range(256)]
for key, value in classmap.items():
colors[int(key)] = value["hex"]
cmap = matplotlib.colors.ListedColormap(colors)

# sequences needed for an informative colorbar
values = list(classmap)
boundaries = [(values[i + 1] + values[i]) / 2 for i in range(len(values) - 1)]
boundaries = [0, *boundaries, 255]
ticks = [
(boundaries[i + 1] + boundaries[i]) / 2 for i in range(len(boundaries) - 1)
]
tick_labels = [value["description"] for value in classmap.values()]

fig, ax = plt.subplots()
normalizer = matplotlib.colors.Normalize(vmin=0, vmax=255)

da = ds.to_dataarray().squeeze()
da.plot(ax=ax, cmap=cmap, norm=normalizer)

colorbar = fig.colorbar(
cm.ScalarMappable(norm=normalizer, cmap=cmap),
boundaries=boundaries,
values=values,
cax=fig.axes[1].axes,
# NOTE: make this more general to open any single STAC asset?
def open_maxar_browse(item: pystac.Item, overview_level: int = 0) -> xr.DataArray:
"""
Open a browse image from a STAC item using the specified overview level.
Parameters
----------
item : pystac.Item
The STAC item containing the browse image asset.
overview_level : int, optional
The overview level to use when opening the image, by default 0.
Returns
-------
xr.DataArray
The opened browse image as an xarray DataArray.
Notes
-----
The function uses the `rasterio` engine to open the image and sets the
`GDAL_DISABLE_READDIR_ON_OPEN` and `GDAL_HTTP_HEADERS` environment variables
for optimized reading and authentication, respectively.
"""

# href = item.assets['browse']['href'] # accessed via geopandas
href = item.assets["browse"].href # accessed via pystac

env = rasterio.Env(
GDAL_DISABLE_READDIR_ON_OPEN="EMPTY_DIR",
GDAL_HTTP_HEADERS=f'MAXAR-API-KEY:{os.environ["MAXAR_API_KEY"]}',
)
colorbar.set_ticks(ticks, labels=tick_labels)
return ax
with env:
return xr.open_dataarray(
href,
engine="rasterio",
mask_and_scale=False, # otherwise uint8 -> float32!
backend_kwargs={"open_kwargs": {"overview_level": overview_level}},
)
9 changes: 9 additions & 0 deletions src/coincident/plot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
selection of useful plots with matplotlib
"""

from __future__ import annotations

from coincident.plot.matplotlib import plot_esa_worldcover, plot_maxar_browse

__all__ = ["plot_esa_worldcover", "plot_maxar_browse"]
Loading

0 comments on commit 361ab93

Please sign in to comment.