diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 07353d32c..bcd4456e2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -49,6 +49,11 @@ jobs:
python -m pip install -e src/titiler/extensions["test,cogeo,stac"]
python -m pytest src/titiler/extensions --cov=titiler.extensions --cov-report=xml --cov-append --cov-report=term-missing
+ - name: Test titiler.xarray
+ run: |
+ python -m pip install -e src/titiler/xarray["test"]
+ python -m pytest src/titiler/xarray --cov=titiler.xarray --cov-report=xml --cov-append --cov-report=term-missing
+
- name: Test titiler.mosaic
run: |
python -m pip install -e src/titiler/mosaic["test"]
diff --git a/.github/workflows/deploy_mkdocs.yml b/.github/workflows/deploy_mkdocs.yml
index 31536e303..a3a2e3db0 100644
--- a/.github/workflows/deploy_mkdocs.yml
+++ b/.github/workflows/deploy_mkdocs.yml
@@ -29,7 +29,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- python -m pip install src/titiler/core src/titiler/extensions["cogeo,stac"] src/titiler/mosaic src/titiler/application
+ python -m pip install src/titiler/core src/titiler/extensions["cogeo,stac"] src/titiler/xarray src/titiler/mosaic src/titiler/application
python -m pip install -r requirements/requirements-docs.txt
diff --git a/README.md b/README.md
index c38adc3d9..5dc5daadc 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,7 @@ Starting with version `0.3.0`, the `TiTiler` python module has been split into a
| Package | Version | Description
| ------- | ------- |-------------
[**titiler.core**](https://github.com/developmentseed/titiler/tree/main/src/titiler/core) | [![titiler.core](https://img.shields.io/pypi/v/titiler.core?color=%2334D058&label=pypi)](https://pypi.org/project/titiler.core) | The `Core` package contains libraries to help create a dynamic tiler for COG and STAC
+[**titiler.xarray**](https://github.com/developmentseed/titiler/tree/main/src/titiler/xarray) | [![titiler.xarray](https://img.shields.io/pypi/v/titiler.xarray?color=%2334D058&label=pypi)](https://pypi.org/project/titiler.xarray) | The `xarray` package contains libraries to help create a dynamic tiler for Zarr/NetCDF datasets
[**titiler.extensions**](https://github.com/developmentseed/titiler/tree/main/src/titiler/extensions) | [![titiler.extensions](https://img.shields.io/pypi/v/titiler.extensions?color=%2334D058&label=pypi)](https://pypi.org/project/titiler.extensions) | TiTiler's extensions package. Contains extensions for Tiler Factories.
[**titiler.mosaic**](https://github.com/developmentseed/titiler/tree/main/src/titiler/mosaic) | [![titiler.mosaic](https://img.shields.io/pypi/v/titiler.mosaic?color=%2334D058&label=pypi)](https://pypi.org/project/titiler.mosaic) | The `mosaic` package contains libraries to help create a dynamic tiler for MosaicJSON (adds `cogeo-mosaic` requirement)
[**titiler.application**](https://github.com/developmentseed/titiler/tree/main/src/titiler/application) | [![titiler.application](https://img.shields.io/pypi/v/titiler.application?color=%2334D058&label=pypi)](https://pypi.org/project/titiler.application) | TiTiler's `demo` package. Contains a FastAPI application with full support of COG, STAC and MosaicJSON
@@ -71,6 +72,7 @@ python -m pip install -U pip
python -m pip install titiler.{package}
# e.g.,
# python -m pip install titiler.core
+# python -m pip install titiler.xarray
# python -m pip install titiler.extensions
# python -m pip install titiler.mosaic
# python -m pip install titiler.application (also installs core, extensions and mosaic)
@@ -89,7 +91,7 @@ git clone https://github.com/developmentseed/titiler.git
cd titiler
python -m pip install -U pip
-python -m pip install -e src/titiler/core -e src/titiler/extensions -e src/titiler/mosaic -e src/titiler/application
+python -m pip install -e src/titiler/core -e src/titiler/xarray -e src/titiler/extensions -e src/titiler/mosaic -e src/titiler/application
python -m pip install uvicorn
uvicorn titiler.application.main:app --reload
@@ -125,6 +127,7 @@ Some options can be set via environment variables, see: https://github.com/tiang
src/titiler/ - titiler modules.
├── application/ - Titiler's `Application` package
├── extensions/ - Titiler's `Extensions` package
+ ├── xarray/ - Titiler's `Xarray` package
├── core/ - Titiler's `Core` package
└── mosaic/ - Titiler's `Mosaic` package
```
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 9d91f4104..05c77b3ba 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -39,7 +39,6 @@ nav:
- User Guide:
- Intro: "intro.md"
- Dynamic Tiling: "dynamic_tiling.md"
- - Mosaics: "mosaics.md"
- TileMatrixSets: "tile_matrix_sets.md"
- Output data format: "output_format.md"
@@ -53,6 +52,13 @@ nav:
- Rendering: "advanced/rendering.md"
# - APIRoute and environment variables: "advanced/APIRoute_and_environment_variables.md"
+ - Packages:
+ - titiler.core: "packages/core.md"
+ - titiler.xarray: "packages/xarray.md"
+ - titiler.extensions: "packages/extensions.md"
+ - titiler.mosaic: "packages/mosaic.md"
+ - titiler.application: "packages/application.md"
+
- Endpoints documentation:
- /cog: "endpoints/cog.md"
- /stac: "endpoints/stac.md"
@@ -109,6 +115,11 @@ nav:
- errors: api/titiler/mosaic/errors.md
- models:
- responses: api/titiler/mosaic/models/responses.md
+ - titiler.xarray:
+ - io: api/titiler/xarray/io.md
+ - dependencies: api/titiler/xarray/dependencies.md
+ - extensions: api/titiler/xarray/extensions.md
+ - factory: api/titiler/xarray/factory.md
- Deployment:
- Amazon Web Services:
diff --git a/docs/src/advanced/endpoints_factories.md b/docs/src/advanced/endpoints_factories.md
index 9d5bcdf1a..033186d1e 100644
--- a/docs/src/advanced/endpoints_factories.md
+++ b/docs/src/advanced/endpoints_factories.md
@@ -7,8 +7,9 @@ TiTiler's endpoints factories are helper functions that let users create a FastA
Factories classes use [dependencies injection](dependencies.md) to define most of the endpoint options.
+## titiler.core
-## BaseFactory
+### BaseFactory
class: `titiler.core.factory.BaseFactory`
@@ -27,7 +28,7 @@ Most **Factories** are built from this [abstract based class](https://docs.pytho
- **url_for**: Method to construct endpoint URL
- **add_route_dependencies**: Add dependencies to routes.
-## TilerFactory
+### TilerFactory
class: `titiler.core.factory.TilerFactory`
@@ -40,7 +41,7 @@ Factory meant to create endpoints for single dataset using [*rio-tiler*'s `Reade
- **path_dependency**: Dependency to use to define the dataset url. Defaults to `titiler.core.dependencies.DatasetPathParams`.
- **layer_dependency**: Dependency to define band indexes or expression. Defaults to `titiler.core.dependencies.BidxExprParams`.
- **dataset_dependency**: Dependency to overwrite `nodata` value, apply `rescaling` and change the `I/O` or `Warp` resamplings. Defaults to `titiler.core.dependencies.DatasetParams`.
-- **tile_dependency**: Dependency to defile `buffer` and `padding` to apply at tile creation. Defaults to `titiler.core.dependencies.TileParams`.
+- **tile_dependency**: Dependency to define `buffer` and `padding` to apply at tile creation. Defaults to `titiler.core.dependencies.TileParams`.
- **stats_dependency**: Dependency to define options for *rio-tiler*'s statistics method used in `/statistics` endpoints. Defaults to `titiler.core.dependencies.StatisticsParams`.
- **histogram_dependency**: Dependency to define *numpy*'s histogram options used in `/statistics` endpoints. Defaults to `titiler.core.dependencies.HistogramParams`.
- **img_preview_dependency**: Dependency to define image size for `/preview` and `/statistics` endpoints. Defaults to `titiler.core.dependencies.PreviewParams`.
@@ -50,7 +51,7 @@ Factory meant to create endpoints for single dataset using [*rio-tiler*'s `Reade
- **color_formula_dependency**: Dependency to define the Color Formula. Defaults to `titiler.core.dependencies.ColorFormulaParams`.
- **colormap_dependency**: Dependency to define the Colormap options. Defaults to `titiler.core.dependencies.ColorMapParams`
- **render_dependency**: Dependency to control output image rendering options. Defaults to `titiler.core.dependencies.ImageRenderingParams`
-- **environment_dependency**: Dependency to defile GDAL environment at runtime. Default to `lambda: {}`.
+- **environment_dependency**: Dependency to define GDAL environment at runtime. Default to `lambda: {}`.
- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`.
- **templates**: *Jinja2* templates to use in endpoints. Defaults to `titiler.core.factory.DEFAULT_TEMPLATES`.
- **add_preview**: . Add `/preview` endpoint to the router. Defaults to `True`.
@@ -97,7 +98,7 @@ app.include_router(cog.router)
| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a GeoJSON feature **Optional**
-## MultiBaseTilerFactory
+### MultiBaseTilerFactory
class: `titiler.core.factory.MultiBaseTilerFactory`
@@ -143,8 +144,7 @@ app.include_router(stac.router)
| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of assets **Optional**
| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson feature intersecting assets **Optional**
-
-## MultiBandTilerFactory
+### MultiBandTilerFactory
class: `titiler.core.factory.MultiBandTilerFactory`
@@ -201,56 +201,7 @@ app.include_router(landsat.router)
| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson feature **Optional**
-## MosaicTilerFactory
-
-class: `titiler.mosaic.factory.MosaicTilerFactory`
-
-Endpoints factory for mosaics, built on top of [MosaicJSON](https://github.com/developmentseed/mosaicjson-spec).
-
-#### Attributes
-
-- **backend**: `cogeo_mosaic.backends.BaseBackend` Mosaic backend. Defaults to `cogeo_mosaic.backend.MosaicBackend`.
-- **backend_dependency**: Dependency to control options passed to the backend instance init. Defaults to `titiler.core.dependencies.DefaultDependency`
-- **dataset_reader**: Dataset Reader. Defaults to `rio_tiler.io.Reader`
-- **reader_dependency**: Dependency to control options passed to the reader instance init. Defaults to `titiler.core.dependencies.DefaultDependency`
-- **path_dependency**: Dependency to use to define the dataset url. Defaults to `titiler.mosaic.factory.DatasetPathParams`.
-- **layer_dependency**: Dependency to define band indexes or expression. Defaults to `titiler.core.dependencies.BidxExprParams`.
-- **dataset_dependency**: Dependency to overwrite `nodata` value, apply `rescaling` and change the `I/O` or `Warp` resamplings. Defaults to `titiler.core.dependencies.DatasetParams`.
-- **tile_dependency**: Dependency to defile `buffer` and `padding` to apply at tile creation. Defaults to `titiler.core.dependencies.TileParams`.
-- **process_dependency**: Dependency to control which `algorithm` to apply to the data. Defaults to `titiler.core.algorithm.algorithms.dependency`.
-- **rescale_dependency**: Dependency to set Min/Max values to rescale from, to 0 -> 255. Defaults to `titiler.core.dependencies.RescalingParams`.
-- **color_formula_dependency**: Dependency to define the Color Formula. Defaults to `titiler.core.dependencies.ColorFormulaParams`.
-- **colormap_dependency**: Dependency to define the Colormap options. Defaults to `titiler.core.dependencies.ColorMapParams`
-- **render_dependency**: Dependency to control output image rendering options. Defaults to `titiler.core.dependencies.ImageRenderingParams`
-- **pixel_selection_dependency**: Dependency to select the `pixel_selection` method. Defaults to `titiler.mosaic.factory.PixelSelectionParams`.
-- **environment_dependency**: Dependency to defile GDAL environment at runtime. Default to `lambda: {}`.
-- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`.
-- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`.
-- **templates**: *Jinja2* templates to use in endpoints. Defaults to `titiler.core.factory.DEFAULT_TEMPLATES`.
-- **optional_headers**: List of OptionalHeader which endpoints could add (if implemented). Defaults to `[]`.
-- **add_viewer**: . Add `/map` endpoints to the router. Defaults to `True`.
-
-#### Endpoints
-
-| Method | URL | Output | Description
-| ------ | --------------------------------------------------------------- |--------------------------------------------------- |--------------
-| `GET` | `/` | JSON [MosaicJSON][mosaic_model] | return a MosaicJSON document
-| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return mosaic's bounds
-| `GET` | `/info` | JSON ([Info][mosaic_info_model]) | return mosaic's basic info
-| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][mosaic_geojson_info_model]) | return mosaic's basic info as a GeoJSON feature
-| `GET` | `/tiles` | JSON | List of OGC Tilesets available
-| `GET` | `/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata
-| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a MosaicJSON
-| `GET` | `/{tileMatrixSetId}/map` | HTML | return a simple map viewer **Optional**
-| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document
-| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
-| `GET` | `/point/{lon},{lat}` | JSON ([Point][mosaic_point]) | return pixel value from a MosaicJSON dataset
-| `GET` | `/{z}/{x}/{y}/assets` | JSON | return list of assets intersecting a XYZ tile
-| `GET` | `/{lon},{lat}/assets` | JSON | return list of assets intersecting a point
-| `GET` | `/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box
-
-
-## TMSFactory
+### TMSFactory
class: `titiler.core.factory.TMSFactory`
@@ -278,7 +229,7 @@ app.include_router(tms.router)
| `GET` | `/tileMatrixSets/{tileMatrixSetId}` | JSON ([TileMatrixSet][tilematrixset]) | retrieve the definition of the specified tiling scheme (tile matrix set)
-## AlgorithmFactory
+### AlgorithmFactory
class: `titiler.core.factory.AlgorithmFactory`
@@ -306,7 +257,7 @@ app.include_router(algo.router)
| `GET` | `/algorithms/{algorithmId}` | JSON ([Algorithm Metadata][algorithm_metadata]) | retrieve the metadata of the specified algorithm.
-## ColorMapFactory
+### ColorMapFactory
class: `titiler.core.factory.ColorMapFactory`
@@ -334,6 +285,121 @@ app.include_router(colormap.router)
| `GET` | `/colorMaps/{colorMapId}` | JSON ([colorMap][colormap]) | retrieve the metadata or image of the specified colorMap.
+## titiler.mosaic
+
+### MosaicTilerFactory
+
+class: `titiler.mosaic.factory.MosaicTilerFactory`
+
+Endpoints factory for mosaics, built on top of [MosaicJSON](https://github.com/developmentseed/mosaicjson-spec).
+
+#### Attributes
+
+- **backend**: `cogeo_mosaic.backends.BaseBackend` Mosaic backend. Defaults to `cogeo_mosaic.backend.MosaicBackend`.
+- **backend_dependency**: Dependency to control options passed to the backend instance init. Defaults to `titiler.core.dependencies.DefaultDependency`
+- **dataset_reader**: Dataset Reader. Defaults to `rio_tiler.io.Reader`
+- **reader_dependency**: Dependency to control options passed to the reader instance init. Defaults to `titiler.core.dependencies.DefaultDependency`
+- **path_dependency**: Dependency to use to define the dataset url. Defaults to `titiler.mosaic.factory.DatasetPathParams`.
+- **layer_dependency**: Dependency to define band indexes or expression. Defaults to `titiler.core.dependencies.BidxExprParams`.
+- **dataset_dependency**: Dependency to overwrite `nodata` value, apply `rescaling` and change the `I/O` or `Warp` resamplings. Defaults to `titiler.core.dependencies.DatasetParams`.
+- **tile_dependency**: Dependency to define `buffer` and `padding` to apply at tile creation. Defaults to `titiler.core.dependencies.TileParams`.
+- **process_dependency**: Dependency to control which `algorithm` to apply to the data. Defaults to `titiler.core.algorithm.algorithms.dependency`.
+- **rescale_dependency**: Dependency to set Min/Max values to rescale from, to 0 -> 255. Defaults to `titiler.core.dependencies.RescalingParams`.
+- **color_formula_dependency**: Dependency to define the Color Formula. Defaults to `titiler.core.dependencies.ColorFormulaParams`.
+- **colormap_dependency**: Dependency to define the Colormap options. Defaults to `titiler.core.dependencies.ColorMapParams`
+- **render_dependency**: Dependency to control output image rendering options. Defaults to `titiler.core.dependencies.ImageRenderingParams`
+- **pixel_selection_dependency**: Dependency to select the `pixel_selection` method. Defaults to `titiler.mosaic.factory.PixelSelectionParams`.
+- **environment_dependency**: Dependency to define GDAL environment at runtime. Default to `lambda: {}`.
+- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`.
+- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`.
+- **templates**: *Jinja2* templates to use in endpoints. Defaults to `titiler.core.factory.DEFAULT_TEMPLATES`.
+- **optional_headers**: List of OptionalHeader which endpoints could add (if implemented). Defaults to `[]`.
+- **add_viewer**: . Add `/map` endpoints to the router. Defaults to `True`.
+
+#### Endpoints
+
+| Method | URL | Output | Description
+| ------ | --------------------------------------------------------------- |--------------------------------------------------- |--------------
+| `GET` | `/` | JSON [MosaicJSON][mosaic_model] | return a MosaicJSON document
+| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return mosaic's bounds
+| `GET` | `/info` | JSON ([Info][mosaic_info_model]) | return mosaic's basic info
+| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][mosaic_geojson_info_model]) | return mosaic's basic info as a GeoJSON feature
+| `GET` | `/tiles` | JSON | List of OGC Tilesets available
+| `GET` | `/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata
+| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a MosaicJSON
+| `GET` | `/{tileMatrixSetId}/map` | HTML | return a simple map viewer **Optional**
+| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document
+| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
+| `GET` | `/point/{lon},{lat}` | JSON ([Point][mosaic_point]) | return pixel value from a MosaicJSON dataset
+| `GET` | `/{z}/{x}/{y}/assets` | JSON | return list of assets intersecting a XYZ tile
+| `GET` | `/{lon},{lat}/assets` | JSON | return list of assets intersecting a point
+| `GET` | `/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box
+
+## titiler.xarray
+
+### TilerFactory
+
+class: `titiler.xarray.factory.TilerFactory`
+
+#### Attributes
+
+- **reader**: Dataset Reader **required**.
+- **path_dependency**: Dependency to use to define the dataset url. Defaults to `titiler.core.dependencies.DatasetPathParams`.
+- **reader_dependency**: Dependency to control options passed to the reader instance init. Defaults to `titiler.xarray.dependencies.XarrayParams`
+- **layer_dependency**: Dependency to define band indexes or expression. Defaults to `titiler.core.dependencies.DefaultDependency`.
+- **dataset_dependency**: Dependency to overwrite `nodata` value and change the `Warp` resamplings. Defaults to `titiler.xarray.dependencies.DatasetParams`.
+- **tile_dependency**: Dependency for tile creation options. Defaults to `titiler.core.dependencies.DefaultDependency`.
+- **stats_dependency**: Dependency to define options for *rio-tiler*'s statistics method used in `/statistics` endpoints. Defaults to `titiler.core.dependencies.StatisticsParams`.
+- **histogram_dependency**: Dependency to define *numpy*'s histogram options used in `/statistics` endpoints. Defaults to `titiler.core.dependencies.HistogramParams`.
+- **img_part_dependency**: Dependency to define image size for `/bbox` and `/feature` endpoints. Defaults to `titiler.xarray.dependencies.PartFeatureParams`.
+- **process_dependency**: Dependency to control which `algorithm` to apply to the data. Defaults to `titiler.core.algorithm.algorithms.dependency`.
+- **rescale_dependency**: Dependency to set Min/Max values to rescale from, to 0 -> 255. Defaults to `titiler.core.dependencies.RescalingParams`.
+- **color_formula_dependency**: Dependency to define the Color Formula. Defaults to `titiler.core.dependencies.ColorFormulaParams`.
+- **colormap_dependency**: Dependency to define the Colormap options. Defaults to `titiler.core.dependencies.ColorMapParams`
+- **render_dependency**: Dependency to control output image rendering options. Defaults to `titiler.core.dependencies.ImageRenderingParams`
+- **environment_dependency**: Dependency to define GDAL environment at runtime. Default to `lambda: {}`.
+- **supported_tms**: List of available TileMatrixSets. Defaults to `morecantile.tms`.
+- **templates**: *Jinja2* templates to use in endpoints. Defaults to `titiler.core.factory.DEFAULT_TEMPLATES`.
+- **add_part**: . Add `/bbox` and `/feature` endpoints to the router. Defaults to `True`.
+- **add_viewer**: . Add `/map` endpoints to the router. Defaults to `True`.
+
+
+```python
+from fastapi import FastAPI
+
+from titiler.xarray.factory import TilerFactory
+
+# Create FastAPI application
+app = FastAPI()
+
+# Create router and register set of endpoints
+md = TilerFactory(
+ add_part=True,
+ add_viewer=True,
+)
+
+# add router endpoint to the main application
+app.include_router(md.router)
+```
+
+#### Endpoints
+
+| Method | URL | Output | Description
+| ------ | --------------------------------------------------------------- |-------------------------------------------- |--------------
+| `GET` | `/bounds` | JSON ([Bounds][bounds_model]) | return dataset's bounds
+| `GET` | `/info` | JSON ([Info][info_model]) | return dataset's basic info
+| `GET` | `/info.geojson` | GeoJSON ([InfoGeoJSON][info_geojson_model]) | return dataset's basic info as a GeoJSON feature
+| `POST` | `/statistics` | GeoJSON ([Statistics][stats_geojson_model]) | return dataset's statistics for a GeoJSON
+| `GET` | `/tiles` | JSON | List of OGC Tilesets available
+| `GET` | `/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata
+| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset
+| `GET` | `/{tileMatrixSetId}/map` | HTML | return a simple map viewer **Optional**
+| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document
+| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
+| `GET` | `/point/{lon},{lat}` | JSON ([Point][point_model]) | return pixel values from a dataset
+| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset **Optional**
+| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a GeoJSON feature **Optional**
+
[bounds_model]: https://github.com/cogeotiff/rio-tiler/blob/9aaa88000399ee8d36e71d176f67b6ea3ec53f2d/rio_tiler/models.py#L43-L46
[info_model]: https://github.com/cogeotiff/rio-tiler/blob/9aaa88000399ee8d36e71d176f67b6ea3ec53f2d/rio_tiler/models.py#L56-L72
diff --git a/docs/src/api/titiler/xarray/dependencies.md b/docs/src/api/titiler/xarray/dependencies.md
new file mode 100644
index 000000000..6bb4bf4a6
--- /dev/null
+++ b/docs/src/api/titiler/xarray/dependencies.md
@@ -0,0 +1 @@
+::: titiler.xarray.dependencies
diff --git a/docs/src/api/titiler/xarray/extensions.md b/docs/src/api/titiler/xarray/extensions.md
new file mode 100644
index 000000000..e6b41dc30
--- /dev/null
+++ b/docs/src/api/titiler/xarray/extensions.md
@@ -0,0 +1 @@
+::: titiler.xarray.extensions
diff --git a/docs/src/api/titiler/xarray/factory.md b/docs/src/api/titiler/xarray/factory.md
new file mode 100644
index 000000000..74f2363b4
--- /dev/null
+++ b/docs/src/api/titiler/xarray/factory.md
@@ -0,0 +1 @@
+::: titiler.xarray.factory
diff --git a/docs/src/api/titiler/xarray/io.md b/docs/src/api/titiler/xarray/io.md
new file mode 100644
index 000000000..37ccfec84
--- /dev/null
+++ b/docs/src/api/titiler/xarray/io.md
@@ -0,0 +1 @@
+::: titiler.xarray.io
diff --git a/docs/src/mosaics.md b/docs/src/mosaics.md
deleted file mode 100644
index 1695bca95..000000000
--- a/docs/src/mosaics.md
+++ /dev/null
@@ -1,16 +0,0 @@
-
-[Work in Progress]
-
-![](img/africa_mosaic.png)
-
-`Titiler` has native support for reading and creating web map tiles from **MosaicJSON**.
-
-> MosaicJSON is an open standard for representing metadata about a mosaic of Cloud-Optimized GeoTIFF (COG) files.
-
-Ref: https://github.com/developmentseed/mosaicjson-spec
-
-
-### Links
-
-- https://medium.com/devseed/cog-talk-part-2-mosaics-bbbf474e66df
-- https://github.com/developmentseed/cogeo-mosaic
diff --git a/docs/src/packages/application.md b/docs/src/packages/application.md
new file mode 120000
index 000000000..9f698a377
--- /dev/null
+++ b/docs/src/packages/application.md
@@ -0,0 +1 @@
+../../../src/titiler/application/README.md
\ No newline at end of file
diff --git a/docs/src/packages/core.md b/docs/src/packages/core.md
new file mode 120000
index 000000000..fff7ecdd5
--- /dev/null
+++ b/docs/src/packages/core.md
@@ -0,0 +1 @@
+../../../src/titiler/core/README.md
\ No newline at end of file
diff --git a/docs/src/packages/extensions.md b/docs/src/packages/extensions.md
new file mode 120000
index 000000000..6fcc9e3d1
--- /dev/null
+++ b/docs/src/packages/extensions.md
@@ -0,0 +1 @@
+../../../src/titiler/extensions/README.md
\ No newline at end of file
diff --git a/docs/src/packages/mosaic.md b/docs/src/packages/mosaic.md
new file mode 120000
index 000000000..cf87cb31c
--- /dev/null
+++ b/docs/src/packages/mosaic.md
@@ -0,0 +1 @@
+../../../src/titiler/mosaic/README.md
\ No newline at end of file
diff --git a/docs/src/packages/xarray.md b/docs/src/packages/xarray.md
new file mode 120000
index 000000000..dc85e70b9
--- /dev/null
+++ b/docs/src/packages/xarray.md
@@ -0,0 +1 @@
+../../../src/titiler/xarray/README.md
\ No newline at end of file
diff --git a/scripts/publish b/scripts/publish
index 7e548c6ec..1383cd75f 100755
--- a/scripts/publish
+++ b/scripts/publish
@@ -2,6 +2,7 @@
SUBPACKAGE_DIRS=(
"core"
+ "xarray"
"mosaic"
"application"
"extensions"
diff --git a/src/titiler/application/README.md b/src/titiler/application/README.md
index ec06a22cd..166c10dc3 100644
--- a/src/titiler/application/README.md
+++ b/src/titiler/application/README.md
@@ -6,14 +6,14 @@
## Installation
```bash
-$ python -m pip install -U pip
+python -m pip install -U pip
# From Pypi
-$ python -m pip install titiler.application
+python -m pip install titiler.application
# Or from sources
-$ git clone https://github.com/developmentseed/titiler.git
-$ cd titiler && python -m pip install -e src/titiler/core -e src/titiler/extensions -e src/titiler/mosaic -e src/titiler/application
+git clone https://github.com/developmentseed/titiler.git
+cd titiler && python -m pip install -e src/titiler/core -e src/titiler/extensions -e src/titiler/mosaic -e src/titiler/application
```
Launch Application
diff --git a/src/titiler/core/README.md b/src/titiler/core/README.md
index 1ce51547d..79598062d 100644
--- a/src/titiler/core/README.md
+++ b/src/titiler/core/README.md
@@ -5,14 +5,14 @@ Core of Titiler's application. Contains blocks to create dynamic tile servers.
## Installation
```bash
-$ python -m pip install -U pip
+python -m pip install -U pip
# From Pypi
-$ python -m pip install titiler.core
+python -m pip install titiler.core
# Or from sources
-$ git clone https://github.com/developmentseed/titiler.git
-$ cd titiler && python -m pip install -e src/titiler/core
+git clone https://github.com/developmentseed/titiler.git
+cd titiler && python -m pip install -e src/titiler/core
```
## How To
diff --git a/src/titiler/core/titiler/core/factory.py b/src/titiler/core/titiler/core/factory.py
index 892337102..faad24749 100644
--- a/src/titiler/core/titiler/core/factory.py
+++ b/src/titiler/core/titiler/core/factory.py
@@ -1277,7 +1277,7 @@ def bbox_image(
):
"""Create image from a bbox."""
with rasterio.Env(**env):
- with self.reader(src_path, **reader_params) as src_dst:
+ with self.reader(src_path, **reader_params.as_dict()) as src_dst:
image = src_dst.part(
[minx, miny, maxx, maxy],
dst_crs=dst_crs,
diff --git a/src/titiler/extensions/README.md b/src/titiler/extensions/README.md
index c7ce05f70..3451fae87 100644
--- a/src/titiler/extensions/README.md
+++ b/src/titiler/extensions/README.md
@@ -5,14 +5,14 @@ Extent TiTiler Tiler Factories
## Installation
```bash
-$ python -m pip install -U pip
+python -m pip install -U pip
# From Pypi
-$ python -m pip install titiler.extensions
+python -m pip install titiler.extensions
# Or from sources
-$ git clone https://github.com/developmentseed/titiler.git
-$ cd titiler && python -m pip install -e src/titiler/core -e src/titiler/extensions
+git clone https://github.com/developmentseed/titiler.git
+cd titiler && python -m pip install -e src/titiler/core -e src/titiler/extensions
```
## Available extensions
diff --git a/src/titiler/mosaic/README.md b/src/titiler/mosaic/README.md
index 6c010e8f9..10758ed34 100644
--- a/src/titiler/mosaic/README.md
+++ b/src/titiler/mosaic/README.md
@@ -1,18 +1,24 @@
## titiler.mosaic
-Adds support for MosaicJSON in Titiler.
+
+
+Adds support for [MosaicJSON](https://github.com/developmentseed/mosaicjson-spec) in Titiler.
+
+> MosaicJSON is an open standard for representing metadata about a mosaic of Cloud-Optimized GeoTIFF (COG) files.
+
+Ref: https://github.com/developmentseed/mosaicjson-spec
## Installation
```bash
-$ python -m pip install -U pip
+python -m pip install -U pip
# From Pypi
-$ python -m pip install titiler.mosaic
+python -m pip install titiler.mosaic
# Or from sources
-$ git clone https://github.com/developmentseed/titiler.git
-$ cd titiler && python -m pip install -e src/titiler/core -e src/titiler/mosaic
+git clone https://github.com/developmentseed/titiler.git
+cd titiler && python -m pip install -e src/titiler/core -e src/titiler/mosaic
```
## How To
@@ -23,7 +29,7 @@ from titiler.mosaic.factory import MosaicTilerFactory
# Create a FastAPI application
app = FastAPI(
- description="A lightweight Cloud Optimized GeoTIFF tile server",
+ description="A Mosaic tile server",
)
# Create a set of MosaicJSON endpoints
@@ -33,15 +39,13 @@ mosaic = MosaicTilerFactory()
app.include_router(mosaic.router, tags=["MosaicJSON"])
```
-See [titiler.application](../application) for a full example.
-
## Package structure
```
titiler/
└── mosaic/
├── tests/ - Tests suite
- └── titiler/mosaic/ - `mosaic` namespace package
+ └── titiler/mosaic/ - `mosaic` namespace package
├── models/
| └── responses.py - mosaic response models
├── errors.py - cogeo-mosaic known errors
diff --git a/src/titiler/xarray/README.md b/src/titiler/xarray/README.md
new file mode 100644
index 000000000..f33c2fe61
--- /dev/null
+++ b/src/titiler/xarray/README.md
@@ -0,0 +1,120 @@
+## titiler.xarray
+
+Adds support for Xarray Dataset (NetCDF/Zarr) in Titiler.
+
+## Installation
+
+```bash
+python -m pip install -U pip
+
+# From Pypi
+python -m pip install "titiler.xarray[full]"
+
+# Or from sources
+git clone https://github.com/developmentseed/titiler.git
+cd titiler && python -m pip install -e src/titiler/core -e "src/titiler/xarray[full]"
+```
+
+#### Installation options
+
+Default installation for `titiler.xarray` DOES NOT include `fsspec` or any storage's specific dependencies (e.g `s3fs`) nor `engine` dependencies (`zarr`, `h5netcdf`). This is to ease the customization and deployment of user's applications. If you want to use the default's dataset reader you will need to at least use the `[minimal]` dependencies (e.g `python -m pip install "titiler.xarray[minimal]"`).
+
+Here is the list of available options:
+
+- **full**: `zarr`, `h5netcdf`, `fsspec`, `s3fs`, `aiohttp`, `gcsfs`
+- **minimal**: `zarr`, `h5netcdf`, `fsspec`
+- **gcs**: `gcsfs`
+- **s3**: `s3fs`
+- **http**: `aiohttp`
+
+## How To
+
+```python
+from fastapi import FastAPI
+
+from titiler.xarray.extensions import VariablesExtension
+from titiler.xarray.factory import TilerFactory
+
+app = FastAPI(
+ openapi_url="/api",
+ docs_url="/api.html",
+ description="""Xarray based tiles server for MultiDimensional dataset (Zarr/NetCDF).
+
+---
+
+**Documentation**: https://developmentseed.org/titiler/
+
+**Source Code**: https://github.com/developmentseed/titiler
+
+---
+ """,
+)
+
+md = TilerFactory(
+ router_prefix="/md",
+ extensions=[
+ VariablesExtension(),
+ ],
+)
+app.include_router(md.router, prefix="/md", tags=["Multi Dimensional"])
+```
+
+## Package structure
+
+```
+titiler/
+ └── xarray/
+ ├── tests/ - Tests suite
+ └── titiler/xarray/ - `xarray` namespace package
+ ├── dependencies.py - titiler-xarray dependencies
+ ├── extentions.py - titiler-xarray extensions
+ ├── io.py - titiler-xarray Readers
+ └── factory.py - endpoints factory
+```
+
+## Custom Dataset Opener
+
+A default Dataset IO is provided within `titiler.xarray.Reader` class but will require optional dependencies (`fsspec`, `zarr`, `h5netcdf`, ...) to be installed with `python -m pip install "titiler.xarray[full]"`.
+Dependencies are optional so the entire package size can be optimized to only include dependencies required by a given application.
+
+Example:
+
+**requirements**:
+- `titiler.xarray` (base)
+- `h5netcdf`
+
+
+```python
+from typing import Callable
+import attr
+from fastapi import FastAPI
+from titiler.xarray.io import Reader
+from titiler.xarray.extensions import VariablesExtension
+from titiler.xarray.factory import TilerFactory
+
+import xarray
+import h5netcdf # noqa
+
+# Create a simple Custom reader, using `xarray.open_dataset` opener
+@attr.s
+class CustomReader(Reader):
+ """Custom io.Reader using xarray.open_dataset opener."""
+ # xarray.Dataset options
+ opener: Callable[..., xarray.Dataset] = attr.ib(default=xarray.open_dataset)
+
+
+# Create FastAPI application
+app = FastAPI(openapi_url="/api", docs_url="/api.html")
+
+# Create custom endpoints with the CustomReader
+md = TilerFactory(
+ reader=CustomReader,
+ router_prefix="/md",
+ extensions=[
+ # we also want to use the simple opener for the Extension
+ VariablesExtension(dataset_opener==xarray.open_dataset),
+ ],
+)
+
+app.include_router(md.router, prefix="/md", tags=["Multi Dimensional"])
+```
diff --git a/src/titiler/xarray/notebooks/xarray_dataset_cache.ipynb b/src/titiler/xarray/notebooks/xarray_dataset_cache.ipynb
new file mode 100644
index 000000000..23e87b7a4
--- /dev/null
+++ b/src/titiler/xarray/notebooks/xarray_dataset_cache.ipynb
@@ -0,0 +1,168 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Add Caching Layer for Xarray Dataset"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\n",
+ "import pickle\n",
+ "from typing import Any, Callable, List, Optional\n",
+ "\n",
+ "import attr\n",
+ "import xarray\n",
+ "from morecantile import TileMatrixSet\n",
+ "from rio_tiler.constants import WEB_MERCATOR_TMS\n",
+ "from rio_tiler.io.xarray import XarrayReader\n",
+ "\n",
+ "from titiler.xarray.io import xarray_open_dataset, get_variable\n",
+ "\n",
+ "from diskcache import Cache\n",
+ "\n",
+ "cache_client = Cache()\n",
+ "\n",
+ "\n",
+ "@attr.s\n",
+ "class CustomReader(XarrayReader):\n",
+ " \"\"\"Reader: Open Zarr file and access DataArray.\"\"\"\n",
+ "\n",
+ " src_path: str = attr.ib()\n",
+ " variable: str = attr.ib()\n",
+ "\n",
+ " # xarray.Dataset options\n",
+ " opener: Callable[..., xarray.Dataset] = attr.ib(default=xarray_open_dataset)\n",
+ "\n",
+ " group: Optional[Any] = attr.ib(default=None)\n",
+ " decode_times: bool = attr.ib(default=False)\n",
+ "\n",
+ " # xarray.DataArray options\n",
+ " datetime: Optional[str] = attr.ib(default=None)\n",
+ " drop_dim: Optional[str] = attr.ib(default=None)\n",
+ "\n",
+ " tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)\n",
+ "\n",
+ " ds: xarray.Dataset = attr.ib(init=False)\n",
+ " input: xarray.DataArray = attr.ib(init=False)\n",
+ "\n",
+ " _dims: List = attr.ib(init=False, factory=list)\n",
+ "\n",
+ " def __attrs_post_init__(self):\n",
+ " \"\"\"Set bounds and CRS.\"\"\"\n",
+ " ds = None\n",
+ " # Generate cache key and attempt to fetch the dataset from cache\n",
+ " cache_key = f\"{self.src_path}_group:{self.group}_time:{self.decode_times}\"\n",
+ " data_bytes = cache_client.get(cache_key)\n",
+ " if data_bytes:\n",
+ " print(f\"Found dataset in Cache {cache_key}\")\n",
+ " ds = pickle.loads(data_bytes)\n",
+ "\n",
+ " self.ds = ds or self.opener(\n",
+ " self.src_path,\n",
+ " group=self.group,\n",
+ " decode_times=self.decode_times,\n",
+ " )\n",
+ " if not ds:\n",
+ " # Serialize the dataset to bytes using pickle\n",
+ " cache_key = f\"{self.src_path}_group:{self.group}_time:{self.decode_times}\"\n",
+ " data_bytes = pickle.dumps(self.ds)\n",
+ " print(f\"Adding dataset in Cache: {cache_key}\")\n",
+ " cache_client.set(cache_key, data_bytes, tag=\"data\", expire=300)\n",
+ "\n",
+ " self.input = get_variable(\n",
+ " self.ds,\n",
+ " self.variable,\n",
+ " datetime=self.datetime,\n",
+ " drop_dim=self.drop_dim,\n",
+ " )\n",
+ " super().__attrs_post_init__()\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Adding dataset in Cache: ../tests/fixtures/dataset_2d.nc_group:None_time:False\n",
+ " Size: 16MB\n",
+ "Dimensions: (x: 2000, y: 1000)\n",
+ "Coordinates:\n",
+ " * x (x) float64 16kB -170.0 -169.8 -169.7 -169.5 ... 169.5 169.7 169.8\n",
+ " * y (y) float64 8kB -80.0 -79.84 -79.68 -79.52 ... 79.52 79.68 79.84\n",
+ "Data variables:\n",
+ " dataset (y, x) float64 16MB ...\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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bHH5/X6de+65ksR1XWdB/fSM+ukZ9XN21FHPeqqfeFtap/F7XAAAqxBcAACrWBtcgUBAK1eu5KLlr0H99Iz66Rn1c3bUUc8Gsn6snJwBAHCXtS0uAICbCC4AgFUILgCAVQguAIBVrAyup59+Wueff77OOeccFRUV6e233051kVLiZz/7mXw+X8wyefLk6PqTJ0+qsrJSo0eP1siRI1VRUXHGj3h6zc6dO3XdddcpPz9fPp9PL730Usx6Y4wefvhh5eXladiwYSopKdHHH38cs83Ro0e1ePFiZWRkKCsrS7fffruOHz+exL0YOGern1tvvfWMz1RZWVnMNl6tn9raWs2aNUujRo1STk6O5s+fr4aGhphtevOdampq0rx58zR8+HDl5OTo3nvvVWdnZzJ3ZcD0po6uvPLKMz5Dd9xxR8w2/a0j64LrL3/5i6qrq7Vy5Uq99957mjFjhkpLS2N+mNIlF110kZqbm6PL66+/Hl1311136a9//as2bNigHTt26NNPP9XChQtTWNqBd+LECc2YMUNPP/10t+tXr16tJ598UmvWrNGuXbs0YsQIlZaW6uTJk9FtFi9erA8++EBbtmzRpk2btHPnTi1btixZuzCgzlY/klRWVhbzmXr++edj1nu1fnbs2KHKykq99dZb2rJlizo6OjR37lydOHEius3ZvlNdXV2aN2+eTp06pTfffFPPPvus1q1bp4cffjgVu5RwvakjSVq6dGnMZ2j16tXRdQmpI2OZSy+91FRWVkYfd3V1mfz8fFNbW5vCUqXGypUrzYwZM7pd19raaoYOHWo2bNgQfe7DDz80kkxdXV2SSphakszGjRujjyORiAkGg+ZXv/pV9LnW1lYTCATM888/b4wxZt++fUaSeeedd6LbvPLKK8bn85n//Oc/SSt7Mny1fowxZsmSJeb666//2te4VD+HDh0yksyOHTuMMb37Tv397383fr/fhEKh6DbPPPOMycjIMO3t7cndgST4ah0ZY8z3vvc985Of/ORrX5OIOrKqxXXq1CnV19erpKQk+pzf71dJSYnq6upSWLLU+fjjj5Wfn68JEyZo8eLFampqkiTV19ero6Mjpq4mT56scePGOVtXBw4cUCgUiqmTzMxMFRUVReukrq5OWVlZuuSSS6LblJSUyO/3a9euXUkvcyps375dOTk5uuCCC7R8+XIdOXIkus6l+mlra5MkZWdnS+rdd6qurk7Tpk1Tbm5udJvS0lKFw2F98MEHSSx9cny1jk577rnnNGbMGE2dOlU1NTX6/PPPo+sSUUdW3WT3s88+U1dXV8wOS1Jubq4++uijFJUqdYqKirRu3TpdcMEFam5u1qpVq3TFFVdo7969CoVCSk9PV1ZWVsxrcnNzFQqFUlPgFDu93919fk6vC4VCysnJiVk/ZMgQZWdnO1FvZWVlWrhwoQoLC7V//3498MADKi8vV11dndLS0pypn0gkojvvvFOXX365pk6dKkm9+k6FQqFuP1+n13lJd3UkSTfddJPGjx+v/Px87dmzR/fff78aGhr04osvSkpMHVkVXIhVXl4e/ff06dNVVFSk8ePH64UXXtCwYcNSWDLYatGiRdF/T5s2TdOnT9fEiRO1fft2zZkzJ4UlS67Kykrt3bs3ZswYsb6ujr483jlt2jTl5eVpzpw52r9/vyZOnJiQv21VV+GYMWOUlpZ2xiyelpYWBYPBFJVq8MjKytK3vvUtNTY2KhgM6tSpU2ptbY3ZxuW6Or3fPX1+gsHgGRN9Ojs7dfToUSfrbcKECRozZowaGxsluVE/VVVV2rRpk1577TWdd9550ed7850KBoPdfr5Or/OKr6uj7hQVFUlSzGeov3VkVXClp6dr5syZ2rp1a/S5SCSirVu3qri4OIUlGxyOHz+u/fv3Ky8vTzNnztTQoUNj6qqhoUFNTU3O1lVhYaGCwWBMnYTDYe3atStaJ8XFxWptbVV9fX10m23btikSiUS/gC755JNPdOTIEeXl5Unydv0YY1RVVaWNGzdq27ZtKiwsjFnfm+9UcXGx/vnPf8aE+5YtW5SRkaEpU6YkZ0cG0NnqqDu7d++WpJjPUL/rqI+TSVLmz3/+swkEAmbdunVm3759ZtmyZSYrKytmhoor7r77brN9+3Zz4MAB88Ybb5iSkhIzZswYc+jQIWOMMXfccYcZN26c2bZtm3n33XdNcXGxKS4uTnGpB9axY8fM+++/b95//30jyfz6178277//vvn3v/9tjDHml7/8pcnKyjIvv/yy2bNnj7n++utNYWGh+eKLL6LvUVZWZr797W+bXbt2mddff91MmjTJ3HjjjanapYTqqX6OHTtm7rnnHlNXV2cOHDhgXn31VfOd73zHTJo0yZw8eTL6Hl6tn+XLl5vMzEyzfft209zcHF0+//zz6DZn+051dnaaqVOnmrlz55rdu3ebzZs3m7Fjx5qamppU7FLCna2OGhsbzSOPPGLeffddc+DAAfPyyy+bCRMmmNmzZ0ffIxF1ZF1wGWPMU089ZcaNG2fS09PNpZdeat56661UFyklbrjhBpOXl2fS09PNueeea2644QbT2NgYXf/FF1+YH//4x+Yb3/iGGT58uFmwYIFpbm5OYYkH3muvvWYknbEsWbLEGPPfKfEPPfSQyc3NNYFAwMyZM8c0NDTEvMeRI0fMjTfeaEaOHGkyMjLMbbfdZo4dO5aCvUm8nurn888/N3PnzjVjx441Q4cONePHjzdLly4946TQq/XTXb1IMmvXro1u05vv1L/+9S9TXl5uhg0bZsaMGWPuvvtu09HRkeS9GRhnq6OmpiYze/Zsk52dbQKBgPnmN79p7r33XtPW1hbzPv2tI37WBABgFavGuAAAILgAAFYhuAAAViG4AABWIbgAAFYhuAAAViG4AABWIbgAAFYhuAAAViG4AABWIbgAAFYhuAAAVvn/DbXKOsc2f2sAAAAASUVORK5CYII=",
+ "text/plain": [
+ "