Skip to content
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

Default to CF standard axes for accessing dimension names #20

Merged
merged 11 commits into from
Nov 15, 2021
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
run: python -m pip install .[dev]

- name: Run tests
run: pytest -v
run: pytest tests -v

- name: Lint
run: pre-commit run --all-files
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ author = "Tom Augspurger"
author-email = "taugspurger@microsoft.com"
classifiers = [ "License :: OSI Approved :: MIT License",]
requires = [
"cf_xarray",
"xarray",
"numpy",
"pystac>=1.0.0b3",
Expand Down
150 changes: 52 additions & 98 deletions test_xstac.py → tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
from xstac import xarray_to_stac, fix_attrs
from xstac._xstac import _bbox_to_geometry
import xarray as xr
import pytest
import numpy as np
import pandas as pd
import pytest
import pystac

import xstac

import xarray as xr
from xstac import fix_attrs

data = np.empty((40, 584, 284), dtype="float32")
x = xr.DataArray(
Expand Down Expand Up @@ -117,7 +112,23 @@ def ds():
return fix_attrs(ds)


def test_xarray_to_stac(ds):
@pytest.fixture
def ds_without_spatial_dims():
ds = xr.Dataset(
{
"data": xr.DataArray(
[1, 2],
dims=("time",),
coords={"time": pd.to_datetime(["2021-01-01", "2021-01-02"])},
)
}
)
ds.time.attrs["long_name"] = "time"
return ds


@pytest.fixture
def collection_template():
template = {
"id": "id",
"type": "Collection",
Expand All @@ -126,19 +137,25 @@ def test_xarray_to_stac(ds):
"license": "license",
"stac_version": "1.0.0",
}
result = xarray_to_stac(
ds,
template=template,
temporal_dimension="time",
x_dimension="x",
y_dimension="y",
)
assert result.id == "id"
assert isinstance(result, pystac.Collection)
assert result.description == "description"
assert result.license == "license"
return template


dimensions = result.extra_fields["cube:dimensions"]
@pytest.fixture
def item_template():
template = {
"id": "id",
"type": "Feature",
"links": [],
"geometry": None,
"stac_version": "1.0.0",
"properties": {"datetime": "2021-01-01T00:00:00Z"},
"assets": {},
}
return template


@pytest.fixture
def collection_expected_dims():
expected = {
"time": {
"type": "temporal",
Expand Down Expand Up @@ -352,7 +369,11 @@ def test_xarray_to_stac(ds):
},
},
}
assert dimensions == expected
return expected


@pytest.fixture
def collection_expected_vars():
expected = {
"lat": {
"type": "auxiliary",
Expand Down Expand Up @@ -419,65 +440,11 @@ def test_xarray_to_stac(ds):
},
},
}
assert result.extra_fields["cube:variables"] == expected
return expected


def test_validation_with_none():
# https://github.com/TomAugspurger/xstac/issues/9
template = {
"type": "Collection",
"id": "cesm2-lens",
"stac_version": "1.0.0",
"description": "desc",
"stac_extensions": [
"https://stac-extensions.github.io/datacube/v2.0.0/schema.json"
],
"extent": {
"spatial": {"bbox": [[-180, -90, 180, 90]]},
"temporal": {
"interval": [["1851-01-01T00:00:00Z", "1851-01-01T00:00:00Z"]]
},
},
"providers": [],
"license": "CC0-1.0",
"links": [],
}
ds = xr.Dataset(
{
"data": xr.DataArray(
[1, 2],
dims=("time",),
coords={"time": pd.to_datetime(["2021-01-01", "2021-01-02"])},
)
}
)
ds.time.attrs["long_name"] = "time"
c = xarray_to_stac(ds, template, temporal_dimension="time")
c.normalize_hrefs("/")
c.validate()


def test_xarray_to_stac_item(ds):
template = {
"id": "id",
"type": "Feature",
"links": [],
"geometry": None,
"stac_version": "1.0.0",
"properties": {"datetime": "2021-01-01T00:00:00Z"},
"assets": {},
}
result = xarray_to_stac(
ds,
template=template,
temporal_dimension="time",
x_dimension="x",
y_dimension="y",
)
assert result.id == "id"
assert isinstance(result, pystac.Item)

dimensions = result.properties["cube:dimensions"]
@pytest.fixture
def item_expected_dims():
expected = {
"time": {
"type": "temporal",
Expand Down Expand Up @@ -691,7 +658,11 @@ def test_xarray_to_stac_item(ds):
},
},
}
assert dimensions == expected
return expected


@pytest.fixture
def item_expected_vars():
expected = {
"lat": {
"type": "auxiliary",
Expand Down Expand Up @@ -758,21 +729,4 @@ def test_xarray_to_stac_item(ds):
},
},
}
assert result.properties["cube:variables"] == expected

assert result.properties["start_datetime"] == "1980-07-31T00:00:00Z"
assert result.properties["end_datetime"] == "2019-07-31T00:00:00Z"


def test_bbox_to_geometry():
import shapely.geometry

bbox = [
-160.2988400944475,
17.960033949329812,
-154.7780670634169,
23.51232608231902,
]
result = shapely.geometry.mapping(shapely.geometry.shape(_bbox_to_geometry(bbox)))
expected = shapely.geometry.mapping(shapely.geometry.box(*bbox))
assert result == expected
return expected
95 changes: 95 additions & 0 deletions tests/test_xstac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from xstac import xarray_to_stac
from xstac._xstac import _bbox_to_geometry
import xarray as xr
import pytest
import pystac

import xstac


# def test_no_time_dimension(ds, collection_template):
# _ = xarray_to_stac(ds, template=collection_template, temporal_dimension=False)


@pytest.mark.parametrize("explicit_dims", [False, True])
def test_xarray_to_stac(
ds,
collection_template,
collection_expected_dims,
collection_expected_vars,
explicit_dims,
):
kw = (
dict()
if not explicit_dims
else dict(temporal_dimension="time", x_dimension="x", y_dimension="y")
)
result = xarray_to_stac(ds, template=collection_template, **kw)
assert result.id == "id"
assert isinstance(result, pystac.Collection)
assert result.description == "description"
assert result.license == "license"

dimensions = result.extra_fields["cube:dimensions"]
assert dimensions == collection_expected_dims
assert result.extra_fields["cube:variables"] == collection_expected_vars


def test_validation_with_none(ds_without_spatial_dims):
# https://github.com/TomAugspurger/xstac/issues/9
template = {
"type": "Collection",
"id": "cesm2-lens",
"stac_version": "1.0.0",
"description": "desc",
"stac_extensions": [
"https://stac-extensions.github.io/datacube/v2.0.0/schema.json"
],
"extent": {
"spatial": {"bbox": [[-180, -90, 180, 90]]},
"temporal": {
"interval": [["1851-01-01T00:00:00Z", "1851-01-01T00:00:00Z"]]
},
},
"providers": [],
"license": "CC0-1.0",
"links": [],
}
c = xarray_to_stac(
ds_without_spatial_dims, template, x_dimension=False, y_dimension=False
)
c.normalize_hrefs("/")
c.validate()


def test_xarray_to_stac_item(ds, item_template, item_expected_dims, item_expected_vars):

result = xarray_to_stac(ds, template=item_template)
assert result.id == "id"
assert isinstance(result, pystac.Item)

dimensions = result.properties["cube:dimensions"]
assert dimensions == item_expected_dims
assert result.properties["cube:variables"] == item_expected_vars

assert result.properties["start_datetime"] == "1980-07-31T00:00:00Z"
assert result.properties["end_datetime"] == "2019-07-31T00:00:00Z"


def test_bbox_to_geometry():
import shapely.geometry

bbox = [
-160.2988400944475,
17.960033949329812,
-154.7780670634169,
23.51232608231902,
]
result = shapely.geometry.mapping(shapely.geometry.shape(_bbox_to_geometry(bbox)))
expected = shapely.geometry.mapping(shapely.geometry.box(*bbox))
assert result == expected


def test_missing_dims_error(ds_without_spatial_dims, collection_template):
with pytest.raises(KeyError):
_ = xarray_to_stac(ds_without_spatial_dims, collection_template)
19 changes: 7 additions & 12 deletions xstac/_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import argparse
import json
import fsspec
import cf_xarray
import xarray as xr

import xstac
Expand Down Expand Up @@ -40,16 +41,10 @@ def parse_args(args=None):

parser.add_argument("--reference-system", default=None)
parser.add_argument(
"--temporal_dimension",
default="time",
help="Coordinate name for the 'time' dimension",
)
parser.add_argument(
"--x-dimension", default="x", help="Coordinate name for the 'x' dimension"
)
parser.add_argument(
"--y-dimension", default="y", help="Coordinate name for the 'y' dimension"
"--temporal_dimension", help="Coordinate name for the 'time' dimension"
)
parser.add_argument("--x-dimension", help="Coordinate name for the 'x' dimension")
parser.add_argument("--y-dimension", help="Coordinate name for the 'y' dimension")
parser.add_argument(
"--no-validate",
action="store_false",
Expand All @@ -62,9 +57,9 @@ def parse_args(args=None):
def generate(
template,
asset_key,
x_dimension="x",
y_dimension="y",
temporal_dimension="time",
x_dimension=None,
y_dimension=None,
temporal_dimension=None,
reference_system=None,
validate: bool = True,
):
Expand Down
Loading