2222 SummariesExtension ,
2323)
2424from pystac .extensions .hooks import ExtensionHooks
25+ from pystac .serialization .identify import STACJSONDescription , STACVersionID
2526
2627T = TypeVar ("T" , pystac .Item , pystac .Asset , item_assets .AssetDefinition )
2728
28- SCHEMA_URI : str = "https://stac-extensions.github.io/projection/v1.1 .0/schema.json"
29+ SCHEMA_URI : str = "https://stac-extensions.github.io/projection/v2.0 .0/schema.json"
2930SCHEMA_URIS : list [str ] = [
3031 "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ,
32+ "https://stac-extensions.github.io/projection/v1.1.0/schema.json" ,
3133 SCHEMA_URI ,
3234]
3335PREFIX : str = "proj:"
3436
3537# Field names
38+ CODE_PROP : str = PREFIX + "code"
3639EPSG_PROP : str = PREFIX + "epsg"
3740WKT2_PROP : str = PREFIX + "wkt2"
3841PROJJSON_PROP : str = PREFIX + "projjson"
@@ -66,7 +69,9 @@ class ProjectionExtension(
6669
6770 def apply (
6871 self ,
69- epsg : int | None ,
72+ * ,
73+ epsg : int | None = None ,
74+ code : str | None = None ,
7075 wkt2 : str | None = None ,
7176 projjson : dict [str , Any ] | None = None ,
7277 geometry : dict [str , Any ] | None = None ,
@@ -78,7 +83,10 @@ def apply(
7883 """Applies Projection extension properties to the extended Item.
7984
8085 Args:
81- epsg : REQUIRED. EPSG code of the datasource.
86+ epsg : Code of the datasource. Example: 4326. One of ``code`` and
87+ ``epsg`` must be provided.
88+ code : Code of the datasource. Example: "EPSG:4326". One of ``code`` and
89+ ``epsg`` must be provided.
8290 wkt2 : WKT2 string representing the Coordinate Reference
8391 System (CRS) that the ``geometry`` and ``bbox`` fields represent
8492 projjson : PROJJSON dict representing the
@@ -97,7 +105,15 @@ def apply(
97105 transform : The affine transformation coefficients for
98106 the default grid
99107 """
100- self .epsg = epsg
108+ if epsg is not None and code is not None :
109+ raise KeyError (
110+ "Only one of the options ``code`` and ``epsg`` should be specified."
111+ )
112+ elif epsg :
113+ self .epsg = epsg
114+ else :
115+ self .code = code
116+
101117 self .wkt2 = wkt2
102118 self .projjson = projjson
103119 self .geometry = geometry
@@ -118,11 +134,34 @@ def epsg(self) -> int | None:
118134 It should also be set to ``None`` if a CRS exists, but for which there is no
119135 valid EPSG code.
120136 """
137+ if self .code is not None and self .code .startswith ("EPSG:" ):
138+ return int (self .code .replace ("EPSG:" , "" ))
121139 return self ._get_property (EPSG_PROP , int )
122140
123141 @epsg .setter
124142 def epsg (self , v : int | None ) -> None :
125- self ._set_property (EPSG_PROP , v , pop_if_none = False )
143+ self ._set_property (EPSG_PROP , None )
144+ if v is None :
145+ self .code = None
146+ else :
147+ self .code = f"EPSG:{ v } "
148+
149+ @property
150+ def code (self ) -> str | None :
151+ """Get or set the code of the datasource.
152+
153+ Added in version 2.0.0 of this extension replacing "proj:epsg".
154+
155+ Projection codes are identified by a string. The `proj <https://proj.org/>`_
156+ library defines projections using "authority:code", e.g., "EPSG:4326" or
157+ "IAU_2015:30100". Different projection authorities may define different
158+ string formats.
159+ """
160+ return self ._get_property (CODE_PROP , str )
161+
162+ @code .setter
163+ def code (self , v : int | None ) -> None :
164+ self ._set_property (CODE_PROP , v , pop_if_none = False )
126165
127166 @property
128167 def wkt2 (self ) -> str | None :
@@ -169,13 +208,13 @@ def crs_string(self) -> str | None:
169208 This string can be used to feed, e.g., ``rasterio.crs.CRS.from_string``.
170209 The string is determined by the following heuristic:
171210
172- 1. If an EPSG code is set, return "EPSG:{ code}" , else
211+ 1. If a code is set, return the code string , else
173212 2. If wkt2 is set, return the WKT string, else,
174213 3. If projjson is set, return the projjson as a string, else,
175214 4. Return None
176215 """
177- if self .epsg :
178- return f"EPSG: { self .epsg } "
216+ if self .code :
217+ return self .code
179218 elif self .wkt2 :
180219 return self .wkt2
181220 elif self .projjson :
@@ -190,7 +229,7 @@ def geometry(self) -> dict[str, Any] | None:
190229 This dict should be formatted according the Polygon object format specified in
191230 `RFC 7946, sections 3.1.6 <https://tools.ietf.org/html/rfc7946>`_,
192231 except not necessarily in EPSG:4326 as required by RFC7946. Specified based on
193- the ``epsg ``, ``projjson`` or ``wkt2`` fields (not necessarily EPSG:4326).
232+ the ``code ``, ``projjson`` or ``wkt2`` fields (not necessarily EPSG:4326).
194233 Ideally, this will be represented by a Polygon with five coordinates, as the
195234 item in the asset data CRS should be a square aligned to the original CRS grid.
196235 """
@@ -205,7 +244,7 @@ def bbox(self) -> list[float] | None:
205244 """Get or sets the bounding box of the assets represented by this item in the
206245 asset data CRS.
207246
208- Specified as 4 or 6 coordinates based on the CRS defined in the ``epsg ``,
247+ Specified as 4 or 6 coordinates based on the CRS defined in the ``code ``,
209248 ``projjson`` or ``wkt2`` properties. First two numbers are coordinates of the
210249 lower left corner, followed by coordinates of upper right corner, e.g.,
211250 ``[west, south, east, north]``, ``[xmin, ymin, xmax, ymax]``,
@@ -383,16 +422,32 @@ class SummariesProjectionExtension(SummariesExtension):
383422 defined in the :stac-ext:`Projection Extension <projection>`.
384423 """
385424
425+ @property
426+ def code (self ) -> list [str ] | None :
427+ """Get or sets the summary of :attr:`ProjectionExtension.code` values
428+ for this Collection.
429+ """
430+ return self .summaries .get_list (CODE_PROP )
431+
432+ @code .setter
433+ def code (self , v : list [str ] | None ) -> None :
434+ self ._set_summary (CODE_PROP , v )
435+
386436 @property
387437 def epsg (self ) -> list [int ] | None :
388- """Get or sets the summary of :attr:`ProjectionExtension.epsg` values
438+ """Get the summary of :attr:`ProjectionExtension.epsg` values
389439 for this Collection.
390440 """
391- return self .summaries .get_list (EPSG_PROP )
441+ if self .code is None :
442+ return None
443+ return [int (code .replace ("EPSG:" , "" )) for code in self .code if "EPSG:" in code ]
392444
393445 @epsg .setter
394446 def epsg (self , v : list [int ] | None ) -> None :
395- self ._set_summary (EPSG_PROP , v )
447+ if v is None :
448+ self .code = None
449+ else :
450+ self .code = [f"EPSG:{ epsg } " for epsg in v ]
396451
397452
398453class ProjectionExtensionHooks (ExtensionHooks ):
@@ -402,7 +457,27 @@ class ProjectionExtensionHooks(ExtensionHooks):
402457 "projection" ,
403458 * [uri for uri in SCHEMA_URIS if uri != SCHEMA_URI ],
404459 }
460+ pre_2 = {
461+ "proj" ,
462+ "projection" ,
463+ "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ,
464+ "https://stac-extensions.github.io/projection/v1.1.0/schema.json" ,
465+ }
405466 stac_object_types = {pystac .STACObjectType .ITEM }
406467
468+ def migrate (
469+ self , obj : dict [str , Any ], version : STACVersionID , info : STACJSONDescription
470+ ) -> None :
471+ if not self .has_extension (obj ):
472+ return
473+
474+ # proj:epsg moved to proj:code
475+ if "proj:epsg" in obj ["properties" ]:
476+ epsg = obj ["properties" ]["proj:epsg" ]
477+ obj ["properties" ]["proj:code" ] = f"EPSG:{ epsg } "
478+ del obj ["properties" ]["proj:epsg" ]
479+
480+ super ().migrate (obj , version , info )
481+
407482
408483PROJECTION_EXTENSION_HOOKS : ExtensionHooks = ProjectionExtensionHooks ()
0 commit comments