21
21
SummariesExtension ,
22
22
)
23
23
from pystac .extensions .hooks import ExtensionHooks
24
+ from pystac .serialization .identify import STACJSONDescription , STACVersionID
24
25
25
26
T = TypeVar ("T" , pystac .Item , pystac .Asset , pystac .ItemAssetDefinition )
26
27
27
- SCHEMA_URI : str = "https://stac-extensions.github.io/projection/v1.1 .0/schema.json"
28
+ SCHEMA_URI : str = "https://stac-extensions.github.io/projection/v2.0 .0/schema.json"
28
29
SCHEMA_URIS : list [str ] = [
29
30
"https://stac-extensions.github.io/projection/v1.0.0/schema.json" ,
31
+ "https://stac-extensions.github.io/projection/v1.1.0/schema.json" ,
30
32
SCHEMA_URI ,
31
33
]
32
34
PREFIX : str = "proj:"
33
35
34
36
# Field names
37
+ CODE_PROP : str = PREFIX + "code"
35
38
EPSG_PROP : str = PREFIX + "epsg"
36
39
WKT2_PROP : str = PREFIX + "wkt2"
37
40
PROJJSON_PROP : str = PREFIX + "projjson"
@@ -65,7 +68,9 @@ class ProjectionExtension(
65
68
66
69
def apply (
67
70
self ,
68
- epsg : int | None ,
71
+ * ,
72
+ epsg : int | None = None ,
73
+ code : str | None = None ,
69
74
wkt2 : str | None = None ,
70
75
projjson : dict [str , Any ] | None = None ,
71
76
geometry : dict [str , Any ] | None = None ,
@@ -77,7 +82,10 @@ def apply(
77
82
"""Applies Projection extension properties to the extended Item.
78
83
79
84
Args:
80
- epsg : REQUIRED. EPSG code of the datasource.
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.
81
89
wkt2 : WKT2 string representing the Coordinate Reference
82
90
System (CRS) that the ``geometry`` and ``bbox`` fields represent
83
91
projjson : PROJJSON dict representing the
@@ -96,7 +104,15 @@ def apply(
96
104
transform : The affine transformation coefficients for
97
105
the default grid
98
106
"""
99
- self .epsg = epsg
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
+
100
116
self .wkt2 = wkt2
101
117
self .projjson = projjson
102
118
self .geometry = geometry
@@ -117,11 +133,33 @@ def epsg(self) -> int | None:
117
133
It should also be set to ``None`` if a CRS exists, but for which there is no
118
134
valid EPSG code.
119
135
"""
120
- return self ._get_property (EPSG_PROP , int )
136
+ if self .code is not None and self .code .startswith ("EPSG:" ):
137
+ return int (self .code .replace ("EPSG:" , "" ))
138
+ return None
121
139
122
140
@epsg .setter
123
141
def epsg (self , v : int | None ) -> None :
124
- self ._set_property (EPSG_PROP , v , pop_if_none = False )
142
+ if v is None :
143
+ self .code = None
144
+ else :
145
+ self .code = f"EPSG:{ v } "
146
+
147
+ @property
148
+ def code (self ) -> str | None :
149
+ """Get or set the code of the datasource.
150
+
151
+ Added in version 2.0.0 of this extension replacing "proj:epsg".
152
+
153
+ Projection codes are identified by a string. The `proj <https://proj.org/>`_
154
+ library defines projections using "authority:code", e.g., "EPSG:4326" or
155
+ "IAU_2015:30100". Different projection authorities may define different
156
+ string formats.
157
+ """
158
+ return self ._get_property (CODE_PROP , str )
159
+
160
+ @code .setter
161
+ def code (self , v : int | None ) -> None :
162
+ self ._set_property (CODE_PROP , v , pop_if_none = False )
125
163
126
164
@property
127
165
def wkt2 (self ) -> str | None :
@@ -168,13 +206,13 @@ def crs_string(self) -> str | None:
168
206
This string can be used to feed, e.g., ``rasterio.crs.CRS.from_string``.
169
207
The string is determined by the following heuristic:
170
208
171
- 1. If an EPSG code is set, return "EPSG:{ code}" , else
209
+ 1. If a code is set, return the code string , else
172
210
2. If wkt2 is set, return the WKT string, else,
173
211
3. If projjson is set, return the projjson as a string, else,
174
212
4. Return None
175
213
"""
176
- if self .epsg :
177
- return f"EPSG: { self .epsg } "
214
+ if self .code :
215
+ return self .code
178
216
elif self .wkt2 :
179
217
return self .wkt2
180
218
elif self .projjson :
@@ -189,7 +227,7 @@ def geometry(self) -> dict[str, Any] | None:
189
227
This dict should be formatted according the Polygon object format specified in
190
228
`RFC 7946, sections 3.1.6 <https://tools.ietf.org/html/rfc7946>`_,
191
229
except not necessarily in EPSG:4326 as required by RFC7946. Specified based on
192
- the ``epsg ``, ``projjson`` or ``wkt2`` fields (not necessarily EPSG:4326).
230
+ the ``code ``, ``projjson`` or ``wkt2`` fields (not necessarily EPSG:4326).
193
231
Ideally, this will be represented by a Polygon with five coordinates, as the
194
232
item in the asset data CRS should be a square aligned to the original CRS grid.
195
233
"""
@@ -204,7 +242,7 @@ def bbox(self) -> list[float] | None:
204
242
"""Get or sets the bounding box of the assets represented by this item in the
205
243
asset data CRS.
206
244
207
- Specified as 4 or 6 coordinates based on the CRS defined in the ``epsg ``,
245
+ Specified as 4 or 6 coordinates based on the CRS defined in the ``code ``,
208
246
``projjson`` or ``wkt2`` properties. First two numbers are coordinates of the
209
247
lower left corner, followed by coordinates of upper right corner, e.g.,
210
248
``[west, south, east, north]``, ``[xmin, ymin, xmax, ymax]``,
@@ -382,16 +420,32 @@ class SummariesProjectionExtension(SummariesExtension):
382
420
defined in the :stac-ext:`Projection Extension <projection>`.
383
421
"""
384
422
423
+ @property
424
+ def code (self ) -> list [str ] | None :
425
+ """Get or sets the summary of :attr:`ProjectionExtension.code` values
426
+ for this Collection.
427
+ """
428
+ return self .summaries .get_list (CODE_PROP )
429
+
430
+ @code .setter
431
+ def code (self , v : list [str ] | None ) -> None :
432
+ self ._set_summary (CODE_PROP , v )
433
+
385
434
@property
386
435
def epsg (self ) -> list [int ] | None :
387
- """Get or sets the summary of :attr:`ProjectionExtension.epsg` values
436
+ """Get the summary of :attr:`ProjectionExtension.epsg` values
388
437
for this Collection.
389
438
"""
390
- return self .summaries .get_list (EPSG_PROP )
439
+ if self .code is None :
440
+ return None
441
+ return [int (code .replace ("EPSG:" , "" )) for code in self .code if "EPSG:" in code ]
391
442
392
443
@epsg .setter
393
444
def epsg (self , v : list [int ] | None ) -> None :
394
- self ._set_summary (EPSG_PROP , v )
445
+ if v is None :
446
+ self .code = None
447
+ else :
448
+ self .code = [f"EPSG:{ epsg } " for epsg in v ]
395
449
396
450
397
451
class ProjectionExtensionHooks (ExtensionHooks ):
@@ -401,7 +455,27 @@ class ProjectionExtensionHooks(ExtensionHooks):
401
455
"projection" ,
402
456
* [uri for uri in SCHEMA_URIS if uri != SCHEMA_URI ],
403
457
}
458
+ pre_2 = {
459
+ "proj" ,
460
+ "projection" ,
461
+ "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ,
462
+ "https://stac-extensions.github.io/projection/v1.1.0/schema.json" ,
463
+ }
404
464
stac_object_types = {pystac .STACObjectType .ITEM }
405
465
466
+ def migrate (
467
+ self , obj : dict [str , Any ], version : STACVersionID , info : STACJSONDescription
468
+ ) -> None :
469
+ if not self .has_extension (obj ):
470
+ return
471
+
472
+ # proj:epsg moved to proj:code
473
+ if "proj:epsg" in obj ["properties" ]:
474
+ epsg = obj ["properties" ]["proj:epsg" ]
475
+ obj ["properties" ]["proj:code" ] = f"EPSG:{ epsg } "
476
+ del obj ["properties" ]["proj:epsg" ]
477
+
478
+ super ().migrate (obj , version , info )
479
+
406
480
407
481
PROJECTION_EXTENSION_HOOKS : ExtensionHooks = ProjectionExtensionHooks ()
0 commit comments