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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any