Skip to content

Commit 85efc86

Browse files
author
Tom Augspurger
authored
Merge pull request #5 from TomAugspurger/tom/fix/to_item_collection-types
Improved to_item_collection
2 parents 54e02fd + 50f2886 commit 85efc86

File tree

9 files changed

+177
-11
lines changed

9 files changed

+177
-11
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ repos:
1919
- id: flake8
2020
language_version: python3
2121
- repo: https://github.com/pre-commit/mirrors-mypy
22-
rev: v0.942
22+
rev: v1.2.0
2323
hooks:
2424
- id: mypy
2525
# Override default --ignore-missing-imports

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ dependencies = [
1515
"geopandas",
1616
"pandas",
1717
"pyarrow",
18-
"shapely"
18+
"shapely",
19+
"packaging",
1920
]
2021

2122
[project.optional-dependencies]
@@ -54,6 +55,8 @@ filterwarnings = [
5455

5556
[tool.mypy]
5657

58+
python_version = "3.10"
59+
5760
[[tool.mypy.overrides]]
5861
module = [
5962
"shapely.*",

stac_geoparquet/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""stac-geoparquet"""
22
__version__ = "0.2.0"
33

4-
from .stac_geoparquet import to_geodataframe
4+
from .stac_geoparquet import to_geodataframe, to_dict, to_item_collection
55

66

7-
__all__ = ["__version__", "to_geodataframe"]
7+
__all__ = ["__version__", "to_geodataframe", "to_dict", "to_item_collection"]

stac_geoparquet/_compat.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pystac
2+
import packaging.version
3+
4+
PYSTAC_1_7_0 = packaging.version.parse(pystac.__version__) >= packaging.version.Version(
5+
"1.7.0"
6+
)

stac_geoparquet/stac_geoparquet.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,22 @@
88
import pystac
99
import geopandas
1010
import pandas as pd
11+
import numpy as np
1112
import shapely.geometry
1213

1314
from stac_geoparquet.utils import fix_empty_multipolygon
1415

1516

17+
def _fix_array(v):
18+
if isinstance(v, np.ndarray):
19+
v = v.tolist()
20+
21+
elif isinstance(v, dict):
22+
v = {k: _fix_array(v2) for k, v2 in v.items()}
23+
24+
return v
25+
26+
1627
def to_geodataframe(items: Sequence[dict[str, Any]]) -> geopandas.GeoDataFrame:
1728
"""
1829
Convert a sequence of STAC items to a :class:`geopandas.GeoDataFrame`.
@@ -94,6 +105,7 @@ def to_dict(record: dict) -> dict:
94105
}
95106
item = {}
96107
for k, v in record.items():
108+
v = _fix_array(v)
97109

98110
if k in top_level_keys:
99111
item[k] = v
@@ -107,6 +119,20 @@ def to_dict(record: dict) -> dict:
107119

108120

109121
def to_item_collection(df: geopandas.GeoDataFrame) -> pystac.ItemCollection:
122+
"""
123+
Convert a GeoDataFrame of STAC items to an :class:`pystac.ItemCollection`.
124+
125+
Parameters
126+
----------
127+
df : geopandas.GeoDataFrame
128+
A GeoDataFrame with a schema similar to that exported by stac-geoparquet.
129+
130+
Returns
131+
-------
132+
item_collection : pystac.ItemCollection
133+
The converted ItemCollection. There will be one record / feature per
134+
row in the in the GeoDataFrame.
135+
"""
110136
df2 = df.copy()
111137
datelike = df2.select_dtypes(
112138
include=["datetime64[ns, UTC]", "datetime64[ns]"]
@@ -116,6 +142,5 @@ def to_item_collection(df: geopandas.GeoDataFrame) -> pystac.ItemCollection:
116142
df2[k].dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ").fillna("").replace({"": None})
117143
)
118144

119-
return pystac.ItemCollection(
120-
[to_dict(record) for record in df2.to_dict(orient="records")]
121-
)
145+
records = [to_dict(record) for record in df2.to_dict(orient="records")]
146+
return pystac.ItemCollection(records)

tests/data/naip.parquet

31.1 KB
Binary file not shown.

tests/test_pgstac_reader.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import stac_geoparquet.pgstac_reader
1111
from stac_geoparquet.utils import assert_equal
12+
from stac_geoparquet._compat import PYSTAC_1_7_0
1213

1314

1415
HERE = pathlib.Path(__file__).parent
@@ -96,7 +97,8 @@ def test_naip_item():
9697
]
9798

9899
cfg = stac_geoparquet.pgstac_reader.CollectionConfig(
99-
collection_id="naip", render_config="assets=image&asset_bidx=image%7C1%2C2%2C3"
100+
collection_id="naip",
101+
render_config="assets=image&asset_bidx=image%7C1%2C2%2C3&format=png",
100102
)
101103
result = cfg.make_pgstac_items(records, base_item)[0]
102104
# shapely uses tuples instead of lists
@@ -106,6 +108,11 @@ def test_naip_item():
106108
"https://planetarycomputer.microsoft.com/api/stac/v1/collections/naip/items/pa_m_4108053_se_17_1_20150725_20151201" # noqa: E501
107109
)
108110

111+
if PYSTAC_1_7_0:
112+
# https://github.com/stac-utils/pystac/issues/1102
113+
expected.remove_links(rel=pystac.RelType.SELF)
114+
result.remove_links(rel=pystac.RelType.SELF)
115+
109116
assert_equal(result, expected)
110117

111118

@@ -120,12 +127,17 @@ def test_sentinel2_l2a():
120127
partition_frequency=None,
121128
stac_api="https://planetarycomputer.microsoft.com/api/stac/v1",
122129
should_inject_dynamic_properties=True,
123-
render_config="assets=visual&asset_bidx=visual%7C1%2C2%2C3&nodata=0",
130+
render_config="assets=visual&asset_bidx=visual%7C1%2C2%2C3&nodata=0&format=png",
124131
)
125132
result = pystac.read_dict(config.make_pgstac_items([record], base_item)[0])
126133
expected = pystac.read_file(
127134
"https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items/S2A_MSIL2A_20150704T101006_R022_T35XQA_20210411T133707" # noqa: E501
128135
)
136+
if PYSTAC_1_7_0:
137+
# https://github.com/stac-utils/pystac/issues/1102
138+
expected.remove_links(rel=pystac.RelType.SELF)
139+
result.remove_links(rel=pystac.RelType.SELF)
140+
129141
expected.remove_links(rel=pystac.RelType.LICENSE)
130142
assert_equal(result, expected)
131143

tests/test_stac_geoparquet.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,11 @@ def test_s1_grd():
324324
],
325325
)
326326
def test_smoke(collection_id):
327-
items = requests.get(
327+
r = requests.get(
328328
f"https://planetarycomputer.microsoft.com/api/stac/v1/collections/{collection_id}/items?limit=1"
329-
).json()["features"]
329+
)
330+
r.raise_for_status()
331+
items = r.json()["features"]
330332
df = stac_geoparquet.to_geodataframe(items)
331333

332334
result = to_item_collection(df)

tests/test_to_dict.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import pathlib
2+
3+
import pytest
4+
import geopandas
5+
6+
import stac_geoparquet
7+
8+
9+
HERE = pathlib.Path(__file__).parent
10+
11+
12+
@pytest.fixture
13+
def naip():
14+
return geopandas.read_parquet(HERE / "data" / "naip.parquet")
15+
16+
17+
def test_to_dict(naip):
18+
result = stac_geoparquet.to_item_collection(naip)
19+
expected = {
20+
"assets": {
21+
"image": {
22+
"eo:bands": [
23+
{"common_name": "red", "description": None, "name": "Red"},
24+
{"common_name": "green", "description": None, "name": "Green"},
25+
{"common_name": "blue", "description": None, "name": "Blue"},
26+
{
27+
"common_name": "nir",
28+
"description": "near-infrared",
29+
"name": "NIR",
30+
},
31+
],
32+
"href": "https://naipeuwest.blob.core.windows.net/naip/v002/ok/2010/ok_100cm_2010/34099/m_3409901_nw_14_1_20100425.tif", # noqa: E501
33+
"roles": ["data"],
34+
"title": "RGBIR COG tile",
35+
"type": "image/tiff; application=geotiff; " "profile=cloud-optimized",
36+
},
37+
"rendered_preview": {
38+
"href": "https://planetarycomputer.microsoft.com/api/data/v1/item/preview.png?collection=naip&item=ok_m_3409901_nw_14_1_20100425&assets=image&asset_bidx=image%7C1%2C2%2C3", # noqa: E501
39+
"rel": "preview",
40+
"roles": ["overview"],
41+
"title": "Rendered preview",
42+
"type": "image/png",
43+
},
44+
"thumbnail": {
45+
"href": "https://naipeuwest.blob.core.windows.net/naip/v002/ok/2010/ok_100cm_2010/34099/m_3409901_nw_14_1_20100425.200.jpg", # noqa: E501
46+
"roles": ["thumbnail"],
47+
"title": "Thumbnail",
48+
"type": "image/jpeg",
49+
},
50+
"tilejson": {
51+
"href": "https://planetarycomputer.microsoft.com/api/data/v1/item/tilejson.json?collection=naip&item=ok_m_3409901_nw_14_1_20100425&assets=image&asset_bidx=image%7C1%2C2%2C3", # noqa: E501
52+
"roles": ["tiles"],
53+
"title": "TileJSON with default rendering",
54+
"type": "application/json",
55+
},
56+
},
57+
"bbox": [-100.004084, 34.934259, -99.933454, 35.00323],
58+
"collection": "naip",
59+
"geometry": {
60+
"coordinates": (
61+
(
62+
(-99.933454, 34.934815),
63+
(-99.93423, 35.00323),
64+
(-100.004084, 35.002673),
65+
(-100.00325, 34.934259),
66+
(-99.933454, 34.934815),
67+
),
68+
),
69+
"type": "Polygon",
70+
},
71+
"id": "ok_m_3409901_nw_14_1_20100425",
72+
"links": [
73+
{
74+
"href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/naip",
75+
"rel": "collection",
76+
"type": "application/json",
77+
},
78+
{
79+
"href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/naip",
80+
"rel": "parent",
81+
"type": "application/json",
82+
},
83+
{
84+
"href": "https://planetarycomputer.microsoft.com/api/stac/v1/",
85+
"rel": "root",
86+
"title": "Microsoft Planetary Computer STAC API",
87+
"type": "application/json",
88+
},
89+
{
90+
"href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/naip/items/ok_m_3409901_nw_14_1_20100425", # noqa: E501
91+
"rel": "self",
92+
"type": "application/geo+json",
93+
},
94+
{
95+
"href": "https://planetarycomputer.microsoft.com/api/data/v1/item/map?collection=naip&item=ok_m_3409901_nw_14_1_20100425", # noqa: E501
96+
"rel": "preview",
97+
"title": "Map of item",
98+
"type": "text/html",
99+
},
100+
],
101+
"properties": {
102+
"datetime": "2010-04-25T00:00:00Z",
103+
"gsd": 1.0,
104+
"naip:state": "ok",
105+
"naip:year": "2010",
106+
"proj:bbox": [408377.0, 3866212.0, 414752.0, 3873800.0],
107+
"proj:epsg": 26914,
108+
"proj:shape": [7588, 6375],
109+
"proj:transform": [1.0, 0.0, 408377.0, 0.0, -1.0, 3873800.0, 0.0, 0.0, 1.0],
110+
},
111+
"stac_extensions": [
112+
"https://stac-extensions.github.io/eo/v1.0.0/schema.json",
113+
"https://stac-extensions.github.io/projection/v1.0.0/schema.json",
114+
],
115+
"stac_version": "1.0.0",
116+
"type": "Feature",
117+
}
118+
assert result[0].to_dict() == expected

0 commit comments

Comments
 (0)