pystac-ext-render 2.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-render
3
+ Version: 2.0.0
4
+ Summary: Render 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,pystac,raster,render
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-render
26
+
27
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [Render Extension](https://github.com/stac-extensions/render).
28
+ This extension provides fields for describing visualization parameters for assets, including rescaling, color maps, expressions, and tile matrix set configurations.
29
+
30
+ ## Supported versions
31
+
32
+ - [v2.0.0](https://stac-extensions.github.io/render/v2.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. `2.0.0.post1`.
@@ -0,0 +1,13 @@
1
+ # pystac-ext-render
2
+
3
+ [PySTAC](https://pypi.org/project/pystac/) extension package for the [Render Extension](https://github.com/stac-extensions/render).
4
+ This extension provides fields for describing visualization parameters for assets, including rescaling, color maps, expressions, and tile matrix set configurations.
5
+
6
+ ## Supported versions
7
+
8
+ - [v2.0.0](https://stac-extensions.github.io/render/v2.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. `2.0.0.post1`.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "pystac-ext-render"
3
+ description = "Render extension for PySTAC"
4
+ readme = "README.md"
5
+ version = "2.0.0"
6
+ authors = []
7
+ maintainers = []
8
+ keywords = ["pystac", "imagery", "raster", "catalog", "STAC", "render"]
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"]
File without changes
@@ -0,0 +1,411 @@
1
+ """Implements the :stac-ext:`Render Extension <render>`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Generic, Literal, TypeVar
6
+
7
+ import pystac
8
+ from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
9
+ from pystac.extensions.hooks import ExtensionHooks
10
+ from pystac.utils import get_required, map_opt
11
+
12
+ #: Generalized version of :class:`~pystac.Collection` or :class:`~pystac.Item`
13
+ T = TypeVar("T", pystac.Collection, pystac.Item)
14
+
15
+ SCHEMA_URI_PATTERN: str = (
16
+ "https://stac-extensions.github.io/render/v{version}/schema.json"
17
+ )
18
+ DEFAULT_VERSION: str = "2.0.0"
19
+
20
+ SUPPORTED_VERSIONS = [DEFAULT_VERSION]
21
+
22
+ RENDERS_PROP = "renders"
23
+
24
+
25
+ class Render:
26
+ """Parameters for creating a rendered view of assets."""
27
+
28
+ properties: dict[str, Any]
29
+
30
+ def __init__(self, properties: dict[str, Any]) -> None:
31
+ self.properties = properties
32
+
33
+ @property
34
+ def assets(self) -> list[str]:
35
+ """
36
+ List of asset keys referencing the assets that are
37
+ used to make the rendering.
38
+ """
39
+ return get_required(self.properties.get("assets"), self, "assets")
40
+
41
+ @assets.setter
42
+ def assets(self, v: list[str]) -> None:
43
+ self.properties["assets"] = v
44
+
45
+ @property
46
+ def title(self) -> str | None:
47
+ """Title of the rendering"""
48
+ return self.properties.get("title")
49
+
50
+ @title.setter
51
+ def title(self, v: str | None) -> None:
52
+ if v is not None:
53
+ self.properties["title"] = v
54
+ else:
55
+ self.properties.pop("title", None)
56
+
57
+ @property
58
+ def rescale(self) -> list[list[float]] | None:
59
+ """A list of min/max value pairs to rescale each asset by, e.g.
60
+ ``[[0, 5000], [0, 7000], [0, 9000]]``. If not provided, the
61
+ assets will not be rescaled.
62
+ """
63
+ return self.properties.get("rescale")
64
+
65
+ @rescale.setter
66
+ def rescale(self, v: list[list[float]] | None) -> None:
67
+ if v is not None:
68
+ self.properties["rescale"] = v
69
+ else:
70
+ self.properties.pop("rescale", None)
71
+
72
+ @property
73
+ def nodata(self) -> float | str | None:
74
+ """Nodata value."""
75
+ return self.properties.get("nodata")
76
+
77
+ @nodata.setter
78
+ def nodata(self, v: float | str | None) -> None:
79
+ if v is not None:
80
+ self.properties["nodata"] = v
81
+ else:
82
+ self.properties.pop("nodata", None)
83
+
84
+ @property
85
+ def colormap_name(self) -> str | None:
86
+ """Name of color map to apply to the render.
87
+ See: https://matplotlib.org/stable/gallery/color/colormap_reference.html
88
+ """
89
+ return self.properties.get("colormap_name")
90
+
91
+ @colormap_name.setter
92
+ def colormap_name(self, v: str | None) -> None:
93
+ if v is not None:
94
+ self.properties["colormap_name"] = v
95
+ else:
96
+ self.properties.pop("colormap_name", None)
97
+
98
+ @property
99
+ def colormap(self) -> dict[str, Any] | None:
100
+ """A dictionary containing a custom colormap definition.
101
+ See: https://developmentseed.org/titiler/advanced/rendering/#custom-colormaps
102
+ """
103
+ return self.properties.get("colormap")
104
+
105
+ @colormap.setter
106
+ def colormap(self, v: dict[str, Any] | None) -> None:
107
+ if v is not None:
108
+ self.properties["colormap"] = v
109
+ else:
110
+ self.properties.pop("colormap", None)
111
+
112
+ @property
113
+ def color_formula(self) -> str | None:
114
+ """A string containing a color formula to apply
115
+ color corrections to images. Useful for reducing
116
+ artefacts like atmospheric haze, dark shadows, or
117
+ muted colors.
118
+ See: https://developmentseed.org/titiler/advanced/rendering/#color-formula
119
+ """
120
+ return self.properties.get("color_formula")
121
+
122
+ @color_formula.setter
123
+ def color_formula(self, v: str | None) -> None:
124
+ if v is not None:
125
+ self.properties["color_formula"] = v
126
+ else:
127
+ self.properties.pop("color_formula", None)
128
+
129
+ @property
130
+ def resampling(self) -> str | None:
131
+ """Resampling algorithm to apply to the referenced assets. See GDAL
132
+ resampling algorithm for some examples.
133
+ See: https://gdal.org/en/latest/programs/gdalwarp.html#cmdoption-gdalwarp-r
134
+ """
135
+ return self.properties.get("resampling")
136
+
137
+ @resampling.setter
138
+ def resampling(self, v: str | None) -> None:
139
+ if v is not None:
140
+ self.properties["resampling"] = v
141
+ else:
142
+ self.properties.pop("resampling", None)
143
+
144
+ @property
145
+ def expression(self) -> str | None:
146
+ """Band arithmetic formula to apply to the referenced assets."""
147
+ return self.properties.get("expression")
148
+
149
+ @expression.setter
150
+ def expression(self, v: str | None) -> None:
151
+ if v is not None:
152
+ self.properties["expression"] = v
153
+ else:
154
+ self.properties.pop("expression", None)
155
+
156
+ @property
157
+ def minmax_zoom(self) -> list[int] | None:
158
+ """Zoom level range applicable for the visualization, e.g. ``[2, 18]``."""
159
+ return self.properties.get("minmax_zoom")
160
+
161
+ @minmax_zoom.setter
162
+ def minmax_zoom(self, v: list[int] | None) -> None:
163
+ if v is not None:
164
+ self.properties["minmax_zoom"] = v
165
+ else:
166
+ self.properties.pop("minmax_zoom", None)
167
+
168
+ def apply(
169
+ self,
170
+ assets: list[str],
171
+ title: str | None = None,
172
+ rescale: list[list[float]] | None = None,
173
+ nodata: float | str | None = None,
174
+ colormap_name: str | None = None,
175
+ colormap: dict[str, Any] | None = None,
176
+ color_formula: str | None = None,
177
+ resampling: str | None = None,
178
+ expression: str | None = None,
179
+ minmax_zoom: list[int] | None = None,
180
+ ) -> None:
181
+ """Set the properties for a new Render.
182
+
183
+ Args:
184
+ assets : List of asset keys referencing the assets that are
185
+ used to make the rendering.
186
+ title : Title of the rendering.
187
+ rescale : A list of min/max value pairs to rescale each asset by, e.g.
188
+ ``[[0, 5000], [0, 7000], [0, 9000]]``. If not provided, the
189
+ assets will not be rescaled.
190
+ nodata : Nodata value.
191
+ colormap_name : Name of color map to apply to the render.
192
+ https://matplotlib.org/stable/gallery/color/colormap_reference.html
193
+ colormap : A dictionary containing a custom colormap definition.
194
+ https://developmentseed.org/titiler/advanced/rendering/#custom-colormaps
195
+ color_formula : A string containing a color formula to apply
196
+ color corrections to images. Useful for reducing
197
+ artifacts like atmospheric haze, dark shadows, or
198
+ muted colors.
199
+ https://developmentseed.org/titiler/advanced/rendering/#color-formula
200
+ resampling : Resampling algorithm to apply to the referenced assets. See
201
+ GDAL resampling algorithm for some examples.
202
+ https://gdal.org/en/latest/programs/gdalwarp.html#cmdoption-gdalwarp-r
203
+ expression : Band arithmetic formula to apply to the referenced assets.
204
+ minmax_zoom : Zoom level range applicable for the visualization,
205
+ e.g. ``[2, 18]``.
206
+ """
207
+ self.assets = assets
208
+ self.title = title
209
+ self.rescale = rescale
210
+ self.nodata = nodata
211
+ self.colormap_name = colormap_name
212
+ self.colormap = colormap
213
+ self.color_formula = color_formula
214
+ self.resampling = resampling
215
+ self.expression = expression
216
+ self.minmax_zoom = minmax_zoom
217
+
218
+ @classmethod
219
+ def create(
220
+ cls,
221
+ assets: list[str],
222
+ title: str | None = None,
223
+ rescale: list[list[float]] | None = None,
224
+ nodata: float | str | None = None,
225
+ colormap_name: str | None = None,
226
+ colormap: dict[str, Any] | None = None,
227
+ color_formula: str | None = None,
228
+ resampling: str | None = None,
229
+ expression: str | None = None,
230
+ minmax_zoom: list[int] | None = None,
231
+ ) -> Render:
232
+ """Create a new Render.
233
+
234
+ Args:
235
+ assets: List of asset keys referencing the assets that are
236
+ used to make the rendering.
237
+ title: Title of the rendering.
238
+ rescale: A list of min/max value pairs to rescale each asset by, e.g.
239
+ ``[[0, 5000], [0, 7000], [0, 9000]]``. If not provided, the
240
+ assets will not be rescaled.
241
+ nodata: Nodata value.
242
+ colormap_name: Name of color map to apply to the render.
243
+ https://matplotlib.org/stable/gallery/color/colormap_reference.html
244
+ colormap: A dictionary containing a custom colormap definition.
245
+ https://developmentseed.org/titiler/advanced/rendering/#custom-colormaps
246
+ color_formula: A string containing a color formula to apply
247
+ color corrections to images. Useful for reducing
248
+ artifacts like atmospheric haze, dark shadows, or
249
+ muted colors.
250
+ https://developmentseed.org/titiler/advanced/rendering/#color-formula
251
+ resampling: Resampling algorithm to apply to the referenced assets. See
252
+ GDAL resampling algorithm for some examples.
253
+ https://gdal.org/en/latest/programs/gdalwarp.html#cmdoption-gdalwarp-r
254
+ expression: Band arithmetic formula to apply to the referenced assets.
255
+ minmax_zoom: Zoom level range applicable for the visualization,
256
+ e.g. ``[2, 18]``.
257
+ """
258
+ c = cls({})
259
+ c.apply(
260
+ assets=assets,
261
+ title=title,
262
+ rescale=rescale,
263
+ nodata=nodata,
264
+ colormap_name=colormap_name,
265
+ colormap=colormap,
266
+ color_formula=color_formula,
267
+ resampling=resampling,
268
+ expression=expression,
269
+ minmax_zoom=minmax_zoom,
270
+ )
271
+ return c
272
+
273
+ def to_dict(self) -> dict[str, Any]:
274
+ return self.properties
275
+
276
+ def __eq__(self, other: object) -> bool:
277
+ if not isinstance(other, Render):
278
+ raise NotImplementedError
279
+ return self.properties == other.properties
280
+
281
+ def __repr__(self) -> str:
282
+ props = " ".join(
283
+ [
284
+ f"{key}={value}"
285
+ for key, value in self.properties.items()
286
+ if value is not None
287
+ ]
288
+ )
289
+ return f"<Render {props}>"
290
+
291
+
292
+ class RenderExtension(
293
+ Generic[T],
294
+ PropertiesExtension,
295
+ ExtensionManagementMixin[pystac.Item | pystac.Collection],
296
+ ):
297
+ """An abstract class that can be used to extend the properties of a
298
+ :class:`~pystac.Collection` or :class:`~pystac.Item` with
299
+ properties from the :stac-ext:`Render Extension <render>`. This class is
300
+ generic over the type of STAC Object to be extended (e.g. :class:`~pystac.Item`,
301
+ :class:`~pystac.Collection`).
302
+
303
+ To create a concrete instance of :class:`RenderExtension`, use the
304
+ :meth:`RenderExtension.ext` method. For example:
305
+
306
+ .. code-block:: python
307
+
308
+ >>> item: pystac.Item = ...
309
+ >>> xr_ext = RenderExtension.ext(item)
310
+
311
+ """
312
+
313
+ name: Literal["render"] = "render"
314
+
315
+ @classmethod
316
+ def get_schema_uri(cls) -> str:
317
+ return SCHEMA_URI_PATTERN.format(version=DEFAULT_VERSION)
318
+
319
+ @classmethod
320
+ def ext(cls, obj: T, add_if_missing: bool = False) -> RenderExtension[T]:
321
+ """Extend the given STAC Object with properties from the
322
+ :stac-ext:`Render Extension <render>`.
323
+
324
+ This extension can be applied to instances of :class:`~pystac.Collection`
325
+ or :class:`~pystac.Item`.
326
+
327
+ Raises:
328
+ pystac.ExtensionTypeError : If an invalid object type is passed.
329
+ """
330
+ if isinstance(obj, pystac.Collection):
331
+ cls.ensure_has_extension(obj, add_if_missing)
332
+ return CollectionRenderExtension(obj)
333
+ elif isinstance(obj, pystac.Item):
334
+ cls.ensure_has_extension(obj, add_if_missing)
335
+ return ItemRenderExtension(obj)
336
+ else:
337
+ raise pystac.ExtensionTypeError(
338
+ f"RenderExtension does not apply to type '{type(obj).__name__}'"
339
+ )
340
+
341
+ def apply(
342
+ self,
343
+ renders: dict[str, Render],
344
+ ) -> None:
345
+ """Applies the render extension fields to the extended
346
+ object.
347
+
348
+ Args:
349
+ renders: a dictionary mapping render names to
350
+ :class: `~pystac.extensions.render.Render` objects.
351
+ """
352
+ self.renders = renders
353
+
354
+ @property
355
+ def renders(self) -> dict[str, Render]:
356
+ """A dictionary where each key is the name of a render and each
357
+ value is a :class:`~Render` object.
358
+ """
359
+ renders: dict[str, dict[str, Any]] = get_required(
360
+ self._get_property(RENDERS_PROP, dict[str, dict[str, Any]]),
361
+ self,
362
+ RENDERS_PROP,
363
+ )
364
+ return {k: Render(v) for k, v in renders.items()}
365
+
366
+ @renders.setter
367
+ def renders(self, v: dict[str, Render]) -> None:
368
+ self._set_property(
369
+ RENDERS_PROP,
370
+ map_opt(lambda renders: {k: r.to_dict() for k, r in renders.items()}, v),
371
+ pop_if_none=False,
372
+ )
373
+
374
+
375
+ class CollectionRenderExtension(RenderExtension[pystac.Collection]):
376
+ """A concrete implementation of :class:`RenderExtension` on a
377
+ :class:`~pystac.Collection` that extends the properties of the Collection to include
378
+ properties defined in the :stac-ext:`Render Extension <render>`.
379
+
380
+ This class should generally not be instantiated directly. Instead, call
381
+ :meth:`RenderExtension.ext` on an :class:`~pystac.Collection` to extend it.
382
+ """
383
+
384
+ def __init__(self, collection: pystac.Collection):
385
+ self.collection = collection
386
+ self.properties = collection.extra_fields
387
+
388
+ def __repr__(self) -> str:
389
+ return f"<CollectionRenderExtension Collection id={self.collection.id}>"
390
+
391
+
392
+ class ItemRenderExtension(RenderExtension[pystac.Item]):
393
+ """A concrete implementation of :class:`RenderExtension` on a
394
+ :class:`~pystac.Item` that extends the properties of the Item to include
395
+ properties defined in the :stac-ext:`Render Extension <render>`.
396
+
397
+ This class should generally not be instantiated directly. Instead, call
398
+ :meth:`RenderExtension.ext` on an :class:`~pystac.Item` to extend it.
399
+ """
400
+
401
+ def __init__(self, item: pystac.Item):
402
+ self.item = item
403
+ self.properties = item.properties
404
+
405
+ def __repr__(self) -> str:
406
+ return f"<ItemRenderExtension Item id={self.item.id}>"
407
+
408
+
409
+ class RenderExtensionHooks(ExtensionHooks):
410
+ schema_uri: str = SCHEMA_URI_PATTERN.format(version=DEFAULT_VERSION)
411
+ stac_object_types = {pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM}
@@ -0,0 +1,96 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers: {}
5
+ method: GET
6
+ uri: https://stac-extensions.github.io/render/v2.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/render/v2.0.0/schema.json#\",\n \"title\":
11
+ \"Rendering Extension\",\n \"description\": \"STAC Rendering Extension for
12
+ STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\":
13
+ \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"$ref\":
14
+ \"#/definitions/stac_extensions\"\n },\n {\n \"type\":
15
+ \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n
16
+ \ \"assets\"\n ],\n \"properties\": {\n \"type\":
17
+ {\n \"const\": \"Feature\"\n },\n \"properties\":
18
+ {\n \"type\": \"object\",\n \"required\": [\"renders\"],\n
19
+ \ \"properties\": {\n \"renders\": {\n \"type\":
20
+ \"object\",\n \"additionalProperties\": {\n \"$ref\":
21
+ \"#/definitions/fields\"\n }\n }\n }\n
22
+ \ }\n }\n },\n {\n \"if\": {\n \"properties\":
23
+ {\n \"stac_extensions\": {\n \"contains\": {\n
24
+ \ \"type\": \"string\",\n \"pattern\": \"https:\\/\\/stac-extensions\\\\.github\\\\.io\\/web-map-links\\/.*\"\n
25
+ \ }\n }\n }\n },\n \"then\":
26
+ {\n \"properties\": {\n \"links\": {\n \"type\":
27
+ \"array\",\n \"contains\": {\n \"type\": \"object\",\n
28
+ \ \"required\": [\n \"rel\",\n \"render\"\n
29
+ \ ],\n \"properties\": {\n \"render\":
30
+ {\n \"type\": \"string\"\n }\n }\n
31
+ \ }\n }\n }\n }\n }\n
32
+ \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC
33
+ Collections.\",\n \"type\": \"object\",\n \"allOf\": [\n {\n
34
+ \ \"required\": [\n \"type\",\n \"renders\"\n
35
+ \ ],\n \"properties\": {\n \"type\": {\n \"const\":
36
+ \"Collection\"\n }\n }\n },\n {\n \"$ref\":
37
+ \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n
38
+ \ {\n \"$comment\": \"This validates the fields in Collection
39
+ Assets, but does not require them.\",\n \"anyOf\": [\n {\n
40
+ \ \"type\": \"object\",\n \"required\": [\n \"assets\"\n
41
+ \ ]\n },\n {\n \"type\": \"object\",\n
42
+ \ \"required\": [\n \"item_assets\"\n ]\n
43
+ \ }\n ],\n \"properties\": {\n \"renders\":
44
+ {\n \"type\": \"object\",\n \"not\": {\n \"additionalProperties\":
45
+ {\n \"not\": {\n \"allOf\": [\n {\n
46
+ \ \"$ref\": \"#/definitions/require_any_field\"\n },\n
47
+ \ {\n \"$ref\": \"#/definitions/fields\"\n
48
+ \ }\n ]\n }\n }\n
49
+ \ }\n }\n }\n }\n ]\n }\n ],\n
50
+ \ \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n
51
+ \ \"required\": [\n \"stac_extensions\"\n ],\n \"properties\":
52
+ {\n \"stac_extensions\": {\n \"type\": \"array\",\n \"contains\":
53
+ {\n \"const\": \"https://stac-extensions.github.io/render/v2.0.0/schema.json\"\n
54
+ \ }\n }\n }\n },\n \"require_any_field\": {\n \"$comment\":
55
+ \"Please list all fields here so that we can force the existence of one of
56
+ them in other parts of the schemas.\",\n \"anyOf\": [\n {\n \"type\":
57
+ \"object\",\n \"required\": [\n \"assets\"\n ]\n
58
+ \ },\n {\n \"type\": \"object\",\n \"required\":
59
+ [\n \"title\"\n ]\n },\n {\n \"type\":
60
+ \"object\",\n \"required\": [\n \"rescale\"\n ]\n
61
+ \ },\n {\n \"type\": \"object\",\n \"required\":
62
+ [\n \"nodata\"\n ]\n },\n {\n \"type\":
63
+ \"object\",\n \"required\": [\n \"colormap_name\"\n ]\n
64
+ \ },\n {\n \"type\": \"object\",\n \"required\":
65
+ [\n \"colormap\"\n ]\n },\n {\n \"type\":
66
+ \"object\",\n \"required\": [\n \"color_formula\"\n ]\n
67
+ \ },\n {\n \"type\": \"object\",\n \"required\":
68
+ [\n \"resampling\"\n ]\n },\n {\n \"type\":
69
+ \"object\",\n \"required\": [\n \"expression\"\n ]\n
70
+ \ },\n {\n \"type\": \"object\",\n \"required\":
71
+ [\n \"minmax_zoom\"\n ]\n },\n {\n \"type\":
72
+ \"object\",\n \"required\": [\n \"bidx\"\n ]\n
73
+ \ }\n ]\n },\n \"fields\": {\n \"$comment\": \"Add your
74
+ new fields here. Don't require them here, do that above in the corresponding
75
+ schema.\",\n \"type\": \"object\",\n \"required\": [\n \"assets\"\n
76
+ \ ],\n \"properties\": {\n \"assets\": {\n \"type\":
77
+ \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n
78
+ \ },\n \"title\": {\n \"type\": \"string\"\n },\n
79
+ \ \"rescale\": {\n \"type\": \"array\",\n \"items\":
80
+ {\n \"type\": \"array\",\n \"items\": {\n \"type\":
81
+ \"number\"\n }\n }\n },\n \"nodata\": {\n
82
+ \ \"type\": [\n \"number\",\n \"string\"\n ]\n
83
+ \ },\n \"colormap_name\": {\n \"type\": \"string\"\n
84
+ \ },\n \"colormap\": {\n \"type\": \"object\"\n },\n
85
+ \ \"color_formula\": {\n \"type\": \"string\"\n },\n
86
+ \ \"resampling\": {\n \"type\": \"string\"\n },\n \"expression\":
87
+ {\n \"type\": [\"string\", \"object\", \"array\"]\n },\n \"minmax_zoom\":
88
+ {\n \"type\": \"array\",\n \"items\": {\n \"type\":
89
+ \"number\"\n }\n },\n \"bidx\": {\n \"type\":
90
+ \"array\",\n \"items\": {\n \"type\": \"number\"\n }\n
91
+ \ }\n },\n \"additionalProperties\": true\n }\n }\n}"
92
+ headers: {}
93
+ status:
94
+ code: 200
95
+ message: OK
96
+ version: 1
@@ -0,0 +1,96 @@
1
+ interactions:
2
+ - request:
3
+ body: null
4
+ headers: {}
5
+ method: GET
6
+ uri: https://stac-extensions.github.io/render/v2.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/render/v2.0.0/schema.json#\",\n \"title\":
11
+ \"Rendering Extension\",\n \"description\": \"STAC Rendering Extension for
12
+ STAC Items and STAC Collections.\",\n \"oneOf\": [\n {\n \"$comment\":
13
+ \"This is the schema for STAC Items.\",\n \"allOf\": [\n {\n \"$ref\":
14
+ \"#/definitions/stac_extensions\"\n },\n {\n \"type\":
15
+ \"object\",\n \"required\": [\n \"type\",\n \"properties\",\n
16
+ \ \"assets\"\n ],\n \"properties\": {\n \"type\":
17
+ {\n \"const\": \"Feature\"\n },\n \"properties\":
18
+ {\n \"type\": \"object\",\n \"required\": [\"renders\"],\n
19
+ \ \"properties\": {\n \"renders\": {\n \"type\":
20
+ \"object\",\n \"additionalProperties\": {\n \"$ref\":
21
+ \"#/definitions/fields\"\n }\n }\n }\n
22
+ \ }\n }\n },\n {\n \"if\": {\n \"properties\":
23
+ {\n \"stac_extensions\": {\n \"contains\": {\n
24
+ \ \"type\": \"string\",\n \"pattern\": \"https:\\/\\/stac-extensions\\\\.github\\\\.io\\/web-map-links\\/.*\"\n
25
+ \ }\n }\n }\n },\n \"then\":
26
+ {\n \"properties\": {\n \"links\": {\n \"type\":
27
+ \"array\",\n \"contains\": {\n \"type\": \"object\",\n
28
+ \ \"required\": [\n \"rel\",\n \"render\"\n
29
+ \ ],\n \"properties\": {\n \"render\":
30
+ {\n \"type\": \"string\"\n }\n }\n
31
+ \ }\n }\n }\n }\n }\n
32
+ \ ]\n },\n {\n \"$comment\": \"This is the schema for STAC
33
+ Collections.\",\n \"type\": \"object\",\n \"allOf\": [\n {\n
34
+ \ \"required\": [\n \"type\",\n \"renders\"\n
35
+ \ ],\n \"properties\": {\n \"type\": {\n \"const\":
36
+ \"Collection\"\n }\n }\n },\n {\n \"$ref\":
37
+ \"#/definitions/stac_extensions\"\n }\n ],\n \"anyOf\": [\n
38
+ \ {\n \"$comment\": \"This validates the fields in Collection
39
+ Assets, but does not require them.\",\n \"anyOf\": [\n {\n
40
+ \ \"type\": \"object\",\n \"required\": [\n \"assets\"\n
41
+ \ ]\n },\n {\n \"type\": \"object\",\n
42
+ \ \"required\": [\n \"item_assets\"\n ]\n
43
+ \ }\n ],\n \"properties\": {\n \"renders\":
44
+ {\n \"type\": \"object\",\n \"not\": {\n \"additionalProperties\":
45
+ {\n \"not\": {\n \"allOf\": [\n {\n
46
+ \ \"$ref\": \"#/definitions/require_any_field\"\n },\n
47
+ \ {\n \"$ref\": \"#/definitions/fields\"\n
48
+ \ }\n ]\n }\n }\n
49
+ \ }\n }\n }\n }\n ]\n }\n ],\n
50
+ \ \"definitions\": {\n \"stac_extensions\": {\n \"type\": \"object\",\n
51
+ \ \"required\": [\n \"stac_extensions\"\n ],\n \"properties\":
52
+ {\n \"stac_extensions\": {\n \"type\": \"array\",\n \"contains\":
53
+ {\n \"const\": \"https://stac-extensions.github.io/render/v2.0.0/schema.json\"\n
54
+ \ }\n }\n }\n },\n \"require_any_field\": {\n \"$comment\":
55
+ \"Please list all fields here so that we can force the existence of one of
56
+ them in other parts of the schemas.\",\n \"anyOf\": [\n {\n \"type\":
57
+ \"object\",\n \"required\": [\n \"assets\"\n ]\n
58
+ \ },\n {\n \"type\": \"object\",\n \"required\":
59
+ [\n \"title\"\n ]\n },\n {\n \"type\":
60
+ \"object\",\n \"required\": [\n \"rescale\"\n ]\n
61
+ \ },\n {\n \"type\": \"object\",\n \"required\":
62
+ [\n \"nodata\"\n ]\n },\n {\n \"type\":
63
+ \"object\",\n \"required\": [\n \"colormap_name\"\n ]\n
64
+ \ },\n {\n \"type\": \"object\",\n \"required\":
65
+ [\n \"colormap\"\n ]\n },\n {\n \"type\":
66
+ \"object\",\n \"required\": [\n \"color_formula\"\n ]\n
67
+ \ },\n {\n \"type\": \"object\",\n \"required\":
68
+ [\n \"resampling\"\n ]\n },\n {\n \"type\":
69
+ \"object\",\n \"required\": [\n \"expression\"\n ]\n
70
+ \ },\n {\n \"type\": \"object\",\n \"required\":
71
+ [\n \"minmax_zoom\"\n ]\n },\n {\n \"type\":
72
+ \"object\",\n \"required\": [\n \"bidx\"\n ]\n
73
+ \ }\n ]\n },\n \"fields\": {\n \"$comment\": \"Add your
74
+ new fields here. Don't require them here, do that above in the corresponding
75
+ schema.\",\n \"type\": \"object\",\n \"required\": [\n \"assets\"\n
76
+ \ ],\n \"properties\": {\n \"assets\": {\n \"type\":
77
+ \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n
78
+ \ },\n \"title\": {\n \"type\": \"string\"\n },\n
79
+ \ \"rescale\": {\n \"type\": \"array\",\n \"items\":
80
+ {\n \"type\": \"array\",\n \"items\": {\n \"type\":
81
+ \"number\"\n }\n }\n },\n \"nodata\": {\n
82
+ \ \"type\": [\n \"number\",\n \"string\"\n ]\n
83
+ \ },\n \"colormap_name\": {\n \"type\": \"string\"\n
84
+ \ },\n \"colormap\": {\n \"type\": \"object\"\n },\n
85
+ \ \"color_formula\": {\n \"type\": \"string\"\n },\n
86
+ \ \"resampling\": {\n \"type\": \"string\"\n },\n \"expression\":
87
+ {\n \"type\": [\"string\", \"object\", \"array\"]\n },\n \"minmax_zoom\":
88
+ {\n \"type\": \"array\",\n \"items\": {\n \"type\":
89
+ \"number\"\n }\n },\n \"bidx\": {\n \"type\":
90
+ \"array\",\n \"items\": {\n \"type\": \"number\"\n }\n
91
+ \ }\n },\n \"additionalProperties\": true\n }\n }\n}"
92
+ headers: {}
93
+ status:
94
+ code: 200
95
+ message: OK
96
+ version: 1
@@ -0,0 +1,42 @@
1
+ {
2
+ "stac_version": "1.1.0",
3
+ "stac_extensions": [
4
+ "https://stac-extensions.github.io/render/v2.0.0/schema.json"
5
+ ],
6
+ "type": "Collection",
7
+ "id": "senitnel2-l2a",
8
+ "title": "Sentinel-2 L2A",
9
+ "description": "Sentinel-2 L2A data",
10
+ "license": "Apache-2.0",
11
+ "extent": {
12
+ "spatial": {
13
+ "bbox": [[172.9, 1.3, 173, 1.4]]
14
+ },
15
+ "temporal": {
16
+ "interval": [["2015-06-23T00:00:00Z", null]]
17
+ }
18
+ },
19
+ "item_assets": {},
20
+ "renders": {
21
+ "ndvi": {
22
+ "title": "Normalized Difference Vegetation Index",
23
+ "assets": ["ndvi"],
24
+ "resampling": "average",
25
+ "colormap_name": "ylgn"
26
+ }
27
+ },
28
+ "summaries": {
29
+ "datetime": {
30
+ "minimum": "2015-06-23T00:00:00Z",
31
+ "maximum": "2019-07-10T13:44:56Z"
32
+ }
33
+ },
34
+ "links": [
35
+ {
36
+ "href": "./collection.json",
37
+ "rel": "root",
38
+ "title": "Sentinel-2 L2A",
39
+ "type": "application/json"
40
+ }
41
+ ]
42
+ }
@@ -0,0 +1,175 @@
1
+ {
2
+ "type": "Feature",
3
+ "stac_version": "1.1.0",
4
+ "stac_extensions": [
5
+ "https://stac-extensions.github.io/render/v2.0.0/schema.json"
6
+ ],
7
+ "id": "S2B_33SVB_20210221_0_L2A",
8
+ "bbox": [
9
+ 13.86148243891681, 36.95257399124932, 15.111074610520053,
10
+ 37.94752813015372
11
+ ],
12
+ "geometry": {
13
+ "type": "Polygon",
14
+ "coordinates": [
15
+ [
16
+ [13.876381589019879, 36.95257399124932],
17
+ [13.86148243891681, 37.942072015005024],
18
+ [15.111074610520053, 37.94752813015372],
19
+ [15.109620666835209, 36.95783951241028],
20
+ [13.876381589019879, 36.95257399124932]
21
+ ]
22
+ ]
23
+ },
24
+ "properties": {
25
+ "datetime": "2021-02-21T10:00:17Z",
26
+ "platform": "sentinel-2b",
27
+ "constellation": "sentinel-2",
28
+ "instruments": ["msi"],
29
+ "gsd": 10,
30
+ "renders": {
31
+ "thumbnail": {
32
+ "title": "Thumbnail",
33
+ "assets": ["B04", "B03", "B02"],
34
+ "rescale": [[0, 150]],
35
+ "colormap_name": "rainbow",
36
+ "resampling": "bilinear",
37
+ "bidx": [1],
38
+ "width": 1024,
39
+ "height": 1024,
40
+ "bands": ["B4", "B3", "B2"]
41
+ },
42
+ "sir": {
43
+ "title": "Shortwave Infra-red",
44
+ "assets": ["B12", "B08", "B04"],
45
+ "rescale": [
46
+ [0, 5000],
47
+ [0, 7000],
48
+ [0, 9000]
49
+ ],
50
+ "resampling": "nearest"
51
+ }
52
+ }
53
+ },
54
+ "collection": "sentinel-s2-l2a",
55
+ "assets": {
56
+ "metadata": {
57
+ "title": "Original XML metadata",
58
+ "type": "application/xml",
59
+ "roles": ["metadata"],
60
+ "href": "metadata.xml"
61
+ },
62
+ "B01": {
63
+ "title": "Band 1 (coastal)",
64
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
65
+ "roles": ["data"],
66
+ "gsd": 60,
67
+ "href": "B01.tif"
68
+ },
69
+ "B02": {
70
+ "title": "Band 2 (blue)",
71
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
72
+ "roles": ["data"],
73
+ "gsd": 10,
74
+ "href": "B02.tif"
75
+ },
76
+ "B03": {
77
+ "title": "Band 3 (green)",
78
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
79
+ "roles": ["data"],
80
+ "gsd": 10,
81
+ "href": "B03.tif"
82
+ },
83
+ "B04": {
84
+ "title": "Band 4 (red)",
85
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
86
+ "roles": ["data"],
87
+ "gsd": 10,
88
+ "href": "B04.tif"
89
+ },
90
+ "B05": {
91
+ "title": "Band 5",
92
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
93
+ "roles": ["data"],
94
+ "gsd": 20,
95
+ "href": "B05.tif"
96
+ },
97
+ "B06": {
98
+ "title": "Band 6",
99
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
100
+ "roles": ["data"],
101
+ "gsd": 20,
102
+ "href": "B06.tif"
103
+ },
104
+ "B07": {
105
+ "title": "Band 7",
106
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
107
+ "roles": ["data"],
108
+ "gsd": 20,
109
+ "href": "B07.tif"
110
+ },
111
+ "B08": {
112
+ "title": "Band 8 (nir)",
113
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
114
+ "roles": ["data"],
115
+ "gsd": 10,
116
+ "href": "B08.tif"
117
+ },
118
+ "B8A": {
119
+ "title": "Band 8A",
120
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
121
+ "roles": ["data"],
122
+ "gsd": 20,
123
+ "href": "B8A.tif"
124
+ },
125
+ "B09": {
126
+ "title": "Band 9",
127
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
128
+ "roles": ["data"],
129
+ "gsd": 60,
130
+ "href": "B09.tif"
131
+ },
132
+ "B11": {
133
+ "title": "Band 11 (swir16)",
134
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
135
+ "roles": ["data"],
136
+ "gsd": 20,
137
+ "href": "B11.tif"
138
+ },
139
+ "B12": {
140
+ "title": "Band 12 (swir22)",
141
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
142
+ "roles": ["data"],
143
+ "gsd": 20,
144
+ "href": "B12.tif"
145
+ },
146
+ "AOT": {
147
+ "title": "Aerosol Optical Thickness (AOT)",
148
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
149
+ "roles": ["data"],
150
+ "href": "AOT.tif"
151
+ },
152
+ "WVP": {
153
+ "title": "Water Vapour (WVP)",
154
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
155
+ "roles": ["data"],
156
+ "href": "WVP.tif",
157
+ "proj:shape": [10980, 10980],
158
+ "proj:transform": [10, 0, 399960, 0, -10, 4200000, 0, 0, 1]
159
+ },
160
+ "SCL": {
161
+ "title": "Scene Classification Map (SCL)",
162
+ "type": "image/tiff; application=geotiff; profile=cloud-optimized",
163
+ "roles": ["data"],
164
+ "href": "SCL.tif"
165
+ }
166
+ },
167
+ "links": [
168
+ {
169
+ "rel": "collection",
170
+ "href": "./collection.json",
171
+ "type": "application/json",
172
+ "title": "Sentinel-2 L2A Cogs Collection"
173
+ }
174
+ ]
175
+ }
@@ -0,0 +1,236 @@
1
+ """Tests for pystac.tests.extensions.render"""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ import pystac
9
+ import pystac.errors
10
+ from pystac.extensions.render import Render, RenderExtension
11
+
12
+ DATA_FILES = Path(__file__).resolve().parent / "data-files"
13
+
14
+
15
+ @pytest.fixture
16
+ def ext_collection_uri() -> str:
17
+ return str(DATA_FILES / "collection.json")
18
+
19
+
20
+ @pytest.fixture
21
+ def ext_collection(ext_collection_uri: str) -> pystac.Collection:
22
+ return pystac.Collection.from_file(ext_collection_uri)
23
+
24
+
25
+ @pytest.fixture
26
+ def ext_item_uri() -> str:
27
+ return str(DATA_FILES / "item.json")
28
+
29
+
30
+ @pytest.fixture
31
+ def ext_item(ext_item_uri: str) -> pystac.Item:
32
+ return pystac.Item.from_file(ext_item_uri)
33
+
34
+
35
+ @pytest.fixture
36
+ def render() -> Render:
37
+ return Render.create(
38
+ assets=["B4", "B3", "B2"],
39
+ title="RGB",
40
+ rescale=[[0, 1000], [0, 1000], [0, 1000]],
41
+ nodata=-9999,
42
+ colormap_name="viridis",
43
+ colormap={
44
+ "0": "#e5f5f9",
45
+ "10": "#99d8c9",
46
+ "255": "#2ca25f",
47
+ },
48
+ color_formula="gamma rg 1.3, sigmoidal rgb 22 0.1, saturation 1.5",
49
+ resampling="bilinear",
50
+ expression="(B08-B04)/(B08+B04)",
51
+ minmax_zoom=[2, 18],
52
+ )
53
+
54
+
55
+ @pytest.fixture
56
+ def thumbnail_render(ext_item: pystac.Item) -> Render:
57
+ return RenderExtension.ext(ext_item).renders["thumbnail"]
58
+
59
+
60
+ def test_collection_stac_extensions(ext_collection: pystac.Collection) -> None:
61
+ assert RenderExtension.has_extension(ext_collection)
62
+
63
+
64
+ def test_item_stac_extensions(ext_item: pystac.Item) -> None:
65
+ assert RenderExtension.has_extension(ext_item)
66
+
67
+
68
+ def test_ext_raises_if_item_does_not_conform(item: pystac.Item) -> None:
69
+ with pytest.raises(pystac.errors.ExtensionNotImplemented):
70
+ RenderExtension.ext(item)
71
+
72
+
73
+ def test_ext_raises_if_collection_does_not_conform(
74
+ collection: pystac.Collection,
75
+ ) -> None:
76
+ with pytest.raises(pystac.errors.ExtensionNotImplemented):
77
+ RenderExtension.ext(collection)
78
+
79
+
80
+ def test_ext_raises_on_catalog(catalog: pystac.Catalog) -> None:
81
+ with pytest.raises(
82
+ pystac.errors.ExtensionTypeError,
83
+ match="RenderExtension does not apply to type 'Catalog'",
84
+ ):
85
+ RenderExtension.ext(catalog) # type: ignore
86
+
87
+
88
+ def test_item_to_from_dict(ext_item_uri: str, ext_item: pystac.Item) -> None:
89
+ with open(ext_item_uri) as f:
90
+ d = json.load(f)
91
+ actual = ext_item.to_dict(include_self_link=False)
92
+ assert actual == d
93
+
94
+
95
+ def test_collection_to_from_dict(
96
+ ext_collection_uri: str, ext_collection: pystac.Collection
97
+ ) -> None:
98
+ with open(ext_collection_uri) as f:
99
+ d = json.load(f)
100
+ actual = ext_collection.to_dict(include_self_link=False)
101
+ assert actual == d
102
+
103
+
104
+ def test_add_to_item(item: pystac.Item) -> None:
105
+ assert not RenderExtension.has_extension(item)
106
+ RenderExtension.add_to(item)
107
+
108
+ assert RenderExtension.has_extension(item)
109
+
110
+
111
+ def test_add_to_collection(collection: pystac.Collection) -> None:
112
+ assert not RenderExtension.has_extension(collection)
113
+ RenderExtension.add_to(collection)
114
+
115
+ assert RenderExtension.has_extension(collection)
116
+
117
+
118
+ def test_get_render_values(thumbnail_render: Render) -> None:
119
+ assert thumbnail_render.title == "Thumbnail"
120
+ assert thumbnail_render.assets == ["B04", "B03", "B02"]
121
+ assert thumbnail_render.rescale == [[0, 150]]
122
+ assert thumbnail_render.colormap_name == "rainbow"
123
+ assert thumbnail_render.resampling == "bilinear"
124
+ assert thumbnail_render.properties.get("bidx") == [1]
125
+ assert thumbnail_render.properties.get("width") == 1024
126
+ assert thumbnail_render.properties.get("height") == 1024
127
+ assert thumbnail_render.properties.get("bands") == ["B4", "B3", "B2"]
128
+
129
+
130
+ def test_apply_renders_to_item(item: pystac.Item, render: Render) -> None:
131
+ RenderExtension.add_to(item)
132
+
133
+ RenderExtension.ext(item).apply({"render": render})
134
+ assert item.ext.render.renders["render"].assets == ["B4", "B3", "B2"]
135
+ assert item.ext.render.renders["render"].title == "RGB"
136
+ assert item.ext.render.renders["render"].rescale == [
137
+ [0, 1000],
138
+ [0, 1000],
139
+ [0, 1000],
140
+ ]
141
+ assert item.ext.render.renders["render"].nodata == -9999
142
+ assert item.ext.render.renders["render"].colormap_name == "viridis"
143
+ assert item.ext.render.renders["render"].colormap == {
144
+ "0": "#e5f5f9",
145
+ "10": "#99d8c9",
146
+ "255": "#2ca25f",
147
+ }
148
+ assert (
149
+ item.ext.render.renders["render"].color_formula
150
+ == "gamma rg 1.3, sigmoidal rgb 22 0.1, saturation 1.5"
151
+ )
152
+ assert item.ext.render.renders["render"].resampling == "bilinear"
153
+ assert item.ext.render.renders["render"].expression == "(B08-B04)/(B08+B04)"
154
+ assert item.ext.render.renders["render"].minmax_zoom == [2, 18]
155
+
156
+ assert item.ext.render.renders["render"] == render
157
+
158
+
159
+ def test_get_unset_properties(item: pystac.Item) -> None:
160
+ RenderExtension.add_to(item)
161
+ RenderExtension.ext(item).apply(
162
+ {
163
+ "render": Render.create(
164
+ assets=["B4", "B3", "B2"],
165
+ )
166
+ }
167
+ )
168
+
169
+ assert item.ext.render.renders["render"].title is None
170
+ assert item.ext.render.renders["render"].rescale is None
171
+ assert item.ext.render.renders["render"].nodata is None
172
+ assert item.ext.render.renders["render"].colormap_name is None
173
+ assert item.ext.render.renders["render"].colormap is None
174
+ assert item.ext.render.renders["render"].color_formula is None
175
+ assert item.ext.render.renders["render"].resampling is None
176
+ assert item.ext.render.renders["render"].expression is None
177
+ assert item.ext.render.renders["render"].minmax_zoom is None
178
+
179
+
180
+ def test_equality_check_with_unexpected_type_raises_notimplemented_error() -> None:
181
+ render = Render.create(
182
+ assets=["B4", "B3", "B2"],
183
+ )
184
+ with pytest.raises(NotImplementedError):
185
+ _ = render == 1
186
+
187
+
188
+ def test_item_repr(ext_item: pystac.Item) -> None:
189
+ ext = RenderExtension.ext(ext_item)
190
+ assert ext.__repr__() == f"<ItemRenderExtension Item id={ext_item.id}>"
191
+
192
+
193
+ def test_collection_repr(ext_collection: pystac.Collection) -> None:
194
+ ext = RenderExtension.ext(ext_collection)
195
+ assert (
196
+ ext.__repr__()
197
+ == f"<CollectionRenderExtension Collection id={ext_collection.id}>"
198
+ )
199
+
200
+
201
+ def test_render_repr() -> None:
202
+ render = Render.create(
203
+ assets=["B4", "B3", "B2"],
204
+ title="RGB",
205
+ rescale=[[0, 1000], [0, 1000], [0, 1000]],
206
+ nodata=-9999,
207
+ colormap_name="viridis",
208
+ colormap={
209
+ "0": "#e5f5f9",
210
+ "10": "#99d8c9",
211
+ "255": "#2ca25f",
212
+ },
213
+ color_formula="gamma rg 1.3, sigmoidal rgb 22 0.1, saturation 1.5",
214
+ resampling="bilinear",
215
+ expression="(B08-B04)/(B08+B04)",
216
+ minmax_zoom=[2, 18],
217
+ )
218
+
219
+ assert render.__repr__() == (
220
+ "<Render assets=['B4', 'B3', 'B2'] "
221
+ "title=RGB rescale=[[0, 1000], [0, 1000], [0, 1000]] "
222
+ "nodata=-9999 colormap_name=viridis "
223
+ "colormap={'0': '#e5f5f9', '10': '#99d8c9', '255': '#2ca25f'} "
224
+ "color_formula=gamma rg 1.3, sigmoidal rgb 22 0.1, saturation 1.5 "
225
+ "resampling=bilinear expression=(B08-B04)/(B08+B04) minmax_zoom=[2, 18]>"
226
+ )
227
+
228
+
229
+ @pytest.mark.vcr
230
+ def test_item_validate(ext_item: pystac.Item) -> None:
231
+ assert ext_item.validate()
232
+
233
+
234
+ @pytest.mark.vcr
235
+ def test_collection_validate(ext_collection: pystac.Collection) -> None:
236
+ assert ext_collection.validate()