2121 SummariesExtension ,
2222)
2323from pystac .extensions .hooks import ExtensionHooks
24+ from pystac .serialization .identify import STACJSONDescription , STACVersionID
2425
2526T = TypeVar ("T" , pystac .Item , pystac .Asset , pystac .ItemAssetDefinition )
2627
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"
2829SCHEMA_URIS : list [str ] = [
2930 "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ,
31+ "https://stac-extensions.github.io/projection/v1.1.0/schema.json" ,
3032 SCHEMA_URI ,
3133]
3234PREFIX : str = "proj:"
3335
3436# Field names
37+ CODE_PROP : str = PREFIX + "code"
3538EPSG_PROP : str = PREFIX + "epsg"
3639WKT2_PROP : str = PREFIX + "wkt2"
3740PROJJSON_PROP : str = PREFIX + "projjson"
@@ -65,7 +68,9 @@ class ProjectionExtension(
6568
6669 def apply (
6770 self ,
68- epsg : int | None ,
71+ * ,
72+ epsg : int | None = None ,
73+ code : str | None = None ,
6974 wkt2 : str | None = None ,
7075 projjson : dict [str , Any ] | None = None ,
7176 geometry : dict [str , Any ] | None = None ,
@@ -77,7 +82,10 @@ def apply(
7782 """Applies Projection extension properties to the extended Item.
7883
7984 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.
8189 wkt2 : WKT2 string representing the Coordinate Reference
8290 System (CRS) that the ``geometry`` and ``bbox`` fields represent
8391 projjson : PROJJSON dict representing the
@@ -96,7 +104,15 @@ def apply(
96104 transform : The affine transformation coefficients for
97105 the default grid
98106 """
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+
100116 self .wkt2 = wkt2
101117 self .projjson = projjson
102118 self .geometry = geometry
@@ -117,11 +133,33 @@ def epsg(self) -> int | None:
117133 It should also be set to ``None`` if a CRS exists, but for which there is no
118134 valid EPSG code.
119135 """
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
121139
122140 @epsg .setter
123141 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 )
125163
126164 @property
127165 def wkt2 (self ) -> str | None :
@@ -168,13 +206,13 @@ def crs_string(self) -> str | None:
168206 This string can be used to feed, e.g., ``rasterio.crs.CRS.from_string``.
169207 The string is determined by the following heuristic:
170208
171- 1. If an EPSG code is set, return "EPSG:{ code}" , else
209+ 1. If a code is set, return the code string , else
172210 2. If wkt2 is set, return the WKT string, else,
173211 3. If projjson is set, return the projjson as a string, else,
174212 4. Return None
175213 """
176- if self .epsg :
177- return f"EPSG: { self .epsg } "
214+ if self .code :
215+ return self .code
178216 elif self .wkt2 :
179217 return self .wkt2
180218 elif self .projjson :
@@ -189,7 +227,7 @@ def geometry(self) -> dict[str, Any] | None:
189227 This dict should be formatted according the Polygon object format specified in
190228 `RFC 7946, sections 3.1.6 <https://tools.ietf.org/html/rfc7946>`_,
191229 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).
193231 Ideally, this will be represented by a Polygon with five coordinates, as the
194232 item in the asset data CRS should be a square aligned to the original CRS grid.
195233 """
@@ -204,7 +242,7 @@ def bbox(self) -> list[float] | None:
204242 """Get or sets the bounding box of the assets represented by this item in the
205243 asset data CRS.
206244
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 ``,
208246 ``projjson`` or ``wkt2`` properties. First two numbers are coordinates of the
209247 lower left corner, followed by coordinates of upper right corner, e.g.,
210248 ``[west, south, east, north]``, ``[xmin, ymin, xmax, ymax]``,
@@ -382,16 +420,32 @@ class SummariesProjectionExtension(SummariesExtension):
382420 defined in the :stac-ext:`Projection Extension <projection>`.
383421 """
384422
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+
385434 @property
386435 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
388437 for this Collection.
389438 """
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 ]
391442
392443 @epsg .setter
393444 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 ]
395449
396450
397451class ProjectionExtensionHooks (ExtensionHooks ):
@@ -401,7 +455,27 @@ class ProjectionExtensionHooks(ExtensionHooks):
401455 "projection" ,
402456 * [uri for uri in SCHEMA_URIS if uri != SCHEMA_URI ],
403457 }
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+ }
404464 stac_object_types = {pystac .STACObjectType .ITEM }
405465
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+
406480
407481PROJECTION_EXTENSION_HOOKS : ExtensionHooks = ProjectionExtensionHooks ()
0 commit comments