pystac-ext-mgrs 1.0.0__tar.gz

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,163 @@
1
+ *.pyc
2
+ *.egg-info
3
+ *.eggs
4
+ .DS_Store
5
+ data
6
+ config.json
7
+ stdout*
8
+ /integration*
9
+ .idea
10
+ .vscode
11
+ .actrc
12
+
13
+
14
+ # Sphinx documentation
15
+ .ipynb_checkpoints/
16
+
17
+ docs/tutorials/pystac-example*
18
+ docs/tutorials/spacenet-stac/
19
+ docs/tutorials/spacenet-cog-stac/
20
+ docs/tutorials/data/
21
+ docs/quickstart_stac/
22
+
23
+ # Byte-compiled / optimized / DLL files
24
+ __pycache__/
25
+ *.py[cod]
26
+ *$py.class
27
+
28
+ # C extensions
29
+ *.so
30
+
31
+ # Distribution / packaging
32
+ .Python
33
+ build/
34
+ develop-eggs/
35
+ dist/
36
+ downloads/
37
+ eggs/
38
+ .eggs/
39
+ lib/
40
+ lib64/
41
+ parts/
42
+ sdist/
43
+ var/
44
+ wheels/
45
+ share/python-wheels/
46
+ *.egg-info/
47
+ .installed.cfg
48
+ *.egg
49
+ MANIFEST
50
+
51
+ # PyInstaller
52
+ # Usually these files are written by a python script from a template
53
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
54
+ *.manifest
55
+ *.spec
56
+
57
+ # Installer logs
58
+ pip-log.txt
59
+ pip-delete-this-directory.txt
60
+
61
+ # Unit test / coverage reports
62
+ htmlcov/
63
+ .tox/
64
+ .nox/
65
+ .coverage
66
+ .coverage.*
67
+ .cache
68
+ nosetests.xml
69
+ coverage.xml
70
+ *.cover
71
+ *.py,cover
72
+ .hypothesis/
73
+ .pytest_cache/
74
+ cover/
75
+
76
+ # Translations
77
+ *.mo
78
+ *.pot
79
+
80
+ # Django stuff:
81
+ *.log
82
+ local_settings.py
83
+ db.sqlite3
84
+ db.sqlite3-journal
85
+
86
+ # Flask stuff:
87
+ instance/
88
+ .webassets-cache
89
+
90
+ # Scrapy stuff:
91
+ .scrapy
92
+
93
+ # Sphinx documentation
94
+ docs/_build/
95
+
96
+ # PyBuilder
97
+ .pybuilder/
98
+ target/
99
+
100
+ # Jupyter Notebook
101
+ .ipynb_checkpoints
102
+
103
+ # IPython
104
+ profile_default/
105
+ ipython_config.py
106
+
107
+ # pyenv
108
+ # For a library or package, you might want to ignore these files since the code is
109
+ # intended to run in multiple environments; otherwise, check them in:
110
+ # .python-version
111
+
112
+ # pipenv
113
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
114
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
115
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
116
+ # install all needed dependencies.
117
+ # Pipfile.lock
118
+
119
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
120
+ __pypackages__/
121
+
122
+ # Celery stuff
123
+ celerybeat-schedule
124
+ celerybeat.pid
125
+
126
+ # SageMath parsed files
127
+ *.sage.py
128
+
129
+ # Environments
130
+ .env
131
+ .venv
132
+ env/
133
+ venv/
134
+ ENV/
135
+ env.bak/
136
+ venv.bak/
137
+
138
+ # Spyder project settings
139
+ .spyderproject
140
+ .spyproject
141
+
142
+ # Rope project settings
143
+ .ropeproject
144
+
145
+ # mkdocs documentation
146
+ /site
147
+
148
+ # mypy
149
+ .mypy_cache/
150
+ .dmypy.json
151
+ dmypy.json
152
+
153
+ # Pyre type checker
154
+ .pyre/
155
+
156
+ # pytype static type analyzer
157
+ .pytype/
158
+
159
+ # Cython debug symbols
160
+ cython_debug/
161
+
162
+ # asv environments
163
+ .asv
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: pystac-ext-mgrs
3
+ Version: 1.0.0
4
+ Summary: MGRS 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,mgrs,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-mgrs
26
+
27
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [MGRS Extension](https://github.com/stac-extensions/mgrs).
28
+ This extension provides fields for describing data using the Military Grid Reference System (MGRS), including UTM zone, latitude band, and grid square identifiers.
29
+
30
+ ## Supported versions
31
+
32
+ - [v1.0.0](https://stac-extensions.github.io/mgrs/v1.0.0/schema.json)
33
+
34
+ ## Versioning
35
+
36
+ This package's version corresponds to the version of the extension specification it targets.
37
+ 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. `1.0.0.post1`.
@@ -0,0 +1,13 @@
1
+ # pystac-ext-mgrs
2
+
3
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [MGRS Extension](https://github.com/stac-extensions/mgrs).
4
+ This extension provides fields for describing data using the Military Grid Reference System (MGRS), including UTM zone, latitude band, and grid square identifiers.
5
+
6
+ ## Supported versions
7
+
8
+ - [v1.0.0](https://stac-extensions.github.io/mgrs/v1.0.0/schema.json)
9
+
10
+ ## Versioning
11
+
12
+ This package's version corresponds to the version of the extension specification it targets.
13
+ 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. `1.0.0.post1`.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "pystac-ext-mgrs"
3
+ description = "MGRS extension for PySTAC"
4
+ readme = "README.md"
5
+ version = "1.0.0"
6
+ authors = []
7
+ maintainers = []
8
+ keywords = ["pystac", "imagery", "raster", "catalog", "STAC", "mgrs"]
9
+ license = { text = "Apache-2.0" }
10
+ classifiers = [
11
+ "Development Status :: 5 - Production/Stable",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: Apache Software License",
14
+ "Natural Language :: English",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ ]
21
+ requires-python = ">=3.10"
22
+ dependencies = ["pystac-core"]
23
+
24
+ [project.urls]
25
+ Documentation = "https://pystac.readthedocs.io"
26
+ Repository = "https://github.com/stac-utils/pystac"
27
+ Issues = "https://github.com/stac-utils/pystac/issues"
28
+ Changelog = "https://github.com/stac-utils/pystac/blob/main/CHANGELOG.md"
29
+ Discussions = "https://github.com/radiantearth/stac-spec/discussions/categories/stac-software"
30
+
31
+ [build-system]
32
+ requires = ["hatchling"]
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["pystac"]
@@ -0,0 +1,252 @@
1
+ """Implements the :stac-ext:`MGRS Extension <mgrs>`."""
2
+
3
+ import re
4
+ from re import Pattern
5
+ from typing import Any, Literal
6
+
7
+ import pystac
8
+ from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
9
+ from pystac.extensions.hooks import ExtensionHooks
10
+
11
+ SCHEMA_URI: str = "https://stac-extensions.github.io/mgrs/v1.0.0/schema.json"
12
+ SCHEMA_STARTSWITH: str = "https://stac-extensions.github.io/mgrs/"
13
+ PREFIX: str = "mgrs:"
14
+
15
+ # Field names
16
+ LATITUDE_BAND_PROP: str = PREFIX + "latitude_band" # required
17
+ GRID_SQUARE_PROP: str = PREFIX + "grid_square" # required
18
+ UTM_ZONE_PROP: str = PREFIX + "utm_zone"
19
+
20
+ LATITUDE_BANDS: frozenset[str] = frozenset(
21
+ {
22
+ "C",
23
+ "D",
24
+ "E",
25
+ "F",
26
+ "G",
27
+ "H",
28
+ "J",
29
+ "K",
30
+ "L",
31
+ "M",
32
+ "N",
33
+ "P",
34
+ "Q",
35
+ "R",
36
+ "S",
37
+ "T",
38
+ "U",
39
+ "V",
40
+ "W",
41
+ "X",
42
+ }
43
+ )
44
+
45
+ UTM_ZONES: frozenset[int] = frozenset(
46
+ {
47
+ 1,
48
+ 2,
49
+ 3,
50
+ 4,
51
+ 5,
52
+ 6,
53
+ 7,
54
+ 8,
55
+ 9,
56
+ 10,
57
+ 11,
58
+ 12,
59
+ 13,
60
+ 14,
61
+ 15,
62
+ 16,
63
+ 17,
64
+ 18,
65
+ 19,
66
+ 20,
67
+ 21,
68
+ 22,
69
+ 23,
70
+ 24,
71
+ 25,
72
+ 26,
73
+ 27,
74
+ 28,
75
+ 29,
76
+ 30,
77
+ 31,
78
+ 32,
79
+ 33,
80
+ 34,
81
+ 35,
82
+ 36,
83
+ 37,
84
+ 38,
85
+ 39,
86
+ 40,
87
+ 41,
88
+ 42,
89
+ 43,
90
+ 44,
91
+ 45,
92
+ 46,
93
+ 47,
94
+ 48,
95
+ 49,
96
+ 50,
97
+ 51,
98
+ 52,
99
+ 53,
100
+ 54,
101
+ 55,
102
+ 56,
103
+ 57,
104
+ 58,
105
+ 59,
106
+ 60,
107
+ }
108
+ )
109
+
110
+ GRID_SQUARE_REGEX: str = (
111
+ r"[ABCDEFGHJKLMNPQRSTUVWXYZ][ABCDEFGHJKLMNPQRSTUV](\d{2}|\d{4}|\d{6}|\d{8}|\d{10})?"
112
+ )
113
+ GRID_SQUARE_PATTERN: Pattern[str] = re.compile(GRID_SQUARE_REGEX)
114
+
115
+
116
+ def validated_latitude_band(v: str) -> str:
117
+ if not isinstance(v, str):
118
+ raise ValueError("Invalid MGRS latitude band: must be str")
119
+ if v not in LATITUDE_BANDS:
120
+ raise ValueError(f"Invalid MGRS latitude band: {v} is not in {LATITUDE_BANDS}")
121
+ return v
122
+
123
+
124
+ def validated_grid_square(v: str) -> str:
125
+ if not isinstance(v, str):
126
+ raise ValueError("Invalid MGRS grid square identifier: must be str")
127
+ if not GRID_SQUARE_PATTERN.fullmatch(v):
128
+ raise ValueError(
129
+ f"Invalid MGRS grid square identifier: {v}"
130
+ f" does not match the regex {GRID_SQUARE_REGEX}"
131
+ )
132
+ return v
133
+
134
+
135
+ def validated_utm_zone(v: int | None) -> int | None:
136
+ if v is not None and not isinstance(v, int):
137
+ raise ValueError("Invalid MGRS utm zone: must be None or int")
138
+ if v is not None and v not in UTM_ZONES:
139
+ raise ValueError(f"Invalid MGRS UTM zone: {v} is not in {UTM_ZONES}")
140
+ return v
141
+
142
+
143
+ class MgrsExtension(
144
+ PropertiesExtension,
145
+ ExtensionManagementMixin[pystac.Item | pystac.Collection],
146
+ ):
147
+ """A concrete implementation of :class:`~pystac.extensions.mgrs.MgrsExtension`
148
+ on an :class:`~pystac.Item`
149
+ that extends the properties of the Item to include properties defined in the
150
+ :stac-ext:`MGRS Extension <mgrs>`.
151
+
152
+ This class should generally not be instantiated directly. Instead, call
153
+ :meth:`~pystac.extensions.mgrs.MgrsExtension.ext` on an :class:`~pystac.Item`
154
+ to extend it.
155
+
156
+ .. code-block:: python
157
+
158
+ >>> item: pystac.Item = ...
159
+ >>> proj_ext = MgrsExtension.ext(item)
160
+ """
161
+
162
+ name: Literal["mgrs"] = "mgrs"
163
+ item: pystac.Item
164
+ """The :class:`~pystac.Item` being extended."""
165
+
166
+ properties: dict[str, Any]
167
+ """The :class:`~pystac.Item` properties, including extension properties."""
168
+
169
+ def __init__(self, item: pystac.Item):
170
+ self.item = item
171
+ self.properties = item.properties
172
+
173
+ def __repr__(self) -> str:
174
+ return f"<ItemMgrsExtension Item id={self.item.id}>"
175
+
176
+ def apply(
177
+ self,
178
+ latitude_band: str,
179
+ grid_square: str,
180
+ utm_zone: int | None = None,
181
+ ) -> None:
182
+ """Applies MGRS extension properties to the extended Item.
183
+
184
+ Args:
185
+ latitude_band : REQUIRED. The latitude band of the Item's centroid.
186
+ grid_square : REQUIRED. MGRS grid square of the Item's centroid.
187
+ utm_zone : The UTM Zone of the Item centroid.
188
+ """
189
+ self.latitude_band = validated_latitude_band(latitude_band)
190
+ self.grid_square = validated_grid_square(grid_square)
191
+ self.utm_zone = validated_utm_zone(utm_zone)
192
+
193
+ @property
194
+ def latitude_band(self) -> str | None:
195
+ """Get or sets the latitude band of the datasource."""
196
+ return self._get_property(LATITUDE_BAND_PROP, str)
197
+
198
+ @latitude_band.setter
199
+ def latitude_band(self, v: str) -> None:
200
+ self._set_property(
201
+ LATITUDE_BAND_PROP, validated_latitude_band(v), pop_if_none=False
202
+ )
203
+
204
+ @property
205
+ def grid_square(self) -> str | None:
206
+ """Get or sets the latitude band of the datasource."""
207
+ return self._get_property(GRID_SQUARE_PROP, str)
208
+
209
+ @grid_square.setter
210
+ def grid_square(self, v: str) -> None:
211
+ self._set_property(
212
+ GRID_SQUARE_PROP, validated_grid_square(v), pop_if_none=False
213
+ )
214
+
215
+ @property
216
+ def utm_zone(self) -> int | None:
217
+ """Get or sets the latitude band of the datasource."""
218
+ return self._get_property(UTM_ZONE_PROP, int)
219
+
220
+ @utm_zone.setter
221
+ def utm_zone(self, v: int | None) -> None:
222
+ self._set_property(UTM_ZONE_PROP, validated_utm_zone(v), pop_if_none=True)
223
+
224
+ @classmethod
225
+ def get_schema_uri(cls) -> str:
226
+ return SCHEMA_URI
227
+
228
+ @classmethod
229
+ def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> "MgrsExtension":
230
+ """Extends the given STAC Object with properties from the :stac-ext:`MGRS
231
+ Extension <mgrs>`.
232
+
233
+ This extension can be applied to instances of :class:`~pystac.Item`.
234
+
235
+ Raises:
236
+
237
+ pystac.ExtensionTypeError : If an invalid object type is passed.
238
+ """
239
+ if isinstance(obj, pystac.Item):
240
+ cls.ensure_has_extension(obj, add_if_missing)
241
+ return MgrsExtension(obj)
242
+ else:
243
+ raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
244
+
245
+
246
+ class MgrsExtensionHooks(ExtensionHooks):
247
+ schema_uri: str = SCHEMA_URI
248
+ prev_extension_ids: set[str] = set()
249
+ stac_object_types = {pystac.STACObjectType.ITEM}
250
+
251
+
252
+ MGRS_EXTENSION_HOOKS: ExtensionHooks = MgrsExtensionHooks()
File without changes
@@ -0,0 +1,52 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers: {}
5
+ method: GET
6
+ uri: https://stac-extensions.github.io/mgrs/v1.0.0/schema.json
7
+ response:
8
+ body:
9
+ string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\":
10
+ \"https://stac-extensions.github.io/mgrs/v1.0.0/schema.json#\",\n \"title\":
11
+ \"MGRS Extension\",\n \"description\": \"STAC MGRS Extension for STAC Items.\",\n
12
+ \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n
13
+ \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\":
14
+ [\n \"type\",\n \"properties\",\n \"assets\"\n
15
+ \ ],\n \"properties\": {\n \"type\": {\n \"const\":
16
+ \"Feature\"\n },\n \"properties\": {\n \"allOf\":
17
+ [\n {\n \"$comment\": \"Require fields here
18
+ for item properties.\",\n \"required\": [\n \"mgrs:latitude_band\",\n
19
+ \ \"mgrs:grid_square\"\n ]\n },\n
20
+ \ {\n \"$ref\": \"#/definitions/fields\"\n
21
+ \ }\n ]\n },\n \"assets\":
22
+ {\n \"type\": \"object\",\n \"additionalProperties\":
23
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
24
+ \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
25
+ \ }\n ]\n },\n {\n \"$comment\": \"This is the schema
26
+ for STAC Collections.\",\n \"allOf\": [\n {\n \"type\":
27
+ \"object\",\n \"required\": [\n \"type\"\n ],\n
28
+ \ \"properties\": {\n \"type\": {\n \"const\":
29
+ \"Collection\"\n },\n \"assets\": {\n \"type\":
30
+ \"object\",\n \"additionalProperties\": {\n \"$ref\":
31
+ \"#/definitions/fields\"\n }\n },\n \"item_assets\":
32
+ {\n \"type\": \"object\",\n \"additionalProperties\":
33
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
34
+ \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
35
+ \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\":
36
+ {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n
37
+ \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\":
38
+ \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/mgrs/v1.0.0/schema.json\"\n
39
+ \ }\n }\n }\n },\n \"fields\": {\n \"$comment\":
40
+ \"Add your new fields here. Don't require them here, do that above in the
41
+ item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"mgrs:latitude_band\":
42
+ {\n \"type\": \"string\"\n },\n \"mgrs:grid_square\":
43
+ {\n \"type\": \"string\"\n },\n \"mgrs:utm_zone\":
44
+ {\n \"type\": \"integer\"\n }\n },\n \"patternProperties\":
45
+ {\n \"^(?!mgrs:)\": {\n \"$comment\": \"Do not allow other
46
+ fields with this prefix\"\n }\n },\n \"additionalProperties\":
47
+ false\n }\n }\n}"
48
+ headers: {}
49
+ status:
50
+ code: 200
51
+ message: OK
52
+ version: 1
@@ -0,0 +1,52 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers: {}
5
+ method: GET
6
+ uri: https://stac-extensions.github.io/mgrs/v1.0.0/schema.json
7
+ response:
8
+ body:
9
+ string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\":
10
+ \"https://stac-extensions.github.io/mgrs/v1.0.0/schema.json#\",\n \"title\":
11
+ \"MGRS Extension\",\n \"description\": \"STAC MGRS Extension for STAC Items.\",\n
12
+ \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n
13
+ \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\":
14
+ [\n \"type\",\n \"properties\",\n \"assets\"\n
15
+ \ ],\n \"properties\": {\n \"type\": {\n \"const\":
16
+ \"Feature\"\n },\n \"properties\": {\n \"allOf\":
17
+ [\n {\n \"$comment\": \"Require fields here
18
+ for item properties.\",\n \"required\": [\n \"mgrs:latitude_band\",\n
19
+ \ \"mgrs:grid_square\"\n ]\n },\n
20
+ \ {\n \"$ref\": \"#/definitions/fields\"\n
21
+ \ }\n ]\n },\n \"assets\":
22
+ {\n \"type\": \"object\",\n \"additionalProperties\":
23
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
24
+ \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
25
+ \ }\n ]\n },\n {\n \"$comment\": \"This is the schema
26
+ for STAC Collections.\",\n \"allOf\": [\n {\n \"type\":
27
+ \"object\",\n \"required\": [\n \"type\"\n ],\n
28
+ \ \"properties\": {\n \"type\": {\n \"const\":
29
+ \"Collection\"\n },\n \"assets\": {\n \"type\":
30
+ \"object\",\n \"additionalProperties\": {\n \"$ref\":
31
+ \"#/definitions/fields\"\n }\n },\n \"item_assets\":
32
+ {\n \"type\": \"object\",\n \"additionalProperties\":
33
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
34
+ \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
35
+ \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\":
36
+ {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n
37
+ \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\":
38
+ \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/mgrs/v1.0.0/schema.json\"\n
39
+ \ }\n }\n }\n },\n \"fields\": {\n \"$comment\":
40
+ \"Add your new fields here. Don't require them here, do that above in the
41
+ item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"mgrs:latitude_band\":
42
+ {\n \"type\": \"string\"\n },\n \"mgrs:grid_square\":
43
+ {\n \"type\": \"string\"\n },\n \"mgrs:utm_zone\":
44
+ {\n \"type\": \"integer\"\n }\n },\n \"patternProperties\":
45
+ {\n \"^(?!mgrs:)\": {\n \"$comment\": \"Do not allow other
46
+ fields with this prefix\"\n }\n },\n \"additionalProperties\":
47
+ false\n }\n }\n}"
48
+ headers: {}
49
+ status:
50
+ code: 200
51
+ message: OK
52
+ version: 1
@@ -0,0 +1,52 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers: {}
5
+ method: GET
6
+ uri: https://stac-extensions.github.io/mgrs/v1.0.0/schema.json
7
+ response:
8
+ body:
9
+ string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\":
10
+ \"https://stac-extensions.github.io/mgrs/v1.0.0/schema.json#\",\n \"title\":
11
+ \"MGRS Extension\",\n \"description\": \"STAC MGRS Extension for STAC Items.\",\n
12
+ \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n
13
+ \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\":
14
+ [\n \"type\",\n \"properties\",\n \"assets\"\n
15
+ \ ],\n \"properties\": {\n \"type\": {\n \"const\":
16
+ \"Feature\"\n },\n \"properties\": {\n \"allOf\":
17
+ [\n {\n \"$comment\": \"Require fields here
18
+ for item properties.\",\n \"required\": [\n \"mgrs:latitude_band\",\n
19
+ \ \"mgrs:grid_square\"\n ]\n },\n
20
+ \ {\n \"$ref\": \"#/definitions/fields\"\n
21
+ \ }\n ]\n },\n \"assets\":
22
+ {\n \"type\": \"object\",\n \"additionalProperties\":
23
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
24
+ \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
25
+ \ }\n ]\n },\n {\n \"$comment\": \"This is the schema
26
+ for STAC Collections.\",\n \"allOf\": [\n {\n \"type\":
27
+ \"object\",\n \"required\": [\n \"type\"\n ],\n
28
+ \ \"properties\": {\n \"type\": {\n \"const\":
29
+ \"Collection\"\n },\n \"assets\": {\n \"type\":
30
+ \"object\",\n \"additionalProperties\": {\n \"$ref\":
31
+ \"#/definitions/fields\"\n }\n },\n \"item_assets\":
32
+ {\n \"type\": \"object\",\n \"additionalProperties\":
33
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
34
+ \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
35
+ \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\":
36
+ {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n
37
+ \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\":
38
+ \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/mgrs/v1.0.0/schema.json\"\n
39
+ \ }\n }\n }\n },\n \"fields\": {\n \"$comment\":
40
+ \"Add your new fields here. Don't require them here, do that above in the
41
+ item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"mgrs:latitude_band\":
42
+ {\n \"type\": \"string\"\n },\n \"mgrs:grid_square\":
43
+ {\n \"type\": \"string\"\n },\n \"mgrs:utm_zone\":
44
+ {\n \"type\": \"integer\"\n }\n },\n \"patternProperties\":
45
+ {\n \"^(?!mgrs:)\": {\n \"$comment\": \"Do not allow other
46
+ fields with this prefix\"\n }\n },\n \"additionalProperties\":
47
+ false\n }\n }\n}"
48
+ headers: {}
49
+ status:
50
+ code: 200
51
+ message: OK
52
+ version: 1
@@ -0,0 +1,52 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers: {}
5
+ method: GET
6
+ uri: https://stac-extensions.github.io/mgrs/v1.0.0/schema.json
7
+ response:
8
+ body:
9
+ string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\":
10
+ \"https://stac-extensions.github.io/mgrs/v1.0.0/schema.json#\",\n \"title\":
11
+ \"MGRS Extension\",\n \"description\": \"STAC MGRS Extension for STAC Items.\",\n
12
+ \ \"oneOf\": [\n {\n \"$comment\": \"This is the schema for STAC Items.\",\n
13
+ \ \"allOf\": [\n {\n \"type\": \"object\",\n \"required\":
14
+ [\n \"type\",\n \"properties\",\n \"assets\"\n
15
+ \ ],\n \"properties\": {\n \"type\": {\n \"const\":
16
+ \"Feature\"\n },\n \"properties\": {\n \"allOf\":
17
+ [\n {\n \"$comment\": \"Require fields here
18
+ for item properties.\",\n \"required\": [\n \"mgrs:latitude_band\",\n
19
+ \ \"mgrs:grid_square\"\n ]\n },\n
20
+ \ {\n \"$ref\": \"#/definitions/fields\"\n
21
+ \ }\n ]\n },\n \"assets\":
22
+ {\n \"type\": \"object\",\n \"additionalProperties\":
23
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
24
+ \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
25
+ \ }\n ]\n },\n {\n \"$comment\": \"This is the schema
26
+ for STAC Collections.\",\n \"allOf\": [\n {\n \"type\":
27
+ \"object\",\n \"required\": [\n \"type\"\n ],\n
28
+ \ \"properties\": {\n \"type\": {\n \"const\":
29
+ \"Collection\"\n },\n \"assets\": {\n \"type\":
30
+ \"object\",\n \"additionalProperties\": {\n \"$ref\":
31
+ \"#/definitions/fields\"\n }\n },\n \"item_assets\":
32
+ {\n \"type\": \"object\",\n \"additionalProperties\":
33
+ {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n
34
+ \ }\n },\n {\n \"$ref\": \"#/definitions/stac_extensions\"\n
35
+ \ }\n ]\n }\n ],\n \"definitions\": {\n \"stac_extensions\":
36
+ {\n \"type\": \"object\",\n \"required\": [\n \"stac_extensions\"\n
37
+ \ ],\n \"properties\": {\n \"stac_extensions\": {\n \"type\":
38
+ \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/mgrs/v1.0.0/schema.json\"\n
39
+ \ }\n }\n }\n },\n \"fields\": {\n \"$comment\":
40
+ \"Add your new fields here. Don't require them here, do that above in the
41
+ item schema.\",\n \"type\": \"object\",\n \"properties\": {\n \"mgrs:latitude_band\":
42
+ {\n \"type\": \"string\"\n },\n \"mgrs:grid_square\":
43
+ {\n \"type\": \"string\"\n },\n \"mgrs:utm_zone\":
44
+ {\n \"type\": \"integer\"\n }\n },\n \"patternProperties\":
45
+ {\n \"^(?!mgrs:)\": {\n \"$comment\": \"Do not allow other
46
+ fields with this prefix\"\n }\n },\n \"additionalProperties\":
47
+ false\n }\n }\n}"
48
+ headers: {}
49
+ status:
50
+ code: 200
51
+ message: OK
52
+ version: 1
@@ -0,0 +1,53 @@
1
+ {
2
+ "stac_version": "1.1.0",
3
+ "stac_extensions": [
4
+ "https://stac-extensions.github.io/mgrs/v1.0.0/schema.json"
5
+ ],
6
+ "type": "Feature",
7
+ "id": "item",
8
+ "bbox": [
9
+ 172.9,
10
+ 1.3,
11
+ 173,
12
+ 1.4
13
+ ],
14
+ "geometry": {
15
+ "type": "Polygon",
16
+ "coordinates": [
17
+ [
18
+ [
19
+ 172.9,
20
+ 1.3
21
+ ],
22
+ [
23
+ 173,
24
+ 1.3
25
+ ],
26
+ [
27
+ 173,
28
+ 1.4
29
+ ],
30
+ [
31
+ 172.9,
32
+ 1.4
33
+ ],
34
+ [
35
+ 172.9,
36
+ 1.3
37
+ ]
38
+ ]
39
+ ]
40
+ },
41
+ "properties": {
42
+ "datetime": "2020-12-11T22:38:32Z",
43
+ "mgrs:utm_zone": 13,
44
+ "mgrs:latitude_band": "X",
45
+ "mgrs:grid_square": "DH"
46
+ },
47
+ "links": [],
48
+ "assets": {
49
+ "data": {
50
+ "href": "https://example.com/examples/file.xyz"
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,140 @@
1
+ """Tests for pystac.tests.extensions.mgrs"""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ import pystac
9
+ from pystac.extensions.mgrs import MgrsExtension
10
+
11
+ DATA_FILES = Path(__file__).resolve().parent / "data-files"
12
+
13
+
14
+ @pytest.fixture
15
+ def ext_item_uri() -> str:
16
+ return str(DATA_FILES / "item.json")
17
+
18
+
19
+ @pytest.fixture
20
+ def ext_item(ext_item_uri: str) -> pystac.Item:
21
+ return pystac.Item.from_file(ext_item_uri)
22
+
23
+
24
+ def test_stac_extensions(ext_item: pystac.Item) -> None:
25
+ assert MgrsExtension.has_extension(ext_item)
26
+
27
+
28
+ def test_get_schema_uri(ext_item: pystac.Item) -> None:
29
+ assert MgrsExtension.get_schema_uri() in ext_item.stac_extensions
30
+
31
+
32
+ def test_ext_raises_if_item_does_not_conform(item: pystac.Item) -> None:
33
+ with pytest.raises(pystac.errors.ExtensionNotImplemented):
34
+ MgrsExtension.ext(item)
35
+
36
+
37
+ def test_ext_raises_on_collection(collection: pystac.Collection) -> None:
38
+ with pytest.raises(
39
+ pystac.errors.ExtensionTypeError,
40
+ match="MgrsExtension does not apply to type 'Collection'",
41
+ ) as e:
42
+ MgrsExtension.ext(collection) # type: ignore
43
+ assert "Hint" not in str(e.value)
44
+
45
+
46
+ def test_to_from_dict(ext_item_uri: str, ext_item: pystac.Item) -> None:
47
+ with open(ext_item_uri) as f:
48
+ d = json.load(f)
49
+ actual = ext_item.to_dict(include_self_link=False)
50
+ assert actual == d
51
+
52
+
53
+ def test_add_to(item: pystac.Item) -> None:
54
+ assert not MgrsExtension.has_extension(item)
55
+ MgrsExtension.add_to(item)
56
+
57
+ assert MgrsExtension.has_extension(item)
58
+
59
+
60
+ def test_apply(item: pystac.Item) -> None:
61
+ MgrsExtension.add_to(item)
62
+ MgrsExtension.ext(item).apply(latitude_band="X", grid_square="DH")
63
+ assert MgrsExtension.ext(item).latitude_band
64
+ assert MgrsExtension.ext(item).grid_square
65
+
66
+
67
+ def test_apply_without_required_fields_raises(item: pystac.Item) -> None:
68
+ MgrsExtension.add_to(item)
69
+ with pytest.raises(TypeError, match="missing 2 required positional arguments"):
70
+ MgrsExtension.ext(item).apply() # type: ignore
71
+
72
+
73
+ @pytest.mark.vcr()
74
+ def test_validate(ext_item: pystac.Item) -> None:
75
+ assert ext_item.validate()
76
+
77
+
78
+ @pytest.mark.parametrize("field", ["latitude_band", "grid_square", "utm_zone"])
79
+ def test_get_field(ext_item: pystac.Item, field: str) -> None:
80
+ prop = ext_item.properties[f"mgrs:{field}"]
81
+ attr = getattr(MgrsExtension.ext(ext_item), field)
82
+
83
+ assert attr is not None
84
+ assert attr == prop
85
+
86
+
87
+ @pytest.mark.vcr()
88
+ @pytest.mark.parametrize(
89
+ "field,value",
90
+ [
91
+ ("latitude_band", "C"),
92
+ ("grid_square", "ZA"),
93
+ ("utm_zone", 59),
94
+ ],
95
+ )
96
+ def test_set_field(ext_item: pystac.Item, field: str, value) -> None: # type: ignore
97
+ original = ext_item.properties[f"mgrs:{field}"]
98
+ setattr(MgrsExtension.ext(ext_item), field, value)
99
+ new = ext_item.properties[f"mgrs:{field}"]
100
+
101
+ assert new != original
102
+ assert new == value
103
+ assert ext_item.validate()
104
+
105
+
106
+ def test_utm_zone_set_to_none_pops_from_dict(ext_item: pystac.Item) -> None:
107
+ assert "mgrs:utm_zone" in ext_item.properties
108
+
109
+ MgrsExtension.ext(ext_item).utm_zone = None
110
+ assert "mgrs:utm_zone" not in ext_item.properties
111
+
112
+
113
+ def test_invalid_latitude_band_raises_informative_error(ext_item: pystac.Item) -> None:
114
+ with pytest.raises(ValueError, match="must be str"):
115
+ MgrsExtension.ext(ext_item).latitude_band = 2 # type: ignore
116
+
117
+ with pytest.raises(ValueError, match="must be str"):
118
+ MgrsExtension.ext(ext_item).latitude_band = None # type: ignore
119
+
120
+ with pytest.raises(ValueError, match="a is not in "):
121
+ MgrsExtension.ext(ext_item).latitude_band = "a"
122
+
123
+
124
+ def test_invalid_grid_square_raises_informative_error(ext_item: pystac.Item) -> None:
125
+ with pytest.raises(ValueError, match="must be str"):
126
+ MgrsExtension.ext(ext_item).grid_square = 2 # type: ignore
127
+
128
+ with pytest.raises(ValueError, match="must be str"):
129
+ MgrsExtension.ext(ext_item).grid_square = None # type: ignore
130
+
131
+ with pytest.raises(ValueError, match="nv does not match the regex "):
132
+ MgrsExtension.ext(ext_item).grid_square = "nv"
133
+
134
+
135
+ def test_invalid_utm_zone_raises_informative_error(ext_item: pystac.Item) -> None:
136
+ with pytest.raises(ValueError, match="must be None or int"):
137
+ MgrsExtension.ext(ext_item).utm_zone = "foo" # type: ignore
138
+
139
+ with pytest.raises(ValueError, match="61 is not in "):
140
+ MgrsExtension.ext(ext_item).utm_zone = 61