pystac-ext-projection 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"""Implements the :stac-ext:`Projection Extension <projection>`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Generic,
|
|
9
|
+
Literal,
|
|
10
|
+
TypeVar,
|
|
11
|
+
cast,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
import pystac
|
|
15
|
+
from pystac.extensions.base import (
|
|
16
|
+
ExtensionManagementMixin,
|
|
17
|
+
PropertiesExtension,
|
|
18
|
+
SummariesExtension,
|
|
19
|
+
)
|
|
20
|
+
from pystac.extensions.hooks import ExtensionHooks
|
|
21
|
+
from pystac.serialization.identify import STACJSONDescription, STACVersionID
|
|
22
|
+
|
|
23
|
+
#: Generalized version of :class:`~pystac.Item`, :class:`~pystac.Asset`,
|
|
24
|
+
#: or :class:`~pystac.ItemAssetDefinition`
|
|
25
|
+
T = TypeVar("T", pystac.Item, pystac.Asset, pystac.ItemAssetDefinition)
|
|
26
|
+
|
|
27
|
+
SCHEMA_URI: str = "https://stac-extensions.github.io/projection/v2.0.0/schema.json"
|
|
28
|
+
SCHEMA_URIS: list[str] = [
|
|
29
|
+
"https://stac-extensions.github.io/projection/v1.0.0/schema.json",
|
|
30
|
+
"https://stac-extensions.github.io/projection/v1.1.0/schema.json",
|
|
31
|
+
"https://stac-extensions.github.io/projection/v1.2.0/schema.json",
|
|
32
|
+
SCHEMA_URI,
|
|
33
|
+
]
|
|
34
|
+
PREFIX: str = "proj:"
|
|
35
|
+
|
|
36
|
+
# Field names
|
|
37
|
+
CODE_PROP: str = PREFIX + "code"
|
|
38
|
+
EPSG_PROP: str = PREFIX + "epsg"
|
|
39
|
+
WKT2_PROP: str = PREFIX + "wkt2"
|
|
40
|
+
PROJJSON_PROP: str = PREFIX + "projjson"
|
|
41
|
+
GEOM_PROP: str = PREFIX + "geometry"
|
|
42
|
+
BBOX_PROP: str = PREFIX + "bbox"
|
|
43
|
+
CENTROID_PROP: str = PREFIX + "centroid"
|
|
44
|
+
SHAPE_PROP: str = PREFIX + "shape"
|
|
45
|
+
TRANSFORM_PROP: str = PREFIX + "transform"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ProjectionExtension(
|
|
49
|
+
Generic[T],
|
|
50
|
+
PropertiesExtension,
|
|
51
|
+
ExtensionManagementMixin[pystac.Item | pystac.Collection],
|
|
52
|
+
):
|
|
53
|
+
"""An abstract class that can be used to extend the properties of an
|
|
54
|
+
:class:`~pystac.Item` with properties from the :stac-ext:`Projection
|
|
55
|
+
Extension <projection>`. This class is generic over the type of STAC Object to be
|
|
56
|
+
extended (e.g. :class:`~pystac.Item`, :class:`~pystac.Collection`).
|
|
57
|
+
|
|
58
|
+
To create a concrete instance of :class:`ProjectionExtension`, use the
|
|
59
|
+
:meth:`ProjectionExtension.ext` method. For example:
|
|
60
|
+
|
|
61
|
+
.. code-block:: python
|
|
62
|
+
|
|
63
|
+
>>> item: pystac.Item = ...
|
|
64
|
+
>>> proj_ext = ProjectionExtension.ext(item)
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
name: Literal["proj"] = "proj"
|
|
68
|
+
|
|
69
|
+
def apply(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
epsg: int | None = None,
|
|
73
|
+
code: str | None = None,
|
|
74
|
+
wkt2: str | None = None,
|
|
75
|
+
projjson: dict[str, Any] | None = None,
|
|
76
|
+
geometry: dict[str, Any] | None = None,
|
|
77
|
+
bbox: list[float] | None = None,
|
|
78
|
+
centroid: dict[str, float] | None = None,
|
|
79
|
+
shape: list[int] | None = None,
|
|
80
|
+
transform: list[float] | None = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Applies Projection extension properties to the extended Item.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
epsg : Code of the datasource. Example: 4326. One of ``code`` and
|
|
86
|
+
``epsg`` must be provided.
|
|
87
|
+
code : Code of the datasource. Example: "EPSG:4326". One of ``code`` and
|
|
88
|
+
``epsg`` must be provided.
|
|
89
|
+
wkt2 : WKT2 string representing the Coordinate Reference
|
|
90
|
+
System (CRS) that the ``geometry`` and ``bbox`` fields represent
|
|
91
|
+
projjson : PROJJSON dict representing the
|
|
92
|
+
Coordinate Reference System (CRS) that the ``geometry`` and ``bbox``
|
|
93
|
+
fields represent
|
|
94
|
+
geometry : GeoJSON Polygon dict that defines the footprint of
|
|
95
|
+
this Item.
|
|
96
|
+
bbox : Bounding box of the Item in the asset CRS in
|
|
97
|
+
2 or 3 dimensions.
|
|
98
|
+
centroid : A dict with members 'lat' and 'lon' that defines
|
|
99
|
+
coordinates representing the centroid of the item in the asset data CRS.
|
|
100
|
+
Coordinates are defined in latitude and longitude, even if the data
|
|
101
|
+
coordinate system may not use lat/long.
|
|
102
|
+
shape : Number of pixels in Y and X directions for the
|
|
103
|
+
default grid.
|
|
104
|
+
transform : The affine transformation coefficients for
|
|
105
|
+
the default grid
|
|
106
|
+
"""
|
|
107
|
+
if epsg is not None and code is not None:
|
|
108
|
+
raise KeyError(
|
|
109
|
+
"Only one of the options ``code`` and ``epsg`` should be specified."
|
|
110
|
+
)
|
|
111
|
+
elif epsg:
|
|
112
|
+
self.epsg = epsg
|
|
113
|
+
else:
|
|
114
|
+
self.code = code
|
|
115
|
+
|
|
116
|
+
self.wkt2 = wkt2
|
|
117
|
+
self.projjson = projjson
|
|
118
|
+
self.geometry = geometry
|
|
119
|
+
self.bbox = bbox
|
|
120
|
+
self.centroid = centroid
|
|
121
|
+
self.shape = shape
|
|
122
|
+
self.transform = transform
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def epsg(self) -> int | None:
|
|
126
|
+
"""Get or sets the EPSG code of the datasource.
|
|
127
|
+
|
|
128
|
+
A Coordinate Reference System (CRS) is the data reference system (sometimes
|
|
129
|
+
called a 'projection') used by the asset data, and can usually be referenced
|
|
130
|
+
using an `EPSG code <https://epsg.io/>`_.
|
|
131
|
+
If the asset data does not have a CRS, such as in the case of non-rectified
|
|
132
|
+
imagery with Ground Control Points, ``epsg`` should be set to ``None``.
|
|
133
|
+
It should also be set to ``None`` if a CRS exists, but for which there is no
|
|
134
|
+
valid EPSG code.
|
|
135
|
+
"""
|
|
136
|
+
if self.code is not None and self.code.startswith("EPSG:"):
|
|
137
|
+
return int(self.code.replace("EPSG:", ""))
|
|
138
|
+
elif epsg := self._get_property(
|
|
139
|
+
EPSG_PROP, int
|
|
140
|
+
): # In case the dictionary was not migrated
|
|
141
|
+
return epsg
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
@epsg.setter
|
|
145
|
+
def epsg(self, v: int | None) -> None:
|
|
146
|
+
if v is None:
|
|
147
|
+
self.code = None
|
|
148
|
+
else:
|
|
149
|
+
self.code = f"EPSG:{v}"
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def code(self) -> str | None:
|
|
153
|
+
"""Get or set the code of the datasource.
|
|
154
|
+
|
|
155
|
+
Added in version 2.0.0 of this extension replacing "proj:epsg".
|
|
156
|
+
|
|
157
|
+
Projection codes are identified by a string. The `proj <https://proj.org/>`_
|
|
158
|
+
library defines projections using "authority:code", e.g., "EPSG:4326" or
|
|
159
|
+
"IAU_2015:30100". Different projection authorities may define different
|
|
160
|
+
string formats.
|
|
161
|
+
"""
|
|
162
|
+
return self._get_property(CODE_PROP, str)
|
|
163
|
+
|
|
164
|
+
@code.setter
|
|
165
|
+
def code(self, v: str | None) -> None:
|
|
166
|
+
self._set_property(CODE_PROP, v, pop_if_none=False)
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def wkt2(self) -> str | None:
|
|
170
|
+
"""Get or sets the WKT2 string representing the Coordinate Reference System
|
|
171
|
+
(CRS) that the ``proj:geometry`` and ``proj:bbox`` fields represent
|
|
172
|
+
|
|
173
|
+
This value is a
|
|
174
|
+
`WKT2 string <https://docs.opengeospatial.org/is/12-063r5/12-063r5.html>`_.
|
|
175
|
+
If the data does not have a CRS, such as in the case of non-rectified imagery
|
|
176
|
+
with Ground Control Points, ``wkt2`` should be set to ``None``. It should also
|
|
177
|
+
be set to ``None`` if a CRS exists, but for which a WKT2 string does not exist.
|
|
178
|
+
"""
|
|
179
|
+
return self._get_property(WKT2_PROP, str)
|
|
180
|
+
|
|
181
|
+
@wkt2.setter
|
|
182
|
+
def wkt2(self, v: str | None) -> None:
|
|
183
|
+
self._set_property(WKT2_PROP, v)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def projjson(self) -> dict[str, Any] | None:
|
|
187
|
+
"""Get or sets the PROJJSON string representing the Coordinate Reference System
|
|
188
|
+
(CRS) that the ``proj:geometry`` and ``proj:bbox`` fields represent
|
|
189
|
+
|
|
190
|
+
This value is a
|
|
191
|
+
`PROJJSON object <https://proj.org/specifications/projjson.html>`_.
|
|
192
|
+
If the data does not have a CRS, such as in the case of non-rectified imagery
|
|
193
|
+
with Ground Control Points, ``projjson`` should be set to ``None``. It should
|
|
194
|
+
also be set to ``None`` if a CRS exists, but for which a PROJJSON string does
|
|
195
|
+
not exist.
|
|
196
|
+
|
|
197
|
+
The schema for this object can be found
|
|
198
|
+
`here <https://proj.org/schemas/v0.2/projjson.schema.json>`_.
|
|
199
|
+
"""
|
|
200
|
+
return self._get_property(PROJJSON_PROP, dict[str, Any])
|
|
201
|
+
|
|
202
|
+
@projjson.setter
|
|
203
|
+
def projjson(self, v: dict[str, Any] | None) -> None:
|
|
204
|
+
self._set_property(PROJJSON_PROP, v)
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def crs_string(self) -> str | None:
|
|
208
|
+
"""Returns the coordinate reference system (CRS) string for the extension.
|
|
209
|
+
|
|
210
|
+
This string can be used to feed, e.g., ``rasterio.crs.CRS.from_string``.
|
|
211
|
+
The string is determined by the following heuristic:
|
|
212
|
+
|
|
213
|
+
1. If a code is set, return the code string, else
|
|
214
|
+
2. If wkt2 is set, return the WKT string, else,
|
|
215
|
+
3. If projjson is set, return the projjson as a string, else,
|
|
216
|
+
4. Return None
|
|
217
|
+
"""
|
|
218
|
+
if self.code:
|
|
219
|
+
return self.code
|
|
220
|
+
elif self.epsg:
|
|
221
|
+
return f"EPSG:{self.epsg}"
|
|
222
|
+
elif self.wkt2:
|
|
223
|
+
return self.wkt2
|
|
224
|
+
elif self.projjson:
|
|
225
|
+
import json
|
|
226
|
+
|
|
227
|
+
return json.dumps(self.projjson)
|
|
228
|
+
else:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def geometry(self) -> dict[str, Any] | None:
|
|
233
|
+
"""Get or sets a Polygon GeoJSON dict representing the footprint of this item.
|
|
234
|
+
|
|
235
|
+
This dict should be formatted according the Polygon object format specified in
|
|
236
|
+
`RFC 7946, sections 3.1.6 <https://tools.ietf.org/html/rfc7946>`_,
|
|
237
|
+
except not necessarily in EPSG:4326 as required by RFC7946. Specified based on
|
|
238
|
+
the ``code``, ``projjson`` or ``wkt2`` fields (not necessarily EPSG:4326).
|
|
239
|
+
Ideally, this will be represented by a Polygon with five coordinates, as the
|
|
240
|
+
item in the asset data CRS should be a square aligned to the original CRS grid.
|
|
241
|
+
"""
|
|
242
|
+
return self._get_property(GEOM_PROP, dict[str, Any])
|
|
243
|
+
|
|
244
|
+
@geometry.setter
|
|
245
|
+
def geometry(self, v: dict[str, Any] | None) -> None:
|
|
246
|
+
self._set_property(GEOM_PROP, v)
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def bbox(self) -> list[float] | None:
|
|
250
|
+
"""Get or sets the bounding box of the assets represented by this item in the
|
|
251
|
+
asset data CRS.
|
|
252
|
+
|
|
253
|
+
Specified as 4 or 6 coordinates based on the CRS defined in the ``code``,
|
|
254
|
+
``projjson`` or ``wkt2`` properties. First two numbers are coordinates of the
|
|
255
|
+
lower left corner, followed by coordinates of upper right corner, e.g.,
|
|
256
|
+
``[west, south, east, north]``, ``[xmin, ymin, xmax, ymax]``,
|
|
257
|
+
``[left, down, right, up]``, or ``[west, south, lowest, east, north,
|
|
258
|
+
highest]``. The length of the array must be 2*n where n is the number of
|
|
259
|
+
dimensions.
|
|
260
|
+
"""
|
|
261
|
+
return self._get_property(BBOX_PROP, list[float])
|
|
262
|
+
|
|
263
|
+
@bbox.setter
|
|
264
|
+
def bbox(self, v: list[float] | None) -> None:
|
|
265
|
+
self._set_property(BBOX_PROP, v)
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def centroid(self) -> dict[str, float] | None:
|
|
269
|
+
"""Get or sets coordinates representing the centroid of the item in the asset
|
|
270
|
+
data CRS.
|
|
271
|
+
|
|
272
|
+
Coordinates are defined in latitude and longitude, even if the data coordinate
|
|
273
|
+
system does not use lat/long.
|
|
274
|
+
|
|
275
|
+
Example::
|
|
276
|
+
|
|
277
|
+
item.ext.proj.centroid = { 'lat': 0.0, 'lon': 0.0 }
|
|
278
|
+
"""
|
|
279
|
+
return self._get_property(CENTROID_PROP, dict[str, float])
|
|
280
|
+
|
|
281
|
+
@centroid.setter
|
|
282
|
+
def centroid(self, v: dict[str, float] | None) -> None:
|
|
283
|
+
self._set_property(CENTROID_PROP, v)
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def shape(self) -> list[int] | None:
|
|
287
|
+
"""Get or sets the number of pixels in Y and X directions for the default grid.
|
|
288
|
+
|
|
289
|
+
The shape is an array of integers that represents the number of pixels in the
|
|
290
|
+
most common pixel grid used by the item's assets. The number of pixels should
|
|
291
|
+
be specified in Y, X order. If the shape is defined in an item's properties it
|
|
292
|
+
is used as the default shape for all assets that don't have an overriding shape.
|
|
293
|
+
"""
|
|
294
|
+
return self._get_property(SHAPE_PROP, list[int])
|
|
295
|
+
|
|
296
|
+
@shape.setter
|
|
297
|
+
def shape(self, v: list[int] | None) -> None:
|
|
298
|
+
self._set_property(SHAPE_PROP, v)
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def transform(self) -> list[float] | None:
|
|
302
|
+
"""Get or sets the affine transformation coefficients for the default grid.
|
|
303
|
+
|
|
304
|
+
The transform is a linear mapping from pixel coordinate space (Pixel, Line) to
|
|
305
|
+
projection coordinate space (Xp, Yp). It is a 3x3 matrix stored as a flat array
|
|
306
|
+
of 9 elements in row major order. Since the last row is always 0,0,1 it can be
|
|
307
|
+
omitted, in which case only 6 elements are recorded. This mapping can be
|
|
308
|
+
obtained from GDAL `GetGeoTransform <https://gdal.org/api/gdaldataset_cpp.html\
|
|
309
|
+
#_CPPv4N11GDALDataset15GetGeoTransformEPd>`_
|
|
310
|
+
or the Rasterio `Transform <https://rasterio.readthedocs.io/en/stable/api\
|
|
311
|
+
/rasterio.io.html#rasterio.io.BufferedDatasetWriter.transform>`_.
|
|
312
|
+
"""
|
|
313
|
+
return self._get_property(TRANSFORM_PROP, list[float])
|
|
314
|
+
|
|
315
|
+
@transform.setter
|
|
316
|
+
def transform(self, v: list[float] | None) -> None:
|
|
317
|
+
self._set_property(TRANSFORM_PROP, v)
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def get_schema_uri(cls) -> str:
|
|
321
|
+
return SCHEMA_URI
|
|
322
|
+
|
|
323
|
+
@classmethod
|
|
324
|
+
def get_schema_uris(cls) -> list[str]:
|
|
325
|
+
import warnings
|
|
326
|
+
|
|
327
|
+
warnings.warn(
|
|
328
|
+
"get_schema_uris is deprecated and will be removed in v2",
|
|
329
|
+
DeprecationWarning,
|
|
330
|
+
)
|
|
331
|
+
return SCHEMA_URIS
|
|
332
|
+
|
|
333
|
+
@classmethod
|
|
334
|
+
def ext(cls, obj: T, add_if_missing: bool = False) -> ProjectionExtension[T]:
|
|
335
|
+
"""Extends the given STAC Object with properties from the :stac-ext:`Projection
|
|
336
|
+
Extension <projection>`.
|
|
337
|
+
|
|
338
|
+
This extension can be applied to instances of :class:`~pystac.Item` or
|
|
339
|
+
:class:`~pystac.Asset`.
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
|
|
343
|
+
pystac.ExtensionTypeError : If an invalid object type is passed.
|
|
344
|
+
"""
|
|
345
|
+
if isinstance(obj, pystac.Item):
|
|
346
|
+
cls.ensure_has_extension(obj, add_if_missing)
|
|
347
|
+
return cast(ProjectionExtension[T], ItemProjectionExtension(obj))
|
|
348
|
+
elif isinstance(obj, pystac.Asset):
|
|
349
|
+
cls.ensure_owner_has_extension(obj, add_if_missing)
|
|
350
|
+
return cast(ProjectionExtension[T], AssetProjectionExtension(obj))
|
|
351
|
+
elif isinstance(obj, pystac.ItemAssetDefinition):
|
|
352
|
+
cls.ensure_owner_has_extension(obj, add_if_missing)
|
|
353
|
+
return cast(ProjectionExtension[T], ItemAssetsProjectionExtension(obj))
|
|
354
|
+
else:
|
|
355
|
+
raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def summaries(
|
|
359
|
+
cls, obj: pystac.Collection, add_if_missing: bool = False
|
|
360
|
+
) -> SummariesProjectionExtension:
|
|
361
|
+
"""Returns the extended summaries object for the given collection."""
|
|
362
|
+
cls.ensure_has_extension(obj, add_if_missing)
|
|
363
|
+
return SummariesProjectionExtension(obj)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class ItemProjectionExtension(ProjectionExtension[pystac.Item]):
|
|
367
|
+
"""A concrete implementation of :class:`ProjectionExtension` on an
|
|
368
|
+
:class:`~pystac.Item` that extends the properties of the Item to include properties
|
|
369
|
+
defined in the :stac-ext:`Projection Extension <projection>`.
|
|
370
|
+
|
|
371
|
+
This class should generally not be instantiated directly. Instead, call
|
|
372
|
+
:meth:`ProjectionExtension.ext` on an :class:`~pystac.Item` to extend it.
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
item: pystac.Item
|
|
376
|
+
"""The :class:`~pystac.Item` being extended."""
|
|
377
|
+
|
|
378
|
+
properties: dict[str, Any]
|
|
379
|
+
"""The :class:`~pystac.Item` properties, including extension properties."""
|
|
380
|
+
|
|
381
|
+
def __init__(self, item: pystac.Item):
|
|
382
|
+
self.item = item
|
|
383
|
+
self.properties = item.properties
|
|
384
|
+
|
|
385
|
+
def __repr__(self) -> str:
|
|
386
|
+
return f"<ItemProjectionExtension Item id={self.item.id}>"
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class AssetProjectionExtension(ProjectionExtension[pystac.Asset]):
|
|
390
|
+
"""A concrete implementation of :class:`ProjectionExtension` on an
|
|
391
|
+
:class:`~pystac.Asset` that extends the Asset fields to include properties defined
|
|
392
|
+
in the :stac-ext:`Projection Extension <projection>`.
|
|
393
|
+
|
|
394
|
+
This class should generally not be instantiated directly. Instead, call
|
|
395
|
+
:meth:`ProjectionExtension.ext` on an :class:`~pystac.Asset` to extend it.
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
asset_href: str
|
|
399
|
+
"""The ``href`` value of the :class:`~pystac.Asset` being extended."""
|
|
400
|
+
|
|
401
|
+
properties: dict[str, Any]
|
|
402
|
+
"""The :class:`~pystac.Asset` fields, including extension properties."""
|
|
403
|
+
|
|
404
|
+
additional_read_properties: Iterable[dict[str, Any]] | None = None
|
|
405
|
+
"""If present, this will be a list containing 1 dictionary representing the
|
|
406
|
+
properties of the owning :class:`~pystac.Item`."""
|
|
407
|
+
|
|
408
|
+
def __init__(self, asset: pystac.Asset):
|
|
409
|
+
self.asset_href = asset.href
|
|
410
|
+
self.properties = asset.extra_fields
|
|
411
|
+
if asset.owner and isinstance(asset.owner, pystac.Item):
|
|
412
|
+
self.additional_read_properties = [asset.owner.properties]
|
|
413
|
+
|
|
414
|
+
def __repr__(self) -> str:
|
|
415
|
+
return f"<AssetProjectionExtension Asset href={self.asset_href}>"
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class ItemAssetsProjectionExtension(ProjectionExtension[pystac.ItemAssetDefinition]):
|
|
419
|
+
properties: dict[str, Any]
|
|
420
|
+
asset_defn: pystac.ItemAssetDefinition
|
|
421
|
+
|
|
422
|
+
def __init__(self, item_asset: pystac.ItemAssetDefinition):
|
|
423
|
+
self.asset_defn = item_asset
|
|
424
|
+
self.properties = item_asset.properties
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class SummariesProjectionExtension(SummariesExtension):
|
|
428
|
+
"""A concrete implementation of :class:`~pystac.extensions.base.SummariesExtension`
|
|
429
|
+
that extends the ``summaries`` field of a :class:`~pystac.Collection` to include
|
|
430
|
+
properties defined in the :stac-ext:`Projection Extension <projection>`.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def code(self) -> list[str] | None:
|
|
435
|
+
"""Get or sets the summary of :attr:`ProjectionExtension.code` values
|
|
436
|
+
for this Collection.
|
|
437
|
+
"""
|
|
438
|
+
return self.summaries.get_list(CODE_PROP)
|
|
439
|
+
|
|
440
|
+
@code.setter
|
|
441
|
+
def code(self, v: list[str] | None) -> None:
|
|
442
|
+
self._set_summary(CODE_PROP, v)
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def epsg(self) -> list[int] | None:
|
|
446
|
+
"""Get the summary of :attr:`ProjectionExtension.epsg` values
|
|
447
|
+
for this Collection.
|
|
448
|
+
"""
|
|
449
|
+
if self.code is None:
|
|
450
|
+
return None
|
|
451
|
+
return [int(code.replace("EPSG:", "")) for code in self.code if "EPSG:" in code]
|
|
452
|
+
|
|
453
|
+
@epsg.setter
|
|
454
|
+
def epsg(self, v: list[int] | None) -> None:
|
|
455
|
+
if v is None:
|
|
456
|
+
self.code = None
|
|
457
|
+
else:
|
|
458
|
+
self.code = [f"EPSG:{epsg}" for epsg in v]
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class ProjectionExtensionHooks(ExtensionHooks):
|
|
462
|
+
schema_uri: str = SCHEMA_URI
|
|
463
|
+
prev_extension_ids = {
|
|
464
|
+
"proj",
|
|
465
|
+
"projection",
|
|
466
|
+
*[uri for uri in SCHEMA_URIS if uri != SCHEMA_URI],
|
|
467
|
+
}
|
|
468
|
+
pre_2 = {
|
|
469
|
+
"proj",
|
|
470
|
+
"projection",
|
|
471
|
+
"https://stac-extensions.github.io/projection/v1.0.0/schema.json",
|
|
472
|
+
"https://stac-extensions.github.io/projection/v1.1.0/schema.json",
|
|
473
|
+
"https://stac-extensions.github.io/projection/v1.2.0/schema.json",
|
|
474
|
+
}
|
|
475
|
+
stac_object_types = {pystac.STACObjectType.ITEM}
|
|
476
|
+
|
|
477
|
+
def migrate(
|
|
478
|
+
self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription
|
|
479
|
+
) -> None:
|
|
480
|
+
if not self.has_extension(obj):
|
|
481
|
+
return
|
|
482
|
+
|
|
483
|
+
import warnings
|
|
484
|
+
|
|
485
|
+
# proj:epsg moved to proj:code
|
|
486
|
+
if epsg := obj["properties"].pop("proj:epsg", None):
|
|
487
|
+
if obj["properties"].get("proj:code", None) is None:
|
|
488
|
+
obj["properties"]["proj:code"] = f"EPSG:{epsg}"
|
|
489
|
+
elif not obj["properties"]["proj:code"] == f"EPSG:{epsg}":
|
|
490
|
+
warnings.warn(
|
|
491
|
+
"Both proj:code and proj:epsg are specified and they have "
|
|
492
|
+
"conflicting values. This might lead to surprising behavior.",
|
|
493
|
+
UserWarning,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
for key in ["assets", "item_assets"]:
|
|
497
|
+
for asset in obj.get(key, {}).values():
|
|
498
|
+
if epsg := asset.pop("proj:epsg", None):
|
|
499
|
+
if asset.get("proj:code", None) is None:
|
|
500
|
+
asset["proj:code"] = f"EPSG:{epsg}"
|
|
501
|
+
elif not asset["proj:code"] == f"EPSG:{epsg}":
|
|
502
|
+
warnings.warn(
|
|
503
|
+
"Both proj:code and proj:epsg are specified and they "
|
|
504
|
+
"have conflicting values. This might lead to surprising "
|
|
505
|
+
"behavior.",
|
|
506
|
+
UserWarning,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
super().migrate(obj, version, info)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
PROJECTION_EXTENSION_HOOKS: ExtensionHooks = ProjectionExtensionHooks()
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pystac-ext-projection
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Projection extension for PySTAC
|
|
5
|
+
Project-URL: Documentation, https://pystac.readthedocs.io
|
|
6
|
+
Project-URL: Repository, https://github.com/stac-utils/pystac
|
|
7
|
+
Project-URL: Issues, https://github.com/stac-utils/pystac/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Discussions, https://github.com/radiantearth/stac-spec/discussions/categories/stac-software
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: STAC,catalog,imagery,projection,pystac,raster
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: pystac-core
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# pystac-ext-projection
|
|
26
|
+
|
|
27
|
+
[PySTAC](https://pypi.org/project/pystac/) extension package for the [Projection Extension](https://github.com/stac-extensions/projection).
|
|
28
|
+
This extension provides fields for describing the geospatial projection of data, including EPSG code, WKT2, PROJJSON, geometry, bounding box, centroid, shape, and transform.
|
|
29
|
+
|
|
30
|
+
## Supported versions
|
|
31
|
+
|
|
32
|
+
- [v2.0.0](https://stac-extensions.github.io/projection/v2.0.0/schema.json)
|
|
33
|
+
- [v1.2.0](https://stac-extensions.github.io/projection/v1.2.0/schema.json)
|
|
34
|
+
- [v1.1.0](https://stac-extensions.github.io/projection/v1.1.0/schema.json)
|
|
35
|
+
- [v1.0.0](https://stac-extensions.github.io/projection/v1.0.0/schema.json)
|
|
36
|
+
|
|
37
|
+
## Versioning
|
|
38
|
+
|
|
39
|
+
This package's version corresponds to the version of the extension specification it targets.
|
|
40
|
+
When we release updates to the package code without changing the target extension version, we use [post releases](https://packaging.python.org/en/latest/discussions/versioning/#post-releases), e.g. `2.0.0.post1`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
pystac/extensions/projection.py,sha256=TYlbQ_Rmsy7_3u0lb80Rnmujirsh_yWMox4ocOjiFUo,19845
|
|
2
|
+
pystac/extensions/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pystac_ext_projection-2.0.0.dist-info/METADATA,sha256=QafS7YKIGqx9lFVZJ7SaQoxRsHsIq3-ehEV6L0XzqFs,2092
|
|
4
|
+
pystac_ext_projection-2.0.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
pystac_ext_projection-2.0.0.dist-info/RECORD,,
|